"""Source-independent API projection for citation-graph judgment nodes.""" from __future__ import annotations import re from typing import Any, Mapping THEMIS_DECIMAL_ID = re.compile(r"^[1-9][0-9]{12}$") def graph_node_card( node_id: object, metadata: Mapping[str, Any] | None = None, *, treatment: str | None = None, cited_by: int = 0, good_law_status: str = "unknown", ) -> dict[str, Any]: """Return the graph/UI contract for one judgment node. The permanent Moonley ID remains the routing key. All presentation fields use the human legal identity so internal IDs never need to be rendered. """ judgment_id = str(node_id) meta = dict(metadata or {}) case_name = " ".join(str(meta.get("case_name") or "").split()) citation = " ".join(str(meta.get("neutral_citation") or "").split()) date = meta.get("date") or meta.get("decision_date") display_name = case_name or citation or "Case name unavailable" tooltip_parts = [display_name] if citation and citation != display_name: tooltip_parts.append(citation) if date: tooltip_parts.append(str(date)) tooltip_parts.append(str(good_law_status or "unknown").replace("_", " ")) return { # Canonical graph contract. "node_id": judgment_id, "judgment_id": judgment_id, "label": display_name, "display_name": display_name, "tooltip": " ยท ".join(tooltip_parts), "hover": { "case_name": case_name or None, "neutral_citation": citation or None, "decision_date": date, "good_law_status": good_law_status or "unknown", }, "open": { "judgment_id": judgment_id, "pdf_id": judgment_id, }, "is_themis_decimal_id": bool(THEMIS_DECIMAL_ID.fullmatch(judgment_id)), # Compatibility with the current phase 1.1 frontend. "id": judgment_id, "name": display_name, "citation": citation or None, "treatment": treatment, "cited_by": int(cited_by or 0), "good_law": good_law_status or "unknown", }