nexu-io/open-design · error · ValueError

representative_ids must be a subset of candidate_ids

Error message

representative_ids must be a subset of candidate_ids

What it means

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.

Source

Thrown at design-templates/last30days/scripts/lib/schema.py:139

    cluster_id: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class Cluster:
    """Ranked cluster of related candidates."""

    cluster_id: str
    title: str
    candidate_ids: list[str]
    representative_ids: list[str]
    sources: list[str]
    score: float
    uncertainty: Literal["single-source", "thin-evidence"] | None = None

    def __post_init__(self) -> None:
        if not set(self.representative_ids) <= set(self.candidate_ids):
            raise ValueError("representative_ids must be a subset of candidate_ids")


@dataclass
class Report:
    """Final pipeline output."""

    topic: str
    range_from: str
    range_to: str
    generated_at: str
    provider_runtime: ProviderRuntime
    query_plan: QueryPlan
    clusters: list[Cluster]
    ranked_candidates: list[Candidate]
    items_by_source: dict[str, list[SourceItem]]
    errors_by_source: dict[str, str]
    warnings: list[str] = field(default_factory=list)
    artifacts: dict[str, Any] = field(default_factory=dict)

View on GitHub (pinned to 5be4028344)

Solutions

  1. Build representative_ids only from ids present in candidate_ids (intersect first).
  2. If candidate_ids is filtered post-hoc, recompute representatives as candidate_ids ∩ old_representatives.
  3. In cluster merge, rebuild representatives from the merged candidate pool.

Example fix

# before
Cluster(cluster_id='c', title='t', candidate_ids=['a','b'], representative_ids=['a','z'], sources=['x'], score=0.5)

# after
cands = ['a', 'b']
reps = [r for r in ['a', 'z'] if r in cands] or cands[:1]
Cluster(cluster_id='c', title='t', candidate_ids=cands, representative_ids=reps, sources=['x'], score=0.5)
Defensive patterns

Strategy: validation

Validate before calling

def build_cluster(cluster_id, title, candidate_ids, representative_ids, sources, score):
    cand_set = set(candidate_ids)
    reps = [r for r in representative_ids if r in cand_set] or candidate_ids[:1]
    return Cluster(cluster_id, title, candidate_ids, reps, sources, score)

Type guard

def representatives_are_subset(candidate_ids, representative_ids) -> bool:
    return set(representative_ids) <= set(candidate_ids)

Try / catch

null

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/df55985f8bd64273. Report an issue: GitHub.