from dataclasses import dataclass from app.modules.design.domain.entities import ( Capability, Entity, Module, TraceabilityLink, ) from app.modules.design.domain.value_objects import FileStatus TEMPLATE_MARKERS = ["TODO", "EXAMPLE", " FileStatus: if not content or not content.strip(): return FileStatus.MISSING lines = [l for l in content.strip().splitlines() if l.strip()] is_csv = file_path.endswith(".csv") # Sparse check if is_csv and len(lines) < 2: return FileStatus.SPARSE if not is_csv and len(lines) < 5: return FileStatus.SPARSE # Count placeholder tokens total_cells = 0 placeholder_cells = 0 for line in lines: cells = line.split(",") if is_csv else [line] for cell in cells: total_cells += 1 if any(m.lower() in cell.lower() for m in TEMPLATE_MARKERS): placeholder_cells += 1 # Placeholder heavy: >50% if total_cells > 0 and placeholder_cells / total_cells > 0.5: return FileStatus.PLACEHOLDER_HEAVY # Template residue: any marker present but <=50% if placeholder_cells > 0: return FileStatus.TEMPLATE_RESIDUE return FileStatus.OK @staticmethod def check_capability_module_linkage( capabilities: list[Capability], traceability_links: list[TraceabilityLink], ) -> list[ConstraintViolation]: linked_caps = {link.capability_id for link in traceability_links} return [ ConstraintViolation( rule="capability_module_linkage", entity_id=cap.capability_id, message=f"Capability {cap.capability_id} has no TraceabilityLink to any module", ) for cap in capabilities if cap.capability_id not in linked_caps ] @staticmethod def check_entity_owner(entities: list[Entity]) -> list[ConstraintViolation]: return [ ConstraintViolation( rule="entity_owner", entity_id=ent.entity_id, message=f"Entity {ent.entity_id} has no owner module", ) for ent in entities if not ent.owner_module ] @staticmethod def check_traceability_references( links: list[TraceabilityLink], capabilities: list[Capability], modules: list[Module], entities: list[Entity], ) -> list[ConstraintViolation]: cap_ids = {c.capability_id for c in capabilities} mod_ids = {m.module_id for m in modules} ent_ids = {e.entity_id for e in entities} violations: list[ConstraintViolation] = [] for link in links: if link.capability_id not in cap_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown capability {link.capability_id}", )) if link.module_id not in mod_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown module {link.module_id}", )) for eid in link.entity_ids: if eid not in ent_ids: violations.append(ConstraintViolation( "traceability_ref", link.trace_id, f"References unknown entity {eid}", )) return violations @classmethod def validate_all( cls, capabilities: list[Capability], modules: list[Module], entities: list[Entity], traceability_links: list[TraceabilityLink], ) -> list[ConstraintViolation]: violations: list[ConstraintViolation] = [] violations.extend(cls.check_capability_module_linkage(capabilities, traceability_links)) violations.extend(cls.check_entity_owner(entities)) violations.extend(cls.check_traceability_references( traceability_links, capabilities, modules, entities, )) return violations