feat: add taxonomy classify service + /classify endpoint
- 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
This commit is contained in:
231
scripts/taxonomy.py
Normal file
231
scripts/taxonomy.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""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),
|
||||
}
|
||||
Reference in New Issue
Block a user