SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
2.5 KB · 82 lines python
Raw Blame History
1"""Stable internal identifiers: prefixed ULIDs (`co_01J…`). Slugs, names and URLs change; ids never do (spec §173)."""2from __future__ import annotations34import hashlib5import re67from slugify import slugify as _slugify8from ulid import ULID910PREFIXES: dict[str, str] = {11    "company": "co",12    "domain": "dom",13    "sensor": "sen",14    "observation": "obs",15    "snapshot": "snap",16    "change": "chg",17    "event": "evt",18    "cluster": "cl",19    "job": "job",20    "person": "person",21    "product": "prod",22    "plan": "plan",23    "location": "loc",24    "news": "news",25    "relationship": "rel",26    "queue_job": "qj",27    "llm_job": "llm",28    "review": "rev",29    "failure": "fail",30    "crawl_run": "run",31    "watchlist": "wl",32    "alert": "alert",33    "api_key": "key",34    "signal": "sig",35}363738def new_id(kind: str) -> str:39    prefix = PREFIXES.get(kind)40    if prefix is None:41        raise ValueError(f"unknown id kind {kind!r}")42    return f"{prefix}_{ULID()}"434445def kind_of(entity_id: str) -> str | None:46    prefix = entity_id.split("_", 1)[0]47    for kind, p in PREFIXES.items():48        if p == prefix:49            return kind50    return None515253_slug_clean = re.compile(r"[^a-z0-9.-]+")545556def slugify(text: str, *, max_length: int = 80) -> str:57    """URL slug (`stripe`, `jpmorgan-chase`, `l-oreal`)."""58    s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9-]+", replacements=[("&", " and "), ("/", "-"), ("_", "-"), ("@", "-at-")])59    s = _slug_clean.sub("-", s).strip("-.")60    return s[:max_length].rstrip("-.") or "company"616263def normalize_alias(text: str) -> str:64    """Deterministic alias key: lowercase ASCII, punctuation collapsed, legal suffixes dropped. Resolution only — never display."""65    s = _slugify(text, lowercase=True, regex_pattern=r"[^a-z0-9]+")66    s = s.replace("-", "")67    for suffix in ("incorporated", "corporation", "limited", "company", "holdings", "group", "inc", "corp", "ltd", "plc", "llc",68                   "gmbh", "ag", "sa", "nv", "bv", "se", "ab", "oyj", "asa", "spa", "srl", "kk", "co"):69        if s.endswith(suffix) and len(s) > len(suffix) + 2:70            s = s[: -len(suffix)]71            break72    return s737475def stable_hash(*parts: str, length: int = 24) -> str:76    """Deterministic key for idempotent rows (queue jobs `sensor_id + window`, job fingerprints, cluster keys)."""77    h = hashlib.sha256("\x1f".join(parts).encode("utf-8")).hexdigest()78    return h[:length]798081__all__ = ["PREFIXES", "kind_of", "new_id", "normalize_alias", "slugify", "stable_hash"]82