- scripts/taxonomy.py: shared taxonomy with 14 categories, keyword scorer and classify_text() function - scripts/classify_server.py: FastAPI service — forwards to kreuzberg /extract, applies taxonomy, returns category/subcategory/confidence alongside full kreuzberg response - Dockerfile.classify: lightweight Python image for classify service - classify.nomad: Nomad job → classify.i80.dk - .gitea/workflows/classify.yml: CI/CD pipeline (build + deploy) - analyse_familie.py: refactored to import from taxonomy.py (no duplication) - .gitignore: exclude dokumenter_keywords.* and extract_all.log
232 lines
12 KiB
Python
232 lines
12 KiB
Python
"""Shared taxonomy: weighted keyword lists + folder mappings + scorer.
|
|
|
|
Used by both analyse_familie.py (batch classify) and classify_server.py (API endpoint).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
TAXONOMY: dict[str, list[tuple[str, float]]] = {
|
|
"Familie og børn": [
|
|
("familie", 1.5), ("familieliv", 2.0), ("samvær", 2.0), ("samværsaftale", 2.5),
|
|
("børn", 1.5), ("barn", 1.5), ("forældre", 2.0), ("forældremyndighed", 2.5),
|
|
("skilsmisse", 2.5), ("separation", 2.0), ("barsel", 2.0), ("barnets", 1.5),
|
|
("søskende", 2.0), ("mor", 1.0), ("far", 1.0), ("mor og far", 2.5),
|
|
("dåb", 2.0), ("konfirmation", 2.5), ("bryllup", 2.0), ("vielse", 2.0),
|
|
("fodselsdag", 1.5), ("fødselsdagskort", 2.0),
|
|
],
|
|
"Skole og uddannelse": [
|
|
("skole", 1.5), ("uddannelse", 1.5), ("gymnasium", 2.5), ("universitetet", 2.0),
|
|
("eksamen", 2.0), ("studieplan", 2.5), ("karakter", 2.0), ("lektier", 2.5),
|
|
("opgave", 1.5), ("matematik", 2.0), ("dansk", 1.0), ("noter", 1.5),
|
|
("pensum", 2.5), ("studie", 1.5), ("kursus", 1.5), ("folkeskole", 2.5),
|
|
("htx", 2.5), ("hf", 2.0), ("hhx", 2.5), ("stx", 2.5), ("eux", 2.5),
|
|
("karakterblad", 3.0), ("eksamensbevis", 3.0), ("studiekort", 3.0),
|
|
("answer key", 2.5), ("quiz", 2.5), ("assessment", 2.5), ("learning", 1.5),
|
|
("lecture", 2.0), ("course", 2.0), ("lesson", 2.0), ("worksheet", 2.5),
|
|
],
|
|
"Arbejde og karriere": [
|
|
("ansøgning", 1.5), ("job", 1.5), ("jobansøgning", 2.5), ("cv", 2.5),
|
|
("curriculum vitae", 3.0), ("opsigelse", 2.5), ("løn", 1.5), ("lønforhandling", 2.5),
|
|
("ansættelseskontrakt", 3.0), ("ansættelse", 2.0), ("arbejdsplads", 2.0),
|
|
("kollega", 2.0), ("arbejdsgiver", 2.5), ("medarbejder", 2.0), ("fagforening", 2.5),
|
|
("a-kasse", 2.5), ("dagpenge", 2.5), ("jobcenter", 2.5), ("referenceliste", 3.0),
|
|
("karriere", 2.0), ("rekruttering", 2.5), ("personaleafdeling", 2.5),
|
|
("arbejde", 1.5), ("projektleder", 2.5), ("møde", 1.5), ("mødedagsorden", 2.5),
|
|
("scrum", 2.5), ("agile", 2.5), ("backlog", 2.5), ("sprint", 2.5),
|
|
("konference", 2.0), ("kompetencer", 2.0),
|
|
],
|
|
"Økonomi og regninger": [
|
|
("faktura", 2.5), ("regning", 2.0), ("betaling", 2.0), ("bank", 2.0),
|
|
("skat", 2.0), ("pension", 2.0), ("opsparing", 2.5), ("gæld", 2.5),
|
|
("lån", 2.5), ("kredit", 2.0), ("inkasso", 3.0), ("afdrag", 2.5),
|
|
("akkord", 3.0), ("restgæld", 3.0), ("kreditor", 2.5), ("økonomi", 1.5),
|
|
("budget", 2.0), ("forsikring", 2.0), ("rykkerbrev", 3.0), ("udbetaling", 2.0),
|
|
("sparekasse", 2.5), ("betalingsservice", 3.0), ("gældstyrelsen", 3.0),
|
|
("netto bank", 2.5), ("netbank", 2.5), ("kontoudtog", 3.0), ("årsopgørelse", 2.5),
|
|
("restskat", 3.0), ("årsopgørelse skat", 3.0), ("momsangivelse", 3.0),
|
|
],
|
|
"Hjem og bolig": [
|
|
("bolig", 2.0), ("hus", 1.5), ("lejlighed", 2.5), ("ejendom", 2.0),
|
|
("husleje", 3.0), ("vedligeholdelse", 2.5), ("renovation", 2.5),
|
|
("el", 1.0), ("vand", 1.0), ("varme", 1.5), ("fjernvarme", 2.5),
|
|
("ejerforening", 3.0), ("andelsbolig", 3.0), ("lejekontrakt", 3.0),
|
|
("fremlejning", 2.5), ("nøgle", 1.5), ("flytning", 2.0),
|
|
("indretning", 2.0), ("have", 1.5), ("grundejerforening", 3.0),
|
|
("BBR", 2.5), ("byggetilladelse", 3.0),
|
|
],
|
|
"Jura og kontrakter": [
|
|
("kontrakt", 2.0), ("aftale", 2.0), ("kontrakter", 2.0), ("juridisk", 2.5),
|
|
("advokat", 2.5), ("testamente", 3.0), ("retssag", 3.0), ("dom", 2.0),
|
|
("stævning", 3.0), ("klage", 2.0), ("tinglysning", 3.0), ("pantebrev", 3.0),
|
|
("tilbud", 1.5), ("vilkår", 2.0), ("betingelser", 2.0), ("fuldmagt", 2.5),
|
|
("forlig", 2.5), ("forsikringsbetingelser", 3.0), ("police", 2.0),
|
|
],
|
|
"Sundhed og medicin": [
|
|
("recept", 2.5), ("medicin", 2.5), ("læge", 2.5), ("hospital", 2.5),
|
|
("sygdom", 2.5), ("behandling", 2.0), ("diagnose", 3.0), ("operation", 2.5),
|
|
("symptomer", 2.5), ("sundhed", 2.0), ("journaloplysning", 3.0),
|
|
("patientjournal", 3.0), ("laboratorium", 2.5), ("blodprøve", 3.0),
|
|
("røntgen", 3.0), ("psykolog", 3.0), ("psykiater", 3.0), ("terapi", 2.5),
|
|
("tandlæge", 3.0), ("optiker", 2.5), ("vaccination", 3.0),
|
|
],
|
|
"IT og teknologi": [
|
|
("software", 2.5), ("server", 2.0), ("netværk", 2.5), ("database", 2.5),
|
|
("programmering", 2.5), ("kode", 2.0), ("linux", 3.0), ("cloud", 2.5),
|
|
("it", 1.5), ("computer", 2.0), ("laptop", 2.5), ("password", 2.5),
|
|
("installation", 2.0), ("konfiguration", 2.0), ("log", 1.5), ("backup", 2.5),
|
|
("docker", 3.0), ("kubernetes", 3.0), ("python", 3.0), ("github", 3.0),
|
|
("azure", 2.5), ("windows", 2.0), ("macos", 3.0), ("licens", 2.0),
|
|
("api", 2.5), ("dokumentation", 1.5), ("teknologi", 2.0), ("system", 1.5),
|
|
("web", 2.0), ("app", 1.5), ("program", 1.5), ("firmware", 3.0),
|
|
("internet", 2.0), ("cybersikkerhed", 3.0),
|
|
("bitcoin", 3.0), ("blockchain", 3.0), ("kryptovaluta", 3.0), ("jupyter", 3.0),
|
|
("notebook", 2.5), ("monitor", 2.0), ("display", 2.0), ("remote control", 2.5),
|
|
("user manual", 2.5), ("dataanalyse", 2.5), ("data analysis", 2.5),
|
|
("django", 3.0), ("javascript", 2.5), ("jquery", 2.5), ("typescript", 3.0),
|
|
("html", 2.0), ("css", 2.0), ("react", 2.5), ("nodejs", 3.0), ("java", 2.5),
|
|
("csharp", 3.0), ("datamatiker", 3.0), ("sql", 2.5), ("rest api", 3.0),
|
|
("programming", 2.5), ("developer", 2.0), ("debugging", 2.5), ("testing", 2.0),
|
|
],
|
|
"Bøger og litteratur": [
|
|
("isbn", 3.0), ("forlag", 1.5), ("roman", 3.0), ("novelle", 3.0),
|
|
("biografi", 3.0), ("poesi", 3.0), ("digtsamling", 3.0), ("bog", 2.0),
|
|
("litteratur", 2.5), ("forfatter", 3.0), ("kapitel", 2.0), ("bogklub", 3.0),
|
|
("bibliotek", 2.5), ("e-bog", 3.0), ("lydbog", 3.0), ("udgivelse", 2.0),
|
|
("biography", 2.5), ("novel", 2.5), ("author", 2.0), ("chapter", 2.0),
|
|
("publisher", 2.0), ("edition", 2.0), ("paperback", 3.0), ("hardcover", 3.0),
|
|
("fiction", 3.0), ("nonfiction", 3.0), ("memoir", 3.0),
|
|
],
|
|
"Rejse og transport": [
|
|
("rejse", 2.0), ("ferie", 2.0), ("fly", 2.5), ("hotel", 2.5),
|
|
("booking", 2.5), ("rejseplan", 2.5), ("pas", 2.0), ("visum", 3.0),
|
|
("bil", 1.5), ("kørekort", 3.0), ("tog", 2.0), ("billet", 2.5),
|
|
("flyrejse", 3.0), ("afgangsgate", 3.0), ("baggage", 2.5), ("cruise", 3.0),
|
|
("afrejse", 2.5), ("ankomst", 2.0), ("itinerary", 3.0), ("pakketur", 2.5),
|
|
],
|
|
"Offentlige myndigheder": [
|
|
("kommune", 2.5), ("stat", 1.5), ("styrelse", 2.5), ("forvaltning", 2.5),
|
|
("gældstyrelsen", 3.0), ("skat", 2.0), ("udbetaling danmark", 3.0),
|
|
("borger.dk", 3.0), ("digitalpost", 2.5), ("afgørelse", 2.5),
|
|
("offentlig myndighed", 3.0), ("ministeri", 2.5), ("ministeriet", 2.5),
|
|
("politi", 2.5), ("domstol", 2.5), ("retsinformation", 3.0),
|
|
("folketing", 2.5), ("region", 2.0), ("jobcenter", 2.5),
|
|
("borger", 1.5), ("ansøgning kommune", 3.0), ("nykøbingvej", 2.5),
|
|
("sakskøbing", 2.5), ("akkordansøgning", 3.0),
|
|
],
|
|
"Projekter og hobby": [
|
|
("hobby", 2.5), ("projekt", 2.0), ("frivillig", 2.5), ("klub", 2.0),
|
|
("aktivitet", 2.0), ("sport", 2.5), ("musik", 2.5), ("opskrift", 2.5),
|
|
("træning", 2.0), ("kreativ", 2.5), ("håndværk", 2.5), ("fotografi", 2.5),
|
|
("spil", 2.0), ("gaming", 3.0), ("maleri", 2.5), ("tegning", 2.0),
|
|
("golf", 3.0), ("fitness", 2.5), ("klippekort", 2.5), ("svømning", 2.5),
|
|
("cykling", 2.5), ("løb", 1.5), ("boldspil", 2.5), ("fodbold", 2.5),
|
|
("concert", 2.0), ("festival", 2.5),
|
|
],
|
|
"Teknik og ingeniørfag": [
|
|
("tegning", 2.0), ("teknisk tegning", 3.0), ("ingeniør", 2.5), ("konstruktion", 2.5),
|
|
("maskine", 2.5), ("elektroteknik", 3.0), ("specifikation", 2.0),
|
|
("diagram", 2.0), ("brugsanvisning", 3.0), ("manual", 2.5), ("datablad", 3.0),
|
|
("CE-mærkning", 3.0), ("ISO", 2.0), ("norm", 2.0), ("standard", 1.5),
|
|
("user manual", 2.5), ("installation guide", 3.0), ("technical specification", 3.0),
|
|
("product guide", 2.5), ("service manual", 3.0),
|
|
],
|
|
"Erhverv og business": [
|
|
("virksomhed", 2.5), ("erhverv", 2.5), ("CVR", 3.0), ("faktura", 2.5),
|
|
("ordre", 2.0), ("leverandør", 2.5), ("kunde", 2.0), ("salg", 2.0),
|
|
("moms", 2.5), ("regnskab", 2.5), ("årsregnskab", 3.0), ("balance", 2.0),
|
|
("resultatopgørelse", 3.0), ("aktieselskab", 3.0), ("iværksætter", 2.5),
|
|
("forretning", 2.0), ("selskab", 2.0), ("ApS", 3.0), ("A/S", 3.0),
|
|
],
|
|
}
|
|
|
|
TAXONOMY_TO_FOLDER: dict[str, str] = {
|
|
"Familie og børn": "Privat/Familie",
|
|
"Skole og uddannelse": "Privat/Personlig/Uddannelse",
|
|
"Arbejde og karriere": "Privat/Personlig/Arbejde",
|
|
"Økonomi og regninger": "Privat/Økonomi",
|
|
"Hjem og bolig": "Privat/Hjem/Bolig",
|
|
"Jura og kontrakter": "Privat/Jura",
|
|
"Sundhed og medicin": "Privat/Personlig/Sundhed",
|
|
"IT og teknologi": "Arkiv/Teknisk",
|
|
"Bøger og litteratur": "Arkiv/Bøger",
|
|
"Rejse og transport": "Privat/Rejser",
|
|
"Offentlige myndigheder": "Privat/Jura/Myndigheder",
|
|
"Projekter og hobby": "Projekter",
|
|
"Teknik og ingeniørfag": "Arkiv/Teknisk",
|
|
"Erhverv og business": "Arkiv/Erhverv",
|
|
}
|
|
|
|
MIN_SCORE: float = 1.5
|
|
|
|
|
|
def keyword_score(doc_text: str, keywords: list[tuple[str, float]]) -> float:
|
|
"""Score a document against a keyword list.
|
|
|
|
Multi-word phrases are matched as substrings; single words are matched as
|
|
whole words (word boundary) to avoid false positives (e.g. 'bil' in 'mobil').
|
|
Returns the sum of weights for all matching entries.
|
|
"""
|
|
text = doc_text.lower()
|
|
total = 0.0
|
|
for kw, weight in keywords:
|
|
kw_lower = kw.lower()
|
|
if " " in kw_lower:
|
|
if kw_lower in text:
|
|
total += weight
|
|
else:
|
|
idx = text.find(kw_lower)
|
|
while idx != -1:
|
|
before = text[idx - 1] if idx > 0 else " "
|
|
after = text[idx + len(kw_lower)] if idx + len(kw_lower) < len(text) else " "
|
|
if not before.isalpha() and not after.isalpha():
|
|
total += weight
|
|
break
|
|
idx = text.find(kw_lower, idx + 1)
|
|
return round(total, 3)
|
|
|
|
|
|
def classify_text(
|
|
content: str,
|
|
keywords: list[str],
|
|
folder_hint: str = "",
|
|
min_score: float = MIN_SCORE,
|
|
) -> dict:
|
|
"""Classify document text + keywords against the taxonomy.
|
|
|
|
Args:
|
|
content: Extracted document text.
|
|
keywords: YAKE keyword strings from kreuzberg.
|
|
folder_hint: Current folder path (used as additional context signal).
|
|
min_score: Minimum score to assign a label (else 'Ukendt').
|
|
|
|
Returns:
|
|
dict with category, subcategory, confidence, runner_up, runner_up_score.
|
|
"""
|
|
kw_text = " ".join(keywords)
|
|
folder_tokens = folder_hint.replace("/", " ").replace("_", " ").replace("-", " ")
|
|
doc_text = f"{content} {folder_tokens} {kw_text}"
|
|
|
|
scores = {
|
|
cat: keyword_score(doc_text, kws)
|
|
for cat, kws in TAXONOMY.items()
|
|
}
|
|
|
|
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)
|
|
best_label, best_score = sorted_scores[0]
|
|
runner_up_label, runner_up_score = sorted_scores[1] if len(sorted_scores) > 1 else ("", 0.0)
|
|
|
|
if best_score >= min_score:
|
|
category = best_label
|
|
subcategory = TAXONOMY_TO_FOLDER.get(best_label, "")
|
|
else:
|
|
category = "Ukendt"
|
|
subcategory = ""
|
|
|
|
return {
|
|
"category": category,
|
|
"subcategory": subcategory,
|
|
"confidence": round(best_score, 3),
|
|
"runner_up": runner_up_label if best_score >= min_score else best_label,
|
|
"runner_up_score": round(runner_up_score, 3),
|
|
}
|