{"record":{"id":"df55985f8bd64273","repo":"nexu-io/open-design","slug":"representative-ids-must-be-a-subset-of-candidate-i","errorCode":null,"errorMessage":"representative_ids must be a subset of candidate_ids","messagePattern":"representative_ids must be a subset of candidate_ids","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"design-templates/last30days/scripts/lib/schema.py","lineNumber":139,"sourceCode":"    cluster_id: str | None = None\n    metadata: dict[str, Any] = field(default_factory=dict)\n\n\n@dataclass\nclass Cluster:\n    \"\"\"Ranked cluster of related candidates.\"\"\"\n\n    cluster_id: str\n    title: str\n    candidate_ids: list[str]\n    representative_ids: list[str]\n    sources: list[str]\n    score: float\n    uncertainty: Literal[\"single-source\", \"thin-evidence\"] | None = None\n\n    def __post_init__(self) -> None:\n        if not set(self.representative_ids) <= set(self.candidate_ids):\n            raise ValueError(\"representative_ids must be a subset of candidate_ids\")\n\n\n@dataclass\nclass Report:\n    \"\"\"Final pipeline output.\"\"\"\n\n    topic: str\n    range_from: str\n    range_to: str\n    generated_at: str\n    provider_runtime: ProviderRuntime\n    query_plan: QueryPlan\n    clusters: list[Cluster]\n    ranked_candidates: list[Candidate]\n    items_by_source: dict[str, list[SourceItem]]\n    errors_by_source: dict[str, str]\n    warnings: list[str] = field(default_factory=list)\n    artifacts: dict[str, Any] = field(default_factory=dict)","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/design-templates/last30days/scripts/lib/schema.py#L121-L157","documentation":"Cluster.__post_init__ enforces that representative_ids is a subset of candidate_ids. A cluster's 'representative' candidates must be drawn from its own candidate pool; an out-of-set representative would reference a candidate that is not part of the cluster, corrupting downstream rendering and evidence linking.","triggerScenarios":"Constructing Cluster with representative_ids containing an id absent from candidate_ids. Reached when clustering code copies representative ids from a different cluster, or when candidate_ids was filtered after representatives were chosen.","commonSituations":"Clustering refactor that picks representatives before finalizing the candidate list. Deduplication step that removed a candidate but left its id in representatives. Cross-cluster merge that did not recompute representatives.","solutions":["Build representative_ids only from ids present in candidate_ids (intersect first).","If candidate_ids is filtered post-hoc, recompute representatives as candidate_ids ∩ old_representatives.","In cluster merge, rebuild representatives from the merged candidate pool."],"exampleFix":"# before\nCluster(cluster_id='c', title='t', candidate_ids=['a','b'], representative_ids=['a','z'], sources=['x'], score=0.5)\n\n# after\ncands = ['a', 'b']\nreps = [r for r in ['a', 'z'] if r in cands] or cands[:1]\nCluster(cluster_id='c', title='t', candidate_ids=cands, representative_ids=reps, sources=['x'], score=0.5)","handlingStrategy":"validation","validationCode":"def build_cluster(cluster_id, title, candidate_ids, representative_ids, sources, score):\n    cand_set = set(candidate_ids)\n    reps = [r for r in representative_ids if r in cand_set] or candidate_ids[:1]\n    return Cluster(cluster_id, title, candidate_ids, reps, sources, score)","typeGuard":"def representatives_are_subset(candidate_ids, representative_ids) -> bool:\n    return set(representative_ids) <= set(candidate_ids)","tryCatchPattern":"null","preventionTips":["Always derive representative_ids from candidate_ids (intersect), never from an external list.","When filtering candidate_ids, recompute representatives as old_reps ∩ new_candidates.","Add a unit test that asserts representatives_are_subset for every cluster built by the clustering stage."],"tags":["validation","dataclass","clustering","invariant"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}