Files
ai-resume/corpus/areas.py

95 lines
3.6 KiB
Python

"""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])