from __future__ import annotations import json from datetime import datetime from pathlib import Path from typing import Dict, List from build_vocabulary import ( FILTERED_OUTPUT_PATH, METADATA_OUTPUT_PATH, build_vocabulary, ) LEXICON_OUTPUT_PATH = Path(__file__).with_name("lexicon_it.json") POS_BY_TAG = { "function": "PREP", "verb_infinitive": "VERB", "adverb": "ADV", "adjective_like": "ADJ", "noun_like": "NOUN", } REGISTER_BY_QUALITY = [ (8, "common"), (5, "standard"), (3, "formal"), (0, "rare"), ] TOPIC_KEYWORDS = { "animals": { "cane", "gatto", "lupo", "volpe", "orso", "pesce", "cervo", "cavallo", "capra", "pecora", "leone", "tigre", "zebra", "aquila", "falco", "serpente", "vipera", "gabbiano", "anatra", "passero", "coniglio", "castoro", "bruco", "cigno", "asino", "alpaca", }, "plants": { "albero", "pianta", "fiore", "foglia", "radice", "seme", "bosco", "selva", "ulivo", "quercia", "ortica", "edera", "aloe", "tulipano", "spiga", "polline", "grano", "erba", }, "nature": { "natura", "bosco", "selva", "montagna", "collina", "roccia", "pietra", "fiume", "lago", "mare", "riva", "fonte", "onda", "vento", "fuoco", "terra", "sole", "luna", "aurora", "nuvola", "nebbia", "deserto", "isola", "greto", "radice", "fiore", "foglia", "erba", "zolla", }, "ecology": { "ambiente", "ecologia", "natura", "bosco", "energia", "acqua", "terra", "clima", "sorgere", "fonte", "solare", "verde", "ulivo", "pianta", "polline", "grano", "radice", }, "geography": { "montagna", "collina", "isola", "deserto", "equatore", "ovest", "oriente", "riva", "mare", "lago", "fiume", "ponte", "confine", "quota", "pianeta", "roccia", "greto", }, "weather": { "vento", "nebbia", "aurora", "pioggia", "sole", "nuvola", "tempesta", "brina", "sereno", "clima", "goccia", }, "sea": { "mare", "onda", "vela", "barca", "porto", "pesce", "ancora", "scoglio", "riva", "veliero", }, "mountain": { "montagna", "quota", "vetta", "roccia", "greto", "collina", "sentiero", "alpino", }, "health": { "salute", "febbre", "medico", "cura", "respiro", "diuretico", "anemico", "vigore", "energia", "dente", "cuore", "corpo", "viso", }, "science": { "atomo", "energia", "metodo", "equatore", "digitale", "misura", "tecnica", "triangolo", "microfibra", "microscopio", "algoritmo", "motore", "materia", "liquido", }, "sport": { "calcio", "atleta", "sportivo", "gol", "pallone", "gara", "trionfo", "primato", "allenatore", "stadio", "squadra", "rete", }, "history": { "re", "principe", "regno", "impero", "senato", "console", "legione", "vittoria", "epoca", "origine", "ritorno", }, "school": { "libro", "quaderno", "lezione", "classe", "studiare", "maestro", "scuola", "esame", "penna", "aula", "figura", "titolo", }, "cinema": { "film", "teatro", "attore", "scena", "dialogo", "regista", "pellicola", "voce", "visione", "finale", "figura", }, "literature": { "libro", "poesia", "favola", "fiaba", "frase", "parola", "lettura", "autore", "storia", "leggenda", "scrivere", "titolo", }, "food": { "pane", "cacao", "gelato", "burro", "latte", "mandorla", "nocciola", "cena", "pranzo", "zuppa", "zucchero", "acqua", "fiore", "frutto", }, "city": { "porta", "strada", "piazza", "ponte", "palazzo", "cortile", "villaggio", "citta", "urbano", "casale", "balcone", "finestra", "stazione", }, "transport": { "automobile", "barca", "vela", "treno", "motore", "viaggio", "ruota", "ponte", "pilota", "volo", "aeroporto", "vettura", }, "work": { "lavoro", "opera", "progetto", "metodo", "tecnica", "strumento", "martello", "guida", "mestiere", "servire", }, "home": { "casa", "finestra", "porta", "parete", "divano", "tavolo", "sedia", "camera", "balcone", "camino", "tetto", "cortile", "vasca", }, } TOPIC_SUFFIXES = { "actions": ("are", "ere", "ire"), "abstract": ("zione", "zioni", "ismo", "ezza", "ita", "mento", "anza", "enza"), "animals": ("cane", "gatto", "lupo", "pesce", "volpe", "orso"), "plants": ("fiore", "foglia", "seme", "radice", "erba"), "nature": ("mare", "lago", "bosco", "vento", "onda", "roccia"), "geography": ("montagna", "isola", "deserto", "confine"), "city": ("strada", "palazzo", "porta", "ponte"), } def infer_pos(tags: List[str]) -> str: for tag in tags: if tag in POS_BY_TAG: return POS_BY_TAG[tag] return "NOUN" def infer_topics(word: str, tags: List[str]) -> List[str]: topics = {"general"} if "verb_infinitive" in tags: topics.add("actions") if any(word.endswith(suffix) for suffix in ("tore", "trice", "zione", "ismo", "ista", "mento", "anza", "enza")): topics.add("abstract") for topic, keywords in TOPIC_KEYWORDS.items(): if word in keywords: topics.add(topic) for topic, suffixes in TOPIC_SUFFIXES.items(): if any(word.endswith(suffix) for suffix in suffixes): topics.add(topic) if "animals" in topics: topics.add("nature") if "plants" in topics: topics.update({"nature", "ecology"}) if "sea" in topics or "mountain" in topics or "weather" in topics: topics.add("nature") if "geography" in topics and "nature" not in topics: topics.add("nature") return sorted(topics) def infer_register(quality: int) -> str: for threshold, label in REGISTER_BY_QUALITY: if quality >= threshold: return label return "rare" def frequency_from_quality(quality: int, index: int, total: int) -> tuple[int, float]: rank = index + 1 normalized_rank = 1.0 - (rank - 1) / max(1, total - 1) quality_boost = min(max(quality, 0), 10) / 20.0 frequency_score = round(min(1.0, normalized_rank * 0.7 + quality_boost), 4) return rank, frequency_score def load_words() -> List[str]: if not FILTERED_OUTPUT_PATH.exists() or not METADATA_OUTPUT_PATH.exists(): build_vocabulary() words = [ line.strip() for line in FILTERED_OUTPUT_PATH.read_text(encoding="utf-8").splitlines() if line.strip() ] return words def load_metadata() -> Dict[str, Dict[str, object]]: if not METADATA_OUTPUT_PATH.exists(): build_vocabulary() return json.loads(METADATA_OUTPUT_PATH.read_text(encoding="utf-8")) def build_lexicon() -> Dict[str, object]: words = load_words() metadata = load_metadata() entries = [] total = len(words) for index, word in enumerate(words): meta = metadata.get(word, {}) tags = list(meta.get("tags", [])) quality = int(meta.get("quality", 0)) frequency_rank, frequency_score = frequency_from_quality(quality, index, total) entry = { "form": word, "normalized_form": word, "lemma": word, "pos": infer_pos(tags), "length": len(word), "frequency_rank": frequency_rank, "frequency_score": frequency_score, "difficulty_word": max(1, min(5, 6 - max(1, min(5, quality // 2 + 1)))), "allowed_in_crossword": True, "quality_score": max(0, min(10, quality)), "topics": infer_topics(word, tags), "morph_features": {}, "register": infer_register(quality), "source_flags": ["from_filtered_vocabulary", "from_metadata_heuristics"], "crossword_flags": tags, "notes": "", } entries.append(entry) return { "meta": { "language": "it", "version": 1, "sources": ["vocaboli_it_filtrato.txt", "vocaboli_it_metadata.json"], "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), "entry_count": len(entries), }, "entries": entries, } def main() -> None: lexicon = build_lexicon() LEXICON_OUTPUT_PATH.write_text( json.dumps(lexicon, ensure_ascii=False, indent=2), encoding="utf-8", ) print(f"Lessico generato: {LEXICON_OUTPUT_PATH}") print(f"Voci generate: {lexicon['meta']['entry_count']}") if __name__ == "__main__": main()