export type CopyAnalysis = { length: number; words: number; sentences: number; avgWordsPerSentence: number; readability: number; // 0..100, higher = easier keywordMatches: string[]; missingKeywords: string[]; seoScore: number; // 0..100 geoScore: number; // 0..100 seoTips: string[]; geoTips: string[]; }; const SYLLABLE_VOWELS = /[aeiouàèéìòùAEIOUÀÈÉÌÒÙ]+/g; function countSyllables(word: string): number { const matches = word.match(SYLLABLE_VOWELS); return matches ? matches.length : 1; } export function analyzeCopy(text: string, primaryKeywords: string[] = []): CopyAnalysis { const clean = text.trim(); const length = clean.length; const wordList = clean.split(/\s+/).filter(Boolean); const words = wordList.length; const sentences = Math.max(1, (clean.match(/[.!?]+/g) ?? []).length); const avgWordsPerSentence = words / sentences; // Flesch reading ease approximation const syllables = wordList.reduce((n, w) => n + countSyllables(w), 0); const asl = words ? words / sentences : 0; const asw = words ? syllables / words : 0; const readability = Math.max(0, Math.min(100, Math.round(206.835 - 1.015 * asl - 84.6 * asw))); const lower = clean.toLowerCase(); const keywordMatches: string[] = []; const missingKeywords: string[] = []; for (const kw of primaryKeywords) { if (!kw) continue; if (lower.includes(kw.toLowerCase())) keywordMatches.push(kw); else missingKeywords.push(kw); } // SEO tips const seoTips: string[] = []; if (length === 0) seoTips.push('Il testo è vuoto.'); if (length > 0 && length < 40) seoTips.push('Testo molto breve: difficile farsi indicizzare.'); if (length > 320) seoTips.push('Testo lungo: valuta di spezzarlo in paragrafi o bullet.'); if (avgWordsPerSentence > 24) seoTips.push('Frasi troppo lunghe: scendi sotto le 24 parole per frase.'); if (readability < 45) seoTips.push('Leggibilità bassa: semplifica lessico e sintassi.'); if (missingKeywords.length && words > 4) { seoTips.push(`Manca una keyword primaria: ${missingKeywords.slice(0, 2).join(', ')}.`); } if (/[A-Z]{6,}/.test(clean)) seoTips.push('Evita testo tutto maiuscolo: penalizza la leggibilità.'); // GEO tips (Generative Engine Optimization) const geoTips: string[] = []; const questionMarks = (clean.match(/\?/g) ?? []).length; const hasNumbers = /\d/.test(clean); const hasEntities = /\b(sassari|sardegna|coni|cammino di santiago|giampy)\b/i.test(clean); const firstPerson = /\b(io|mio|mia|miei|mie|sono|faccio|porto|prendo)\b/i.test(clean); const declarative = sentences >= 1 && /[.!]/.test(clean); if (words >= 20 && questionMarks === 0) geoTips.push('Aggiungi una domanda esplicita per farti citare come risposta diretta.'); if (!hasNumbers && words >= 20) geoTips.push('Inserisci dati concreti (anni, numero clienti, distanze) per aumentare la citabilità.'); if (!hasEntities && words >= 15) geoTips.push('Nomina entità specifiche (luogo, certificazione, brand) per il grounding generativo.'); if (!firstPerson && words >= 20) geoTips.push('Usa la prima persona: i motori generativi estraggono più facilmente testimonianze autoriali.'); if (!declarative && words >= 10) geoTips.push('Termina con punto fermo: le frasi complete vengono citate più spesso.'); // Scores let seoScore = 100; seoScore -= seoTips.length * 12; if (keywordMatches.length === 0 && primaryKeywords.length && words > 4) seoScore -= 10; if (readability >= 60) seoScore += 5; seoScore = Math.max(0, Math.min(100, seoScore)); let geoScore = 100; geoScore -= geoTips.length * 14; if (hasEntities) geoScore += 5; if (hasNumbers) geoScore += 3; geoScore = Math.max(0, Math.min(100, geoScore)); return { length, words, sentences, avgWordsPerSentence: Math.round(avgWordsPerSentence * 10) / 10, readability, keywordMatches, missingKeywords, seoScore, geoScore, seoTips, geoTips }; } export function aggregateScore(analyses: CopyAnalysis[]): { seo: number; geo: number } { if (!analyses.length) return { seo: 0, geo: 0 }; const seo = Math.round(analyses.reduce((a, x) => a + x.seoScore, 0) / analyses.length); const geo = Math.round(analyses.reduce((a, x) => a + x.geoScore, 0) / analyses.length); return { seo, geo }; }