"""Which resume area a harvested document feeds. The payload `area` is what lets the query path filter *before* it searches, so a harvested file has to be classified once, deterministically, and never promoted to make a page read better. The classification is keyword evidence, in this order: the path, the repository name, the description and topics, then the languages. The result is constrained to the platform guide's `fits` list: a platform cannot feed an area it does not declare. When nothing matches, the first `fits` entry wins, which is why every guide lists its strongest area first. This is a heuristic and it is labelled as one. It is not a claim about Sam's career; the curated `content/` tree carries the claims. """ from __future__ import annotations import re from .payloads import AREAS # Order matters: the first area with a hit wins, so the more specific evidence # (an ESP32 repository is edge, not platform) is checked before the general. _RULES = ( ("edge", ( "esp32", "esp8266", "arduino", "lora", "lorawan", "mqtt", "sensor", "zigbee", "z-wave", "relay", "gpio", "pir", "modbus", "iot", "firmware", "breadboard", "raspberry pi", "rpi", "camera", "cam", "temperature", "watering", "fish feeder", "driveway", "light", "snapcast", "mopidy", "audio", "voice bridge", )), ("ai", ( "ai", "llm", "agent", "agentic", "rag", "embedding", "vector", "model", "ml", "prompt", "graphify", "omniroute", "paperclip", "langgraph", "prefect", "whisper", "piper", "chat", "memory", "knowledge graph", "vision", "triage", )), ("platform", ( "nixos", "nix", "docker", "compose", "kubernetes", "k8s", "deploy", "deployment", "backup", "borg", "kopia", "restic", "network", "dns", "email", "caddy", "proxmox", "postgres", "terraform", "ansible", "linux", "systemd", "homelab", "server", "infrastructure", "git", "gitea", "monitor", "logging", "security", "firewall", "vpn", "tailscale", "storage", "garage", "s3", )), ("products", ( "product", "app", "web", "dashboard", "react", "laravel", "flutter", "kotlin", "calendar", "photo", "kontra", "store", "resume", "site", "gps", "tracker", "api", "frontend", "ui", "ux", )), ("history", ( "history", "agency", "client", "archive", "old", "legacy", "2019", "2018", "2020", "2021", "career", "resume copy", )), ("about", ("about", "profile", "bio", "personal", "cv")), ) _WORD_SPLIT = re.compile(r"[^0-9a-z]+") def area_for(evidence: str, fits, fallback: str = None) -> str: """Classify one document from its path, name, description and topics.""" haystack = " " + _WORD_SPLIT.sub(" ", (evidence or "").lower()) + " " allowed = [area for area in (fits or AREAS) if area in AREAS] if not allowed: allowed = list(AREAS) for area, needles in _RULES: if area not in allowed: continue for needle in needles: if f" {needle} " in haystack or f" {needle}s " in haystack: return area if fallback in allowed: return fallback return allowed[0] def area_for_repo(repo: dict, fits) -> str: topics = " ".join(repo.get("topics") or []) evidence = " ".join( str(part) for part in ( repo.get("full_name", ""), repo.get("name", ""), repo.get("description", ""), topics, ) ) return area_for(evidence, fits, fallback=(fits or [None])[0]) def area_for_note(path: str, fits) -> str: return area_for(path, fits, fallback=(fits or [None])[0])