"""Stable internal identifiers: prefixed ULIDs (`model_01J…`). URLs and names change; ids never do.""" from __future__ import annotations import re from slugify import slugify as _slugify from ulid import ULID PREFIXES: dict[str, str] = { "model": "model", "model_family": "family", # Llama 4, Qwen3.6, Claude — groups model releases "artifact": "artifact", # checkpoint / quantisation / conversion of a model (never a model of its own) "company": "company", "organization": "org", "researcher": "person", "paper": "paper", "dataset": "dataset", "benchmark": "bench", "provider": "provider", "framework": "framework", "library": "lib", "repository": "repo", "tool": "tool", "agent": "agent", "application": "app", "hardware": "hw", "runtime": "runtime", "quantization": "quant", "license": "license", "conference": "conf", "university": "univ", "lab": "lab", "product": "product", "release": "release", "regulation": "reg", "incident": "incident", "country": "country", "mcp_server": "mcp", "robot": "robot", "data_center": "dc", "job": "job", "course": "course", "standard": "std", "funding_round": "funding", "acquisition": "acq", # infrastructure records "source": "src", "document": "doc", "snapshot": "snap", "claim": "claim", "relation": "rel", "change_event": "evt", "connector_run": "run", "queue_job": "qj", "llm_job": "llm", "review": "rev", "price": "price", "result": "res", "api_key": "key", } ENTITY_TYPES: tuple[str, ...] = tuple(k for k in PREFIXES if k not in { "source", "document", "snapshot", "claim", "relation", "change_event", "connector_run", "queue_job", "llm_job", "review", "price", "result", "api_key"}) def new_id(kind: str) -> str: prefix = PREFIXES.get(kind) if prefix is None: raise ValueError(f"unknown id kind {kind!r}") return f"{prefix}_{ULID()}" def kind_of(entity_id: str) -> str | None: prefix = entity_id.split("_", 1)[0] for kind, p in PREFIXES.items(): if p == prefix: return kind return None _slug_clean = re.compile(r"[^a-z0-9.+-]+") def slugify(text: str, *, max_length: int = 96) -> str: """URL slug that keeps dots and plus signs (model names like `qwen3-8b`, `gpt-4.1`, `c++`).""" s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9.+-]+", replacements=[("/", "-"), ("_", "-"), ("@", "-at-")]) s = _slug_clean.sub("-", s).strip("-.") return s[:max_length].rstrip("-.") or "item" def normalize_alias(text: str) -> str: """Deterministic alias key: lowercase, ASCII, punctuation collapsed. Used for entity resolution — never for display.""" s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9]+") return s.replace("-", "")