Source code for flameiq.core.models

"""FlameIQ core domain models.

These dataclasses represent the *results* of FlameIQ operations.
They are distinct from schema models (:mod:`flameiq.schema.v1.models`),
which represent *input data*.

No external dependencies.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum


[docs] class RegressionStatus(str, Enum): """The overall outcome of a baseline-vs-current comparison.""" PASS = "pass" """All metrics are within their configured thresholds.""" REGRESSION = "regression" """One or more metrics exceeded their threshold.""" WARNING = "warning" """No threshold breached, but metrics are approaching limits.""" INSUFFICIENT_DATA = "insufficient_data" """Statistical mode requested but sample count too low."""
[docs] @dataclass(frozen=True) class MetricDiff: """The computed difference for a single metric key.""" metric_key: str """Dotted key, e.g. ``"latency.p95"``.""" baseline_value: float """The reference measurement.""" current_value: float """The current measurement.""" change_percent: float """Signed % change. Positive = current is larger.""" threshold_percent: float """Configured allowance for this metric.""" is_regression: bool """Whether the threshold was exceeded.""" is_warning: bool = False """Within threshold but approaching the limit.""" p_value: float | None = None """Statistical p-value (if statistical mode used).""" effect_size: float | None = None """Cohen's d (if statistical mode used).""" @property def direction(self) -> str: """Human-readable direction: ``'increased'``, ``'decreased'``, or ``'unchanged'``.""" if self.change_percent > 0: return "increased" if self.change_percent < 0: return "decreased" return "unchanged" @property def abs_change_percent(self) -> float: """Absolute magnitude of the change.""" return abs(self.change_percent) @property def status_label(self) -> str: """Short status string suitable for display.""" if self.is_regression: return "REGRESSION" if self.is_warning: return "WARNING" return "PASS"
[docs] @dataclass(frozen=True) class ComparisonResult: """The complete result of a baseline-vs-current comparison.""" status: RegressionStatus """Overall pass/regression/warning outcome.""" diffs: list[MetricDiff] = field(default_factory=list) """Per-metric differences, in metric-key order.""" baseline_commit: str | None = None """Git SHA of the baseline snapshot, if available.""" current_commit: str | None = None """Git SHA of the current snapshot, if available.""" statistical_mode: bool = False """Whether the Mann-Whitney U test was applied.""" summary: str | None = None """Optional human-readable summary string.""" @property def regressions(self) -> list[MetricDiff]: """Metrics that breached their threshold.""" return [d for d in self.diffs if d.is_regression] @property def warnings(self) -> list[MetricDiff]: """Metrics within threshold but approaching the limit.""" return [d for d in self.diffs if d.is_warning and not d.is_regression] @property def passed(self) -> list[MetricDiff]: """Metrics that passed cleanly, with no warning.""" return [d for d in self.diffs if not d.is_regression and not d.is_warning] @property def exit_code(self) -> int: """Standard CI exit code. Returns: ``0`` for PASS / WARNING, ``1`` for REGRESSION. """ return 1 if self.status == RegressionStatus.REGRESSION else 0
[docs] def to_dict(self) -> dict[str, object]: """Serialise to a plain dict (e.g. for ``--json`` CLI output).""" return { "status": self.status.value, "exit_code": self.exit_code, "baseline_commit": self.baseline_commit, "current_commit": self.current_commit, "statistical_mode": self.statistical_mode, "summary": self.summary, "counts": { "regressions": len(self.regressions), "warnings": len(self.warnings), "passed": len(self.passed), "total": len(self.diffs), }, "diffs": [ { "metric_key": d.metric_key, "baseline_value": d.baseline_value, "current_value": d.current_value, "change_percent": d.change_percent, "threshold_percent": d.threshold_percent, "is_regression": d.is_regression, "is_warning": d.is_warning, "status": d.status_label, "p_value": d.p_value, "effect_size": d.effect_size, } for d in self.diffs ], }