"""Core models for human-centered ecosystem analysis. The module intentionally keeps the data model dependency-free. The platform is expected to run inside operational mirrors where installing new packages is not always desirable, so every object can be serialized with the standard library. """ from __future__ import annotations from dataclasses import dataclass, field, fields, is_dataclass from datetime import datetime, timezone from enum import Enum from pathlib import Path from typing import Any, Iterable, Mapping, MutableMapping, Sequence def utc_now() -> str: """Return an ISO-8601 timestamp without relying on local locale settings.""" return datetime.now(timezone.utc).replace(microsecond=0).isoformat() def slugify(value: str) -> str: """Create a stable ASCII-ish slug for filenames and object ids.""" allowed: list[str] = [] last_dash = False for char in value.lower().strip(): if "a" <= char <= "z" or "0" <= char <= "9": allowed.append(char) last_dash = False elif char in {" ", "_", "-", ".", "/", "\\"} and not last_dash: allowed.append("-") last_dash = True return "".join(allowed).strip("-") or "item" def as_plain_data(value: Any) -> Any: """Convert dataclasses, enums, paths, and nested values to JSON-safe data.""" if isinstance(value, Enum): return value.value if isinstance(value, Path): return str(value) if is_dataclass(value): return {item.name: as_plain_data(getattr(value, item.name)) for item in fields(value)} if isinstance(value, Mapping): return {str(key): as_plain_data(inner) for key, inner in value.items()} if isinstance(value, (list, tuple, set)): return [as_plain_data(item) for item in value] return value class EvidenceKind(str, Enum): """Kinds of evidence detected in a platform repository.""" README = "readme" PACKAGE_SCRIPT = "package_script" ROUTE = "route" OPENAPI = "openapi" TEST = "test" CONFIG = "config" DOC = "doc" WORKER = "worker" STORAGE = "storage" MCP_TOOL = "mcp_tool" UI_SURFACE = "ui_surface" SECURITY = "security" BUSINESS_RULE = "business_rule" OBSERVABILITY = "observability" UNKNOWN = "unknown" class NeedCategory(str, Enum): """Human need categories used by the matrix engine.""" ADMINISTRATION = "administration" SUPPORT = "support" FINANCE = "finance" LEGAL = "legal" SECURITY = "security" OPERATIONS = "operations" STRATEGY = "strategy" DOCUMENTATION = "documentation" SELF_SERVICE = "self_service" COMMERCIAL = "commercial" EXPERIENCE = "experience" GOVERNANCE = "governance" INTEGRATION = "integration" OBSERVABILITY = "observability" class MaturityLevel(str, Enum): """Operational maturity level from a human point of view.""" NOT_FOUND = "not_found" PLANNED = "planned" CATALOGED = "cataloged" TECHNICAL = "technical" EXPLAINABLE = "explainable" ACTIONABLE = "actionable" READY_FOR_HUMAN = "ready_for_human" AUDITABLE = "auditable" class OrderType(str, Enum): """Service-order type used by the order generator.""" EXECUTIVE = "executiva" MANAGERIAL = "gerencial" class OrderStatus(str, Enum): """Compact status for generated and executed orders.""" PLANNED = "planejada" RUNNING = "em_execucao" COMPLETED = "concluida" PARTIAL = "parcial" BLOCKED = "bloqueada" @dataclass(slots=True) class Evidence: """A small, inspectable proof that a capability exists or is missing.""" kind: EvidenceKind path: str summary: str line: int | None = None confidence: float = 0.5 tags: tuple[str, ...] = () def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @property def reference(self) -> str: if self.line is None: return self.path return f"{self.path}:{self.line}" def is_strong(self) -> bool: return self.confidence >= 0.75 @dataclass(slots=True) class HumanNeed: """A concrete need a person may have while using the ecosystem.""" need_id: str title: str category: NeedCategory description: str success_markers: tuple[str, ...] risk_if_missing: str expected_surfaces: tuple[str, ...] = () def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class HumanProfile: """A human role that should be served by one or more platforms.""" profile_id: str name: str description: str priority_needs: tuple[NeedCategory, ...] typical_questions: tuple[str, ...] expected_outputs: tuple[str, ...] sensitive_concerns: tuple[str, ...] = () def to_dict(self) -> dict[str, Any]: return as_plain_data(self) def wants(self, category: NeedCategory) -> bool: return category in self.priority_needs @dataclass(slots=True) class PlatformDefinition: """Canonical description of a managed platform.""" platform_id: str repo_name: str central_folder: str title: str mission: str primary_categories: tuple[NeedCategory, ...] expected_profiles: tuple[str, ...] related_platforms: tuple[str, ...] = () expected_surfaces: tuple[str, ...] = () known_blockers: tuple[str, ...] = () def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class FileMetric: """Line and extension metric for a repository file.""" path: str extension: str lines: int bytes_size: int def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class ScriptCommand: """A command exposed by package.json or other local metadata.""" name: str command: str source_file: str intent: str = "unknown" def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class PlatformScan: """Repository scan result used as input for matrix and report generation.""" platform: PlatformDefinition repo_path: str exists: bool git_present: bool branch: str | None head: str | None remote_origin: str | None readme_excerpt: str file_metrics: tuple[FileMetric, ...] scripts: tuple[ScriptCommand, ...] evidence: tuple[Evidence, ...] warnings: tuple[str, ...] scanned_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @property def total_lines(self) -> int: return sum(metric.lines for metric in self.file_metrics) @property def code_lines(self) -> int: code_ext = {".ts", ".tsx", ".js", ".mjs", ".cjs", ".py", ".java"} return sum(metric.lines for metric in self.file_metrics if metric.extension in code_ext) @property def has_tests(self) -> bool: return any(item.kind == EvidenceKind.TEST for item in self.evidence) @property def has_openapi(self) -> bool: return any(item.kind == EvidenceKind.OPENAPI for item in self.evidence) @property def has_worker(self) -> bool: return any(item.kind == EvidenceKind.WORKER for item in self.evidence) @property def strong_evidence_count(self) -> int: return sum(1 for item in self.evidence if item.is_strong()) def evidence_by_kind(self, kind: EvidenceKind) -> tuple[Evidence, ...]: return tuple(item for item in self.evidence if item.kind == kind) @dataclass(slots=True) class MatrixCell: """Human service score for one platform and one profile.""" platform_id: str profile_id: str score: int maturity: MaturityLevel explanation: str strengths: tuple[str, ...] gaps: tuple[str, ...] evidence_refs: tuple[str, ...] def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @property def normalized_score(self) -> float: return max(0.0, min(1.0, self.score / 100.0)) def is_ready(self) -> bool: return self.score >= 75 @dataclass(slots=True) class Recommendation: """Actionable recommendation derived from scan and matrix signals.""" recommendation_id: str platform_id: str title: str reason: str expected_impact: str categories: tuple[NeedCategory, ...] priority: int suggested_order_type: OrderType affected_paths: tuple[str, ...] = () validation_steps: tuple[str, ...] = () def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class PlatformHumanReport: """Report model for one platform.""" platform: PlatformDefinition scan: PlatformScan cells: tuple[MatrixCell, ...] recommendations: tuple[Recommendation, ...] summary: str current_state: tuple[str, ...] future_state: tuple[str, ...] missing_for_humans: tuple[str, ...] generated_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @property def average_score(self) -> int: if not self.cells: return 0 return round(sum(cell.score for cell in self.cells) / len(self.cells)) @property def ready_profiles(self) -> tuple[str, ...]: return tuple(cell.profile_id for cell in self.cells if cell.is_ready()) @dataclass(slots=True) class EcosystemHumanReport: """Full ecosystem report model.""" scans: tuple[PlatformScan, ...] platform_reports: tuple[PlatformHumanReport, ...] recommendations: tuple[Recommendation, ...] generated_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @property def total_code_lines(self) -> int: return sum(scan.code_lines for scan in self.scans) @property def total_files(self) -> int: return sum(len(scan.file_metrics) for scan in self.scans) @property def average_score(self) -> int: cells = [cell for report in self.platform_reports for cell in report.cells] if not cells: return 0 return round(sum(cell.score for cell in cells) / len(cells)) def report_for(self, platform_id: str) -> PlatformHumanReport | None: for report in self.platform_reports: if report.platform.platform_id == platform_id: return report return None @dataclass(slots=True) class ServiceOrder: """Generated service order metadata and body.""" order_id: str order_type: OrderType project_id: str title: str purpose: str object_scope: str reason: str expected_result: str affected_paths: tuple[str, ...] validations: tuple[str, ...] ready_criteria: tuple[str, ...] status: OrderStatus = OrderStatus.PLANNED priority: str = "media" def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class GeneratedFile: """File generated or updated by the platform runtime.""" path: str description: str function: str file_type: str changed_by: str change_summary: str relation_to_order: str def to_dict(self) -> dict[str, Any]: return as_plain_data(self) @dataclass(slots=True) class ReportBundle: """Paths and counters produced by a generation run.""" output_root: str generated_files: tuple[GeneratedFile, ...] platform_count: int profile_count: int matrix_cells: int total_code_lines_analyzed: int warnings: tuple[str, ...] generated_at: str = field(default_factory=utc_now) def to_dict(self) -> dict[str, Any]: return as_plain_data(self) def clamp_score(value: int | float) -> int: """Clamp a numeric score into the 0..100 range.""" return int(max(0, min(100, round(float(value))))) def maturity_from_score(score: int) -> MaturityLevel: """Map numeric score to a human maturity label.""" score = clamp_score(score) if score == 0: return MaturityLevel.NOT_FOUND if score < 20: return MaturityLevel.PLANNED if score < 35: return MaturityLevel.CATALOGED if score < 50: return MaturityLevel.TECHNICAL if score < 65: return MaturityLevel.EXPLAINABLE if score < 80: return MaturityLevel.ACTIONABLE if score < 92: return MaturityLevel.READY_FOR_HUMAN return MaturityLevel.AUDITABLE def merge_unique(values: Iterable[str]) -> tuple[str, ...]: """Return values in input order while dropping empty strings and duplicates.""" seen: set[str] = set() output: list[str] = [] for value in values: cleaned = str(value).strip() if not cleaned or cleaned in seen: continue seen.add(cleaned) output.append(cleaned) return tuple(output) def group_by_platform(recommendations: Sequence[Recommendation]) -> dict[str, list[Recommendation]]: grouped: dict[str, list[Recommendation]] = {} for item in recommendations: grouped.setdefault(item.platform_id, []).append(item) for items in grouped.values(): items.sort(key=lambda rec: (-rec.priority, rec.title)) return grouped def incrementing_id(prefix: str, index: int, title: str) -> str: """Create a readable id with a numeric prefix and a slug.""" return f"{index:04d}_{prefix}__{slugify(title)}" def summarize_warnings(scans: Sequence[PlatformScan]) -> tuple[str, ...]: warnings: list[str] = [] for scan in scans: for warning in scan.warnings: warnings.append(f"{scan.platform.platform_id}: {warning}") return merge_unique(warnings) def score_label(score: int) -> str: """Return a compact label for user-facing matrix tables.""" score = clamp_score(score) if score >= 90: return "excelente" if score >= 75: return "forte" if score >= 60: return "util" if score >= 40: return "tecnico" if score >= 20: return "inicial" if score > 0: return "fragil" return "ausente" def ensure_mapping(data: MutableMapping[str, Any], key: str, default: Any) -> Any: """Small helper used by storage migration code.""" if key not in data: data[key] = default return data[key]