from __future__ import annotations from app.schemas.visual_lesson import GraphAlgorithmSpec from app.services.graph_algorithm_compiler import ALGORITHMS, GraphAlgorithmCompiler class GraphAlgorithmValidationError(ValueError): pass class GraphAlgorithmValidator: def __init__(self, compiler: GraphAlgorithmCompiler) -> None: self.compiler = compiler def validate(self, spec: GraphAlgorithmSpec) -> None: node_ids = {node.node_id for node in spec.nodes} if not 2 <= len(node_ids) <= 10 or len(node_ids) != len(spec.nodes): raise GraphAlgorithmValidationError("A graph must contain 2–10 uniquely named nodes") if spec.initial_start_node not in node_ids or spec.initial_target_node not in node_ids: raise GraphAlgorithmValidationError("Initial graph endpoints are invalid") if spec.primary_algorithm not in ALGORITHMS: raise GraphAlgorithmValidationError("The selected graph algorithm is unsupported") edge_ids: set[str] = set() for edge in spec.edges: if edge.edge_id in edge_ids or edge.source not in node_ids or edge.target not in node_ids or edge.weight <= 0: raise GraphAlgorithmValidationError("The graph contains an invalid edge") edge_ids.add(edge.edge_id) claim_ids = {claim.claim_id for claim in spec.evidence_claims} if not {"graph-bfs", "graph-dfs", "graph-dijkstra", "graph-astar"} <= claim_ids: raise GraphAlgorithmValidationError("The graph visualization is missing algorithm definitions") compiled = self.compiler.compile_spec(spec) expected = len(ALGORITHMS) * len(node_ids) * len(node_ids) if not compiled.assertions_passed or len(compiled.branches) != expected: raise GraphAlgorithmValidationError("The graph compiler did not produce every endpoint branch")