Rewrite normalize, cross-IC dedupe, and billing seam (2.49.0).

Downgrade-only clamp and fail-soft coverage stay in src. Inherited copies remain on disk. Self-host billing is NullBilling.
This commit is contained in:
2026-09-20 18:47:45 +02:00
parent fe89851c10
commit 572b8d24f3
6 changed files with 954 additions and 1 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Post-review services load from src; Emmaforo clamp contracts stay downgrade-only."""
from __future__ import annotations
from pathlib import Path
from backend.periscopex.models import Finding
from backend.services.billing_hook import NullBilling, get_billing
from backend.services.dedupe_findings import _build_deduped
from backend.services.normalize_findings import _build_normalized
import backend.services.billing_hook as billing_hook
import backend.services.dedupe_findings as dedupe_findings
import backend.services.normalize_findings as normalize_findings
def test_postpass_modules_are_src():
for mod, name in (
(normalize_findings, "normalize_findings.py"),
(dedupe_findings, "dedupe_findings.py"),
(billing_hook, "billing_hook.py"),
):
path = Path(mod.__file__).resolve()
assert path.name == name
assert "src" in path.parts
assert "Native Periscope overlay" not in path.read_text(encoding="utf-8")[:500]
def test_self_host_billing_is_null():
hook = get_billing()
assert isinstance(hook, NullBilling)
assert hook.credits_for_api_cost(12.0) == 0.0
def test_emmaforo_uart_normalize_cannot_promote_warning():
"""U2-001 class: hedged UART abs-max must stay WARNING."""
originals = [
Finding(
designator="U2",
mpn="CH340E",
finding="5V UART into MCU",
why="Unverified: abs-max for PA14 not confirmed",
status="WARNING",
recommendation="level shift",
source_page=11,
source_quote="",
reference="",
)
]
raw = [{
"merged_from": [1],
"finding": "CH340E 5V will damage the MCU",
"why": "will damage the MCU",
"status": "ERROR",
"recommendation": "level shift",
"change_rationale": "graded ERROR",
}]
built = _build_normalized(raw, [], originals)
assert built is not None
kept, _ = built
assert kept[0].status == "WARNING"
assert kept[0].why.lower().startswith("unverified:")
def test_emmaforo_uart_dedupe_collapses_both_endpoints():
originals = [
Finding(
designator="U2", mpn="CH340E", finding="5V out on UART",
why="w", status="ERROR", recommendation="", source_page=11,
source_quote="q2", reference="r2",
),
Finding(
designator="U3", mpn="ESP32", finding="PA14 overvoltage",
why="w", status="ERROR", recommendation="", source_page=25,
source_quote="q3", reference="r3",
),
]
groups = [{
"member_indices": [1, 2],
"primary_index": 2,
"finding": "CH340E 5V into MCU UART pin",
"why": "same UART interface",
"status": "ERROR",
"recommendation": "level shift",
"change_rationale": "merged 1+2",
}]
built = _build_deduped(groups, originals)
assert built is not None
assert len(built) == 1
assert built[0].designator == "U3"
assert built[0].source_page == 25