1"""Stable internal identifiers: prefixed ULIDs (`model_01J…`). URLs and names change; ids never do."""2from __future__ import annotations34import re56from slugify import slugify as _slugify7from ulid import ULID89PREFIXES: dict[str, str] = {10 "model": "model",11 "company": "company",12 "organization": "org",13 "researcher": "person",14 "paper": "paper",15 "dataset": "dataset",16 "benchmark": "bench",17 "provider": "provider",18 "framework": "framework",19 "library": "lib",20 "repository": "repo",21 "tool": "tool",22 "agent": "agent",23 "application": "app",24 "hardware": "hw",25 "runtime": "runtime",26 "quantization": "quant",27 "license": "license",28 "conference": "conf",29 "university": "univ",30 "lab": "lab",31 "product": "product",32 "release": "release",33 "regulation": "reg",34 "incident": "incident",35 "country": "country",36 "mcp_server": "mcp",37 "robot": "robot",38 "data_center": "dc",39 "job": "job",40 "course": "course",41 "standard": "std",42 "funding_round": "funding",43 "acquisition": "acq",44 # infrastructure records45 "source": "src",46 "document": "doc",47 "snapshot": "snap",48 "claim": "claim",49 "relation": "rel",50 "change_event": "evt",51 "connector_run": "run",52 "queue_job": "qj",53 "llm_job": "llm",54 "review": "rev",55 "price": "price",56 "result": "res",57 "api_key": "key",58}5960ENTITY_TYPES: tuple[str, ...] = tuple(k for k in PREFIXES if k not in {61 "source", "document", "snapshot", "claim", "relation", "change_event", "connector_run", "queue_job", "llm_job",62 "review", "price", "result", "api_key"})636465def new_id(kind: str) -> str:66 prefix = PREFIXES.get(kind)67 if prefix is None:68 raise ValueError(f"unknown id kind {kind!r}")69 return f"{prefix}_{ULID()}"707172def kind_of(entity_id: str) -> str | None:73 prefix = entity_id.split("_", 1)[0]74 for kind, p in PREFIXES.items():75 if p == prefix:76 return kind77 return None787980_slug_clean = re.compile(r"[^a-z0-9.+-]+")818283def slugify(text: str, *, max_length: int = 96) -> str:84 """URL slug that keeps dots and plus signs (model names like `qwen3-8b`, `gpt-4.1`, `c++`)."""85 s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9.+-]+", replacements=[("/", "-"), ("_", "-"), ("@", "-at-")])86 s = _slug_clean.sub("-", s).strip("-.")87 return s[:max_length].rstrip("-.") or "item"888990def normalize_alias(text: str) -> str:91 """Deterministic alias key: lowercase, ASCII, punctuation collapsed. Used for entity resolution — never for display."""92 s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9]+")93 return s.replace("-", "")94