from datetime import datetime from pydantic import BaseModel, Field from .models import CodeLanguage, NodeType, Priority, Status, TestStatus # ─── Requirement ───────────────────────────────────────────────────────── class RequirementCreate(BaseModel): id: str = Field(..., max_length=64) title: str = Field(..., max_length=256) description: str | None = None priority: Priority = Priority.medium phase: int | None = None status: Status = Status.draft node_type: NodeType = NodeType.requirement parent_id: str | None = None sort_order: int = 0 project_id: int | None = None gitea_issue_number: int | None = None gitea_repo: str | None = None acceptance_criteria: str | None = None test_spec: str | None = None test_code: str | None = None impl_code: str | None = None code_language: CodeLanguage | None = None target_test_file: str | None = None target_impl_file: str | None = None class RequirementUpdate(BaseModel): title: str | None = None description: str | None = None priority: Priority | None = None phase: int | None = None status: Status | None = None node_type: NodeType | None = None parent_id: str | None = None sort_order: int | None = None gitea_issue_number: int | None = None gitea_repo: str | None = None acceptance_criteria: str | None = None test_spec: str | None = None test_code: str | None = None impl_code: str | None = None code_language: CodeLanguage | None = None target_test_file: str | None = None target_impl_file: str | None = None measurable: bool | None = None consistent: bool | None = None single_purpose: bool | None = None class QualityReport(BaseModel): traceable: bool measurable: bool consistent: bool single_purpose: bool errors: list[str] warnings: list[str] valid: bool class RequirementResponse(BaseModel): id: str title: str description: str | None priority: Priority phase: int | None status: Status node_type: NodeType parent_id: str | None sort_order: int project_id: int | None gitea_issue_number: int | None gitea_repo: str | None traceable: bool measurable: bool consistent: bool single_purpose: bool acceptance_criteria: str | None test_spec: str | None test_code: str | None impl_code: str | None code_language: CodeLanguage | None target_test_file: str | None target_impl_file: str | None test_status: TestStatus created_at: datetime updated_at: datetime quality: QualityReport | None = None children: list["RequirementResponse"] = [] model_config = {"from_attributes": True} class RequirementTreeNode(BaseModel): id: str title: str priority: Priority phase: int | None status: Status node_type: NodeType parent_id: str | None child_count: int = 0 traceable: bool measurable: bool consistent: bool single_purpose: bool gitea_issue_number: int | None = None children: list["RequirementTreeNode"] = [] model_config = {"from_attributes": True} # ─── Requirement History ──────────────────────────────────────────────── class RequirementHistorySummary(BaseModel): id: int requirement_id: str version: int title: str status: str created_at: datetime change_summary: str | None model_config = {"from_attributes": True} class RequirementHistoryDetail(RequirementHistorySummary): description: str | None priority: str phase: int | None node_type: str acceptance_criteria: str | None test_spec: str | None test_code: str | None impl_code: str | None code_language: str | None target_test_file: str | None target_impl_file: str | None measurable: bool consistent: bool single_purpose: bool gitea_issue_number: int | None gitea_repo: str | None # ─── Graph Layouts ────────────────────────────────────────────────────── class GraphLayoutCreate(BaseModel): name: str = Field(..., max_length=128) description: str | None = None project_id: int | None = None node_positions: dict[str, dict[str, float]] viewport: dict[str, object] is_default: bool = False class GraphLayoutUpdate(BaseModel): name: str | None = None description: str | None = None node_positions: dict[str, dict[str, float]] | None = None viewport: dict[str, object] | None = None is_default: bool | None = None class GraphLayoutResponse(BaseModel): id: int name: str description: str | None project_id: int | None is_default: bool created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class GraphLayoutDetailResponse(GraphLayoutResponse): node_positions: dict[str, dict[str, float]] viewport: dict[str, object] # ─── Cross-Pillar Links ───────────────────────────────────────────────── class LinkCreate(BaseModel): source_id: str target_id: str relationship_type: str = "shared_parent" class LinkResponse(BaseModel): source_id: str target_id: str relationship_type: str model_config = {"from_attributes": True} # ─── Project ──────────────────────────────────────────────────────────── class ProjectCreate(BaseModel): name: str = Field(..., max_length=256) description: str | None = None repo_path: str | None = None repo_url: str | None = None requirements_yaml_path: str | None = None class ProjectResponse(BaseModel): id: int name: str description: str | None repo_path: str | None repo_url: str | None requirements_yaml_path: str | None created_at: datetime model_config = {"from_attributes": True} # ─── Stats ─────────────────────────────────────────────────────────────── class StatsResponse(BaseModel): total_nodes: int pillars: int requirements: int sub_requirements: int leaves: int by_status: dict[str, int] by_priority: dict[str, int] quality_passing: int quality_failing: int coverage_percent: float