"""Data models for the migration pipeline.""" from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Optional # --------------------------------------------------------------------------- # Enumerations # --------------------------------------------------------------------------- class Track(str, Enum): """Migration track.""" A = "track_a_conservative" # Qt5→Qt6 compatibility only B = "track_b_modern" # Qt6 + C++20 modernisation class FileStatus(str, Enum): PENDING = "pending" IN_PROGRESS = "in_progress" SUCCESS = "success" QUARANTINED = "quarantined" class FailureClass(str, Enum): COMPILE_ERROR = "compile_error" LINK_ERROR = "link_error" TEST_FAILURE = "test_failure" TEST_BUILD_FAILURE = "test_build_failure" BUDGET_EXCEEDED = "budget_exceeded" API_ERROR = "api_error" CHUNK_MERGE_ERROR = "chunk_merge_error" UNKNOWN = "unknown" # --------------------------------------------------------------------------- # Chunk # --------------------------------------------------------------------------- @dataclass class CodeChunk: """A contiguous slice of a C++ source file.""" index: int start_line: int # 0-based, inclusive end_line: int # 0-based, exclusive content: str boundary_type: str # "complete" | "semantic" | "fallback" @property def line_count(self) -> int: return self.end_line - self.start_line # --------------------------------------------------------------------------- # Quality gates # --------------------------------------------------------------------------- @dataclass class QualityGateResult: """Result of all quality gates for one migration attempt.""" build_ok: bool = False build_errors: str = "" build_warnings: list[str] = field(default_factory=list) existing_tests_ok: bool = False new_tests_ok: bool = False test_output: str = "" test_warnings: list[str] = field(default_factory=list) @property def all_gates_passed(self) -> bool: """Hard gates: build green, all tests green. Warnings are advisory and do NOT block (config.WARNINGS_ARE_BLOCKING=False).""" from migration_pipeline.config import WARNINGS_ARE_BLOCKING if WARNINGS_ARE_BLOCKING: return ( self.build_ok and self.existing_tests_ok and self.new_tests_ok and not self.build_warnings and not self.test_warnings ) return self.build_ok and self.existing_tests_ok and self.new_tests_ok @property def total_warnings(self) -> int: return len(self.build_warnings) + len(self.test_warnings) # --------------------------------------------------------------------------- # Quarantine # --------------------------------------------------------------------------- @dataclass class QuarantineEntry: """Rich metadata for a file that could not be migrated automatically.""" file_path: str track: str failure_class: str last_error: str # truncated at 2000 chars affected_apis: list[str] chunk_info: str manual_recommendation: str timestamp: str # ISO-8601 UTC retry_count: int elapsed_seconds: float # --------------------------------------------------------------------------- # Per-file result # --------------------------------------------------------------------------- @dataclass class FileResult: file_path: str track: str status: str # FileStatus value iterations: int elapsed_seconds: float gate: QualityGateResult quarantine_entry: Optional[QuarantineEntry] = None migrated_path: str = "" test_name: str = "" # --------------------------------------------------------------------------- # Batch result # --------------------------------------------------------------------------- @dataclass class BatchResult: """Aggregated result for one batch + one track.""" batch_id: str track: str branch: str total_files: int success_count: int = 0 quarantine_count: int = 0 total_warnings: int = 0 mean_elapsed_seconds: float = 0.0 results: list[FileResult] = field(default_factory=list) @property def success_rate(self) -> float: return self.success_count / self.total_files if self.total_files else 0.0 @property def quarantine_rate(self) -> float: return self.quarantine_count / self.total_files if self.total_files else 0.0