119 lines
4.7 KiB
Python
119 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def load_split_report_builder():
|
|
script_path = ROOT / "scripts" / "build_background_corpus_split_report.py"
|
|
assert script_path.exists()
|
|
spec = importlib.util.spec_from_file_location("background_split_report_builder", script_path)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def write_summary(path: Path, *, category: str, detections: list[int]) -> None:
|
|
items = [
|
|
{
|
|
"sample_slug": f"{category}_{index}",
|
|
"background_category": category,
|
|
"model_asset_id": "candidate-model",
|
|
"tile_size": 512,
|
|
"tile_overlap": 64,
|
|
"threshold": 0.35,
|
|
"tile_count": 4,
|
|
"detection_count": detection_count,
|
|
"false_positive_pressure": detection_count / 4,
|
|
}
|
|
for index, detection_count in enumerate(detections, start=1)
|
|
]
|
|
path.write_text(
|
|
json.dumps(
|
|
{
|
|
"generated_at": "2026-07-10T00:00:00+00:00",
|
|
"sample_count": len(items),
|
|
"run_count": len(items),
|
|
"background_category_counts": {category: len(items)},
|
|
"best_by_lowest_pressure": min(items, key=lambda item: item["false_positive_pressure"]),
|
|
"items": items,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def test_split_report_builder_creates_strict_gate_and_context_review(tmp_path: Path) -> None:
|
|
module = load_split_report_builder()
|
|
pure_summary = tmp_path / "pure_empty.json"
|
|
sparse_summary = tmp_path / "sparse_context.json"
|
|
output_dir = tmp_path / "split-report"
|
|
write_summary(pure_summary, category="pure_empty_negative", detections=[0, 2])
|
|
write_summary(sparse_summary, category="sparse_building_context", detections=[1, 5])
|
|
|
|
report = module.build_split_report(
|
|
pure_empty_summary_path=pure_summary,
|
|
sparse_context_summary_path=sparse_summary,
|
|
output_dir=output_dir,
|
|
)
|
|
|
|
assert report["strict_default_gate"]["category"] == "pure_empty_negative"
|
|
assert report["strict_default_gate"]["passes_zero_detection_gate"] is False
|
|
assert report["strict_default_gate"]["max_detection_count"] == 2
|
|
assert report["context_review"]["category"] == "sparse_building_context"
|
|
assert report["context_review"]["review_only"] is True
|
|
assert report["context_review"]["max_detection_count"] == 5
|
|
assert report["recommended_next_step"] == "retrain_or_recalibrate_after_review"
|
|
assert (output_dir / "background_corpus_split_summary.json").exists()
|
|
markdown = (output_dir / "background_corpus_split_summary.md").read_text(encoding="utf-8")
|
|
assert "Strict default gate" in markdown
|
|
assert "Sparse-context review" in markdown
|
|
|
|
|
|
def test_split_report_builder_rejects_wrong_summary_category(tmp_path: Path) -> None:
|
|
module = load_split_report_builder()
|
|
wrong_summary = tmp_path / "wrong.json"
|
|
sparse_summary = tmp_path / "sparse.json"
|
|
write_summary(wrong_summary, category="sparse_building_context", detections=[0])
|
|
write_summary(sparse_summary, category="sparse_building_context", detections=[0])
|
|
|
|
try:
|
|
module.build_split_report(
|
|
pure_empty_summary_path=wrong_summary,
|
|
sparse_context_summary_path=sparse_summary,
|
|
output_dir=tmp_path / "out",
|
|
)
|
|
except SystemExit as exc:
|
|
assert "pure_empty_negative" in str(exc)
|
|
else: # pragma: no cover - defensive assertion for the contract.
|
|
raise AssertionError("wrong category summary should fail")
|
|
|
|
|
|
def test_split_matrix_runner_invokes_both_background_categories() -> None:
|
|
runner = ROOT / "scripts" / "run_background_corpus_split_matrix.sh"
|
|
assert runner.exists()
|
|
source = runner.read_text(encoding="utf-8")
|
|
|
|
assert "run_operator_hard_negative_detection_matrix.sh" in source
|
|
assert "OPERATOR_BACKGROUND_CATEGORIES=\"pure_empty_negative\"" in source
|
|
assert "OPERATOR_BACKGROUND_CATEGORIES=\"sparse_building_context\"" in source
|
|
assert "build_background_corpus_split_report.py" in source
|
|
assert "background_corpus_split_summary.json" in source
|
|
assert "/qa/reference" not in source
|
|
assert "fixture_mode" not in source
|
|
|
|
|
|
def test_readiness_covers_split_matrix_runner() -> None:
|
|
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
|
|
|
|
assert "py_compile scripts/build_background_corpus_split_report.py" in readiness
|
|
assert "bash -n scripts/run_background_corpus_split_matrix.sh" in readiness
|