compass-kb-validation 2.8.0__tar.gz → 2.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/PKG-INFO +1 -1
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/PKG-INFO +1 -1
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/SOURCES.txt +1 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/discover_and_validate.py +25 -1
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/__init__.py +1 -1
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/cli.py +41 -22
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/__init__.py +19 -12
- compass_kb_validation-2.9.0/ingestion_validation/report/audit.py +186 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_citation.py +11 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_evidence.py +11 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_reporting.py +38 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/LICENSE +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/README.md +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/dependency_links.txt +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/entry_points.txt +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/requires.txt +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/compass_kb_validation.egg-info/top_level.txt +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/__main__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/ask.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/baseline.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/chunk_coherence.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/citation.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/config/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/config_lint.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/content_inspect.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/corpus.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/corpus_health.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/diagnostics.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/embedding_drift.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/eval_interop.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/evidence.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/grounding.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/init_wizard.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/live_dashboard.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/mcp_server.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/models/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/notifications.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/pipeline/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/profiler.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/provenance.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/embedding.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/graph.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/source.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/storage.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/vectorstore.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/remediation.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/_report_css.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/_report_js.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/analysis.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/export.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/shell.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/retrieval_analysis.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/text_probes.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/trend_tracker.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/utils/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/__init__.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/api_upload.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/base.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/base_sparql.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/cross_stage.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/graphdb_metadata.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/neptune_metadata.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/redis_chunks.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/registry.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/retrieval_quality.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/s3_storage.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/profile_kb.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/pyproject.toml +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/run_validation.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/setup.cfg +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_alt_adapters.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_analysis_report.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_api_upload.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_ask.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_baseline.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_benchmark.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_check_catalog.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_cli.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_config.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_config_lint.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_content_inspect.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_corpus.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_corpus_health.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_coverage.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_cross_stage.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_diagnostics.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_discover_cli.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_discovery_parallel.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_embedding_drift.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_eval_interop.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_gate.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_graph_transport.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_graph_validators.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_grounding.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_init_wizard.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_manifest.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_mcp.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_migration.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_models.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_neptune_bugfixes.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_neptune_bugfixes2.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_notifications.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_pipeline.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_profiler.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_providers.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_redis_chunks.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_registry.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_remediation.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_retrieval_assertions.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_retrieval_quality.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_review_hardening.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_run_cli.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_sources.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_storage_validator.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_trends.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_universal_concerns.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_utils.py +0 -0
- {compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_vectorstore_filters.py +0 -0
|
@@ -49,6 +49,7 @@ ingestion_validation/report/__init__.py
|
|
|
49
49
|
ingestion_validation/report/_report_css.py
|
|
50
50
|
ingestion_validation/report/_report_js.py
|
|
51
51
|
ingestion_validation/report/analysis.py
|
|
52
|
+
ingestion_validation/report/audit.py
|
|
52
53
|
ingestion_validation/report/export.py
|
|
53
54
|
ingestion_validation/report/shell.py
|
|
54
55
|
ingestion_validation/utils/__init__.py
|
|
@@ -33,6 +33,9 @@ def parse_args(argv=None) -> argparse.Namespace:
|
|
|
33
33
|
p.add_argument("--env", default="dev", help="Environment")
|
|
34
34
|
p.add_argument("--folder", help="Filter to a specific storage subfolder")
|
|
35
35
|
p.add_argument("--limit", type=int, help="Max documents to validate")
|
|
36
|
+
p.add_argument("--with-evidence", nargs="?", type=int, const=0, default=None, metavar="N",
|
|
37
|
+
help="Also run `compass evidence` and embed live proof in each document's modal "
|
|
38
|
+
"(extra KB queries). Optional N caps the documents probed (default: config).")
|
|
36
39
|
p.add_argument("--dry-run", action="store_true", help="List documents without validating")
|
|
37
40
|
p.add_argument("--output-format", choices=["html", "json", "junit", "all"], default="html")
|
|
38
41
|
p.add_argument("--include-graphdb", action="store_true", help="Also validate graph metadata")
|
|
@@ -550,8 +553,29 @@ def main(argv=None) -> int:
|
|
|
550
553
|
passed = sum(1 for r in results if r["result"].all_passed)
|
|
551
554
|
failed = len(results) - passed
|
|
552
555
|
base = f"reports/{args.project}-discovery-report"
|
|
556
|
+
|
|
557
|
+
# Optional: run `compass evidence` and embed live proof per document (query surface; extra KB
|
|
558
|
+
# queries). Keyed by document name so the doc modal renders the proof. Never fails the run.
|
|
559
|
+
evidence_by_doc = None
|
|
560
|
+
if args.with_evidence is not None:
|
|
561
|
+
try:
|
|
562
|
+
from ingestion_validation import evidence as _ev
|
|
563
|
+
from dataclasses import asdict as _asdict
|
|
564
|
+
ev_limit = args.with_evidence or settings.evidence.sample_size
|
|
565
|
+
print(f"\n Gathering evidence (up to {ev_limit} document(s))...")
|
|
566
|
+
payload = _ev.gather_evidence(settings, list(_ev.ALL_CHECKS), limit=ev_limit)
|
|
567
|
+
if payload.get("error"):
|
|
568
|
+
print(f" evidence skipped: {payload['error']}")
|
|
569
|
+
else:
|
|
570
|
+
evidence_by_doc = {}
|
|
571
|
+
for r in payload.get("results", []):
|
|
572
|
+
evidence_by_doc.setdefault(r.document, []).append(_asdict(r))
|
|
573
|
+
print(f" embedded proof for {len(evidence_by_doc)} document(s).")
|
|
574
|
+
except Exception as exc: # noqa: BLE001 - evidence must never break the validation run
|
|
575
|
+
print(f" evidence skipped: {exc}")
|
|
576
|
+
|
|
553
577
|
if args.output_format in ("html", "all"):
|
|
554
|
-
print(f"\n HTML report: {generate_batch_dashboard(results, base + '.html', project=args.project, environment=args.env, run_id=run_id, trends=trend_rows, corpus=corpus, mode='Discovery', profile=profile_data, wall_seconds=wall_seconds, concurrency=concurrency)}")
|
|
578
|
+
print(f"\n HTML report: {generate_batch_dashboard(results, base + '.html', project=args.project, environment=args.env, run_id=run_id, trends=trend_rows, corpus=corpus, mode='Discovery', profile=profile_data, wall_seconds=wall_seconds, concurrency=concurrency, evidence=evidence_by_doc)}")
|
|
555
579
|
if args.output_format in ("json", "all"):
|
|
556
580
|
print(f" JSON report: {export_json(results, base + '.json')}")
|
|
557
581
|
if args.output_format in ("junit", "all"):
|
|
@@ -1266,8 +1266,9 @@ def cmd_evidence(argv: list[str]) -> int:
|
|
|
1266
1266
|
p.add_argument("--document", "-d", default="", help="Only probe documents whose name contains this.")
|
|
1267
1267
|
p.add_argument("--limit", "-l", type=int, default=0, help="Max documents to probe (default: config sample_size).")
|
|
1268
1268
|
p.add_argument("--quick", action="store_true", help="Probe a single document.")
|
|
1269
|
-
p.add_argument("--format", "-f", choices=["markdown", "json"], default="markdown")
|
|
1270
|
-
p.add_argument("--output", "-o", help="Write to a file (default: stdout).")
|
|
1269
|
+
p.add_argument("--format", "-f", choices=["markdown", "json", "html"], default="markdown")
|
|
1270
|
+
p.add_argument("--output", "-o", help="Write to a file (default: stdout for md/json; reports/ for html).")
|
|
1271
|
+
p.add_argument("--open", action="store_true", help="Open the HTML report in a browser.")
|
|
1271
1272
|
args = p.parse_args(argv)
|
|
1272
1273
|
|
|
1273
1274
|
try:
|
|
@@ -1314,14 +1315,23 @@ def cmd_evidence(argv: list[str]) -> int:
|
|
|
1314
1315
|
print(f" {payload['error']}")
|
|
1315
1316
|
return 1
|
|
1316
1317
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1318
|
+
if args.format == "html":
|
|
1319
|
+
from ingestion_validation.report.audit import generate_evidence_report
|
|
1320
|
+
out = generate_evidence_report(payload, args.output or f"reports/evidence-{args.project or 'kb'}.html",
|
|
1321
|
+
project=args.project or "")
|
|
1322
|
+
print(f" HTML report: {out}")
|
|
1323
|
+
if args.open:
|
|
1324
|
+
import webbrowser
|
|
1325
|
+
webbrowser.open(Path(out).resolve().as_uri())
|
|
1323
1326
|
else:
|
|
1324
|
-
|
|
1327
|
+
body = ev.format_json(payload, args.project or "") if args.format == "json" \
|
|
1328
|
+
else ev.format_markdown(payload, args.project or "")
|
|
1329
|
+
if args.output:
|
|
1330
|
+
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
|
|
1331
|
+
Path(args.output).write_text(body + "\n", encoding="utf-8")
|
|
1332
|
+
print(f" Wrote {args.output}")
|
|
1333
|
+
else:
|
|
1334
|
+
print(body)
|
|
1325
1335
|
counts: dict[str, int] = {}
|
|
1326
1336
|
for r in payload["results"]:
|
|
1327
1337
|
counts[r.verdict] = counts.get(r.verdict, 0) + 1
|
|
@@ -1469,8 +1479,9 @@ def cmd_grounding_audit(argv: list[str]) -> int:
|
|
|
1469
1479
|
g.add_argument("--eval-results", metavar="FILE",
|
|
1470
1480
|
help="Batch: JSONL (one object per line) or a JSON array - both accepted. "
|
|
1471
1481
|
"`compass ask --format eval` emits a compatible file.")
|
|
1472
|
-
p.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1473
|
-
p.add_argument("--output", help="Write the report to a file (default: stdout).")
|
|
1482
|
+
p.add_argument("--format", choices=["markdown", "json", "html"], default="markdown")
|
|
1483
|
+
p.add_argument("--output", help="Write the report to a file (default: stdout for md/json; reports/ for html).")
|
|
1484
|
+
p.add_argument("--open", action="store_true", help="Open the HTML report in a browser.")
|
|
1474
1485
|
p.add_argument("--fail-on-fabrication", action="store_true",
|
|
1475
1486
|
help="Exit non-zero if any verifiable claim disagrees with the KB.")
|
|
1476
1487
|
args = p.parse_args(argv)
|
|
@@ -1547,18 +1558,26 @@ def cmd_grounding_audit(argv: list[str]) -> int:
|
|
|
1547
1558
|
doc_exists = _graph_doc_exists(settings) if getattr(settings.eval, "citation_graph_ask", "") else None
|
|
1548
1559
|
results = [run_audit(rec, adapter, settings.eval, doc_exists=doc_exists) for rec in records]
|
|
1549
1560
|
|
|
1550
|
-
if args.format == "
|
|
1551
|
-
from
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
Path(args.output).write_text(body + "\n", encoding="utf-8")
|
|
1559
|
-
print(f" Wrote {args.output}")
|
|
1561
|
+
if args.format == "html":
|
|
1562
|
+
from ingestion_validation.report.audit import generate_grounding_report
|
|
1563
|
+
out = generate_grounding_report(results, args.output or f"reports/grounding-{args.project or 'kb'}.html",
|
|
1564
|
+
project=args.project or "")
|
|
1565
|
+
print(f" HTML report: {out}")
|
|
1566
|
+
if args.open:
|
|
1567
|
+
import webbrowser
|
|
1568
|
+
webbrowser.open(Path(out).resolve().as_uri())
|
|
1560
1569
|
else:
|
|
1561
|
-
|
|
1570
|
+
if args.format == "json":
|
|
1571
|
+
from dataclasses import asdict
|
|
1572
|
+
body = _json.dumps([asdict(r) for r in results], indent=2, default=str)
|
|
1573
|
+
else:
|
|
1574
|
+
body = ("\n\n".join(render_report(r, project=args.project or "") for r in results))
|
|
1575
|
+
if args.output:
|
|
1576
|
+
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
|
|
1577
|
+
Path(args.output).write_text(body + "\n", encoding="utf-8")
|
|
1578
|
+
print(f" Wrote {args.output}")
|
|
1579
|
+
else:
|
|
1580
|
+
print(body)
|
|
1562
1581
|
|
|
1563
1582
|
if not results:
|
|
1564
1583
|
if args.format != "json":
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/__init__.py
RENAMED
|
@@ -112,20 +112,9 @@ li .check-hint{margin:4px 0 0 0;display:block;}
|
|
|
112
112
|
.metric-chip b{color:var(--ink);font-variant-numeric:tabular-nums;}
|
|
113
113
|
.exec-interp-title{font-size:12px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);font-weight:700;margin-bottom:6px;}
|
|
114
114
|
.exec-narrative{font-size:13.5px;line-height:1.6;color:var(--ink);margin:0 0 10px;padding:9px 12px;background:var(--chip-bg);border-radius:8px;border-left:3px solid var(--accent);}
|
|
115
|
-
.subsys-rollup{padding:14px 16px 18px;display:flex;flex-direction:column;gap:8px;border-bottom:1px solid var(--line);}
|
|
116
|
-
.subsys-head{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:700;margin-bottom:4px;}
|
|
117
115
|
.rec-list{padding:14px 16px 16px;display:flex;flex-direction:column;gap:10px;}
|
|
118
|
-
.rec-card{border:1px solid var(--line);border-left:3px solid var(--muted);border-radius:8px;padding:10px 14px;background:var(--chip-bg);}
|
|
119
|
-
.rec-card.bad{border-left-color:var(--bad);}.rec-card.warn{border-left-color:var(--warn);}
|
|
120
|
-
.rec-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:13px;margin-bottom:4px;}
|
|
121
|
-
.rec-impact{margin-left:auto;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;}
|
|
122
116
|
.rec-fix{font-size:12.5px;line-height:1.55;color:var(--ink);}
|
|
123
117
|
.rec-more{font-size:12px;color:var(--muted);padding:4px 2px;}
|
|
124
|
-
.subsys-row{display:grid;grid-template-columns:160px 1fr auto;align-items:center;gap:12px;font-size:12px;}
|
|
125
|
-
.subsys-name{font-weight:700;color:var(--ink);}
|
|
126
|
-
.subsys-bar{height:10px;background:var(--line);border-radius:5px;overflow:hidden;}
|
|
127
|
-
.subsys-bar i{display:block;height:100%;border-radius:5px;}
|
|
128
|
-
.subsys-meta{color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums;}
|
|
129
118
|
@media(max-width:600px){.subsys-row{grid-template-columns:1fr;gap:3px;}}
|
|
130
119
|
.exec-bullets{margin:0 0 4px;padding-left:18px;font-size:13.5px;line-height:1.75;color:var(--ink);}
|
|
131
120
|
.exec-bullets li{margin:0;}
|
|
@@ -1773,6 +1762,19 @@ function renderDoc(idx){
|
|
|
1773
1762
|
html+='<div class="section-title" style="margin-top:24px">Checks by Category</div><div class="table-card"><div class="table-wrap"><table>'+
|
|
1774
1763
|
'<thead><tr><th>Category</th><th class="num">Total</th><th class="num">Passed</th><th class="num">Failed</th><th class="num">Pass Rate</th><th>Coverage</th></tr></thead><tbody>'+catRows+'</tbody></table></div></div>';
|
|
1775
1764
|
}
|
|
1765
|
+
// Evidence (present only with `discover --with-evidence`): concrete live proof for THIS doc.
|
|
1766
|
+
var evAll=(typeof REPORT!=='undefined'&&REPORT.evidence)?REPORT.evidence:null;
|
|
1767
|
+
var ev=evAll?(evAll[d.file]||evAll[d.documentId]||null):null;
|
|
1768
|
+
if(ev&&ev.length){
|
|
1769
|
+
var _vc={CONFIRMED:['bad','Confirmed'],WEAK:['warn','Weak'],NOT_REPRODUCED:['ok','Not reproduced'],ERROR:['warn','Error']};
|
|
1770
|
+
var _pre='white-space:pre-wrap;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.5;color:var(--muted);margin:0;max-height:260px;overflow:auto;';
|
|
1771
|
+
var cards=ev.map(function(r){
|
|
1772
|
+
var vc=_vc[r.verdict]||['warn',r.verdict];
|
|
1773
|
+
var raw=(r.details&&Object.keys(r.details).length)?'<details class="cov-row"><summary class="cov-sum"><span class="cov-name">raw evidence</span></summary><div class="cov-body"><pre style="'+_pre+'">'+_escHtml(JSON.stringify(r.details,null,2))+'</pre></div></details>':'';
|
|
1774
|
+
return '<div class="rca-pattern" style="border-left-color:var(--'+vc[0]+')"><div class="rca-pattern-header"><span class="tag '+vc[0]+'">'+vc[1]+'</span><span class="rca-pattern-title">'+_escHtml((r.check||'').replace(/_/g,' '))+'</span></div><div class="rca-cause">'+_escHtml(r.summary||'')+'</div>'+raw+'</div>';
|
|
1775
|
+
}).join('');
|
|
1776
|
+
html+='<div class="section-title" style="margin-top:24px">Evidence <span class="cat-badge">live proof from the KB</span></div><div class="table-card"><div class="rec-list">'+cards+'</div></div>';
|
|
1777
|
+
}
|
|
1776
1778
|
box.innerHTML=html;
|
|
1777
1779
|
}
|
|
1778
1780
|
// ---- Charts ----
|
|
@@ -1832,6 +1834,7 @@ def generate_batch_dashboard(
|
|
|
1832
1834
|
embed_detail_cap: int = 200,
|
|
1833
1835
|
wall_seconds: float | None = None,
|
|
1834
1836
|
concurrency: int | None = None,
|
|
1837
|
+
evidence: dict | None = None,
|
|
1835
1838
|
) -> str:
|
|
1836
1839
|
"""Generate a self-contained, interactive HTML dashboard. Returns abs path.
|
|
1837
1840
|
|
|
@@ -1915,7 +1918,11 @@ def generate_batch_dashboard(
|
|
|
1915
1918
|
if trimmed else ""
|
|
1916
1919
|
)
|
|
1917
1920
|
|
|
1918
|
-
|
|
1921
|
+
# Optional live-proof payload (from `discover --with-evidence`), keyed by document name, so
|
|
1922
|
+
# the per-document modal can show the concrete evidence for THIS doc. Purely additive: the
|
|
1923
|
+
# report builder stays a pure function of data - evidence is computed upstream (query surface).
|
|
1924
|
+
report_json = json.dumps({"agg": agg, "cases": cases, "evidence": evidence or {}},
|
|
1925
|
+
default=str).replace("</", "<\\/")
|
|
1919
1926
|
trends_html = _trends_section(trends or [])
|
|
1920
1927
|
trends_pill = '<button class="nav-pill" data-section="section-trends" onclick="navScrollTo(\'section-trends\')">Trends</button>' if trends else ""
|
|
1921
1928
|
action_plan_html = _action_plan_section(agg)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Self-contained HTML reports for ``compass evidence`` and ``compass grounding-audit``.
|
|
2
|
+
|
|
3
|
+
Same chrome as the validation dashboard and the coverage/migration analysis reports (via
|
|
4
|
+
``report.shell.render_page``): header, nav pills, dark/light, theme variables, the shared
|
|
5
|
+
card/tile/badge classes. Read-only PRESENTATION of already-computed, deterministic results -
|
|
6
|
+
no LLM and no pass/fail decision happens here. Values are HTML-escaped through ``esc``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ingestion_validation.report.shell import esc, render_page
|
|
16
|
+
|
|
17
|
+
_PRE = ("white-space:pre-wrap;font-family:ui-monospace,Menlo,monospace;font-size:11.5px;"
|
|
18
|
+
"line-height:1.5;color:var(--muted);margin:0;max-height:320px;overflow:auto;")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _abspath(path: str) -> str:
|
|
22
|
+
p = Path(path)
|
|
23
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return str(p.resolve())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _tiles(items: list[tuple[str, object, str]]) -> str:
|
|
28
|
+
cells = "".join(
|
|
29
|
+
f'<div class="tile"><div class="label">{esc(label)}</div>'
|
|
30
|
+
f'<div class="value {cls}">{esc(val)}</div></div>'
|
|
31
|
+
for label, val, cls in items)
|
|
32
|
+
return f'<div class="kpi-grid">{cells}</div>'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _error_page(title: str, heading: str, project: str, run_id: str, message: str, path: str) -> str:
|
|
36
|
+
section = (f'<section class="section" id="section-summary"><div class="section-title">Summary</div>'
|
|
37
|
+
f'<div class="card"><div class="card-inner"><b>{esc(message)}</b></div></div></section>')
|
|
38
|
+
html = render_page(title=title, heading=heading, subtitle=project,
|
|
39
|
+
meta={"Project": project or "-", "Run": run_id or "-"},
|
|
40
|
+
nav=[("section-summary", "Summary")], sections=section)
|
|
41
|
+
Path(path).write_text(html, encoding="utf-8")
|
|
42
|
+
return path
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------------------------------- #
|
|
46
|
+
# compass evidence
|
|
47
|
+
# --------------------------------------------------------------------------- #
|
|
48
|
+
_VERDICT = {"CONFIRMED": ("bad", "Confirmed"), "WEAK": ("warn", "Weak"),
|
|
49
|
+
"NOT_REPRODUCED": ("ok", "Not reproduced"), "ERROR": ("warn", "Error")}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_evidence_report(payload: dict, output_path: str = "reports/evidence-report.html",
|
|
53
|
+
project: str = "", run_id: str = "") -> str:
|
|
54
|
+
"""Render `compass evidence` results: per-check proof cards with the raw evidence."""
|
|
55
|
+
path = _abspath(output_path)
|
|
56
|
+
if payload.get("error"):
|
|
57
|
+
return _error_page("COMPASS Evidence", "Evidence", project, run_id,
|
|
58
|
+
f"Evidence: {payload['error']}", path)
|
|
59
|
+
results = payload.get("results", [])
|
|
60
|
+
counts: dict[str, int] = {}
|
|
61
|
+
for r in results:
|
|
62
|
+
counts[r.verdict] = counts.get(r.verdict, 0) + 1
|
|
63
|
+
tiles = _tiles([
|
|
64
|
+
("Documents probed", payload.get("docs_probed", 0), ""),
|
|
65
|
+
("Confirmed", counts.get("CONFIRMED", 0), "bad"),
|
|
66
|
+
("Weak", counts.get("WEAK", 0), "warn"),
|
|
67
|
+
("Not reproduced", counts.get("NOT_REPRODUCED", 0), "ok"),
|
|
68
|
+
("Errors", counts.get("ERROR", 0), "warn"),
|
|
69
|
+
])
|
|
70
|
+
summary = (f'<section class="section" id="section-summary"><div class="section-title">Summary</div>'
|
|
71
|
+
f'{tiles}<div class="muted" style="margin-top:12px">Deterministic proof built from the '
|
|
72
|
+
"KB's own chunks - no LLM, no live embedding. A CONFIRMED verdict reproduces exactly "
|
|
73
|
+
'what validation flagged.</div></section>')
|
|
74
|
+
|
|
75
|
+
by_check: dict[str, list] = {}
|
|
76
|
+
for r in results:
|
|
77
|
+
by_check.setdefault(r.check, []).append(r)
|
|
78
|
+
nav = [("section-summary", "Summary")]
|
|
79
|
+
sections = summary
|
|
80
|
+
for check, items in by_check.items():
|
|
81
|
+
confirmed = sum(1 for r in items if r.verdict == "CONFIRMED")
|
|
82
|
+
sid = "section-" + "".join(ch for ch in check if ch.isalnum())
|
|
83
|
+
label = check.replace("_", " ").title()
|
|
84
|
+
nav.append((sid, label))
|
|
85
|
+
cards = ""
|
|
86
|
+
for r in items:
|
|
87
|
+
cls, txt = _VERDICT.get(r.verdict, ("warn", r.verdict))
|
|
88
|
+
details = ""
|
|
89
|
+
if r.details:
|
|
90
|
+
details = ('<details class="cov-row"><summary class="cov-sum">'
|
|
91
|
+
'<span class="cov-name">raw evidence</span></summary>'
|
|
92
|
+
f'<div class="cov-body"><pre style="{_PRE}">'
|
|
93
|
+
f'{esc(json.dumps(r.details, indent=2, default=str))}</pre></div></details>')
|
|
94
|
+
cards += (f'<div class="rca-pattern" style="border-left-color:var(--{cls})">'
|
|
95
|
+
'<div class="rca-pattern-header">'
|
|
96
|
+
f'<span class="tag {cls}">{esc(txt)}</span>'
|
|
97
|
+
f'<span class="rca-pattern-title">{esc(r.document)}</span></div>'
|
|
98
|
+
f'<div class="rca-cause">{esc(r.summary)}</div>{details}</div>')
|
|
99
|
+
sections += (f'<section class="section" id="{sid}"><div class="section-title">{esc(label)} '
|
|
100
|
+
f'<span class="cat-badge">{confirmed}/{len(items)} confirmed</span></div>'
|
|
101
|
+
f'<div class="table-card"><div class="rec-list">{cards}</div></div></section>')
|
|
102
|
+
|
|
103
|
+
html = render_page(
|
|
104
|
+
title=f"COMPASS Evidence - {project or 'KB'}",
|
|
105
|
+
heading=f"Evidence - {project or 'KB'}", subtitle=project,
|
|
106
|
+
meta={"Project": project or "-", "Run": run_id or "-",
|
|
107
|
+
"Documents probed": payload.get("docs_probed", 0)},
|
|
108
|
+
nav=nav, sections=sections)
|
|
109
|
+
Path(path).write_text(html, encoding="utf-8")
|
|
110
|
+
return path
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --------------------------------------------------------------------------- #
|
|
114
|
+
# compass grounding-audit
|
|
115
|
+
# --------------------------------------------------------------------------- #
|
|
116
|
+
_ATTR = {
|
|
117
|
+
"none": ("ok", "Grounded"),
|
|
118
|
+
"generation": ("bad", "Generation hallucination"),
|
|
119
|
+
"fabricated_source": ("bad", "Fabricated source"),
|
|
120
|
+
"retrieval": ("warn", "Retrieval gap"),
|
|
121
|
+
"citation_error": ("warn", "Citation error"),
|
|
122
|
+
"unverifiable": ("", "Unverifiable"),
|
|
123
|
+
"unknown": ("", "Unattributed"),
|
|
124
|
+
}
|
|
125
|
+
_SEV = {"CRITICAL": "bad", "HIGH": "bad", "MEDIUM": "warn", "LOW": "warn", "INFO": "ok"}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def generate_grounding_report(results: list[Any], output_path: str = "reports/grounding-report.html",
|
|
129
|
+
project: str = "", run_id: str = "") -> str:
|
|
130
|
+
"""Render `compass grounding-audit` results: per-case verdict + per-claim citation table."""
|
|
131
|
+
path = _abspath(output_path)
|
|
132
|
+
if not results:
|
|
133
|
+
return _error_page("COMPASS Grounding Audit", "Grounding Audit", project, run_id,
|
|
134
|
+
"No eval records to report.", path)
|
|
135
|
+
sound = sum(r.citations.get("summary", {}).get("sound", 0) for r in results)
|
|
136
|
+
checkable = sum(r.citations.get("summary", {}).get("checkable", 0) for r in results)
|
|
137
|
+
acc = round(100.0 * sound / checkable, 1) if checkable else None
|
|
138
|
+
fabr = sum(1 for r in results if r.has_fabrication)
|
|
139
|
+
tiles = _tiles([
|
|
140
|
+
("Cases", len(results), ""),
|
|
141
|
+
("Citations grounded", f"{sound}/{checkable}", ""),
|
|
142
|
+
("Accuracy", f"{acc}%" if acc is not None else "-", "ok" if (acc or 0) >= 80 else "warn"),
|
|
143
|
+
("Cases with fabrication", fabr, "bad" if fabr else "ok"),
|
|
144
|
+
])
|
|
145
|
+
summary = (f'<section class="section" id="section-summary"><div class="section-title">Summary</div>'
|
|
146
|
+
f'{tiles}<div class="muted" style="margin-top:12px">Deterministic adjudication of the '
|
|
147
|
+
"eval's claims against the KB - no LLM in any verdict; it does not judge answer "
|
|
148
|
+
'quality.</div></section>')
|
|
149
|
+
nav = [("section-summary", "Summary")]
|
|
150
|
+
sections = summary
|
|
151
|
+
for i, r in enumerate(results):
|
|
152
|
+
sid = f"section-case{i}"
|
|
153
|
+
sev = r.severity()
|
|
154
|
+
nav.append((sid, (r.test_id or f"case {i + 1}")[:18]))
|
|
155
|
+
verdicts = r.citation_verdicts
|
|
156
|
+
if verdicts:
|
|
157
|
+
rows = "".join(
|
|
158
|
+
f'<tr><td>{esc((v.claim[:90] + "...") if len(v.claim) > 90 else v.claim)}</td>'
|
|
159
|
+
f'<td>{esc(v.doc)}</td><td>{esc(v.page or "-")}</td>'
|
|
160
|
+
f'<td><span class="tag {_ATTR.get(v.attribution, ("", v.attribution))[0]}">'
|
|
161
|
+
f'{esc(_ATTR.get(v.attribution, ("", v.attribution))[1])}</span></td></tr>'
|
|
162
|
+
for v in verdicts)
|
|
163
|
+
table = ('<div class="table-wrap"><table><thead><tr><th>Cited claim</th><th>Source</th>'
|
|
164
|
+
f'<th>Page</th><th>Attribution</th></tr></thead><tbody>{rows}</tbody></table></div>')
|
|
165
|
+
else:
|
|
166
|
+
table = '<div class="muted">No citations in this answer.</div>'
|
|
167
|
+
signals = "".join(
|
|
168
|
+
f'<li><span class="tag {_SEV.get(s.get("severity", "INFO"), "warn")}">{esc(s.get("severity", ""))}'
|
|
169
|
+
f'</span> {esc(s.get("label", ""))} <span class="check-detail">{esc(s.get("detail", ""))}</span></li>'
|
|
170
|
+
for s in (r.signals or []))
|
|
171
|
+
signals_html = f'<ul>{signals}</ul>' if signals else ""
|
|
172
|
+
sections += (
|
|
173
|
+
f'<section class="section" id="{sid}"><div class="section-title">'
|
|
174
|
+
f'{esc(r.test_id or f"Case {i + 1}")} <span class="tag {_SEV.get(sev, "warn")}">{esc(sev)}</span></div>'
|
|
175
|
+
'<div class="card"><div class="card-inner">'
|
|
176
|
+
f'<div class="muted" style="margin-bottom:8px">Q: {esc(r.query)}</div>'
|
|
177
|
+
f'<div class="rca-cause"><b>Verdict:</b> {esc(r.layer_diagnosis())}</div>'
|
|
178
|
+
f'{signals_html}{table}</div></div></section>')
|
|
179
|
+
|
|
180
|
+
html = render_page(
|
|
181
|
+
title=f"COMPASS Grounding Audit - {project or 'KB'}",
|
|
182
|
+
heading=f"Grounding Audit - {project or 'KB'}", subtitle=project,
|
|
183
|
+
meta={"Project": project or "-", "Run": run_id or "-", "Cases": len(results)},
|
|
184
|
+
nav=nav, sections=sections)
|
|
185
|
+
Path(path).write_text(html, encoding="utf-8")
|
|
186
|
+
return path
|
|
@@ -425,6 +425,17 @@ def test_nonstring_answer_field_is_not_stringified():
|
|
|
425
425
|
assert "{" not in r.query
|
|
426
426
|
|
|
427
427
|
|
|
428
|
+
def test_grounding_html_report_renders(tmp_path):
|
|
429
|
+
from ingestion_validation.report.audit import generate_grounding_report
|
|
430
|
+
rec = {"input": "Earnings?", "output": "Earnings rose 40% (MIS, May 2026, p. 1).",
|
|
431
|
+
"context.retrieved": "c", "scores": {}}
|
|
432
|
+
results = [run_audit(rec, _kb(), _cfg(gt_response_path="response"))]
|
|
433
|
+
out = generate_grounding_report(results, str(tmp_path / "gr.html"), project="wp")
|
|
434
|
+
html = open(out, encoding="utf-8").read()
|
|
435
|
+
assert "Grounding Audit" in html
|
|
436
|
+
assert "Generation hallucination" in html and "HIGH" in html # per-claim attribution + severity
|
|
437
|
+
|
|
438
|
+
|
|
428
439
|
def test_report_renders_citation_section():
|
|
429
440
|
rec = {"input": "Earnings?", "response": "Earnings rose 40% (MIS, May 2026, p. 1).",
|
|
430
441
|
"context.retrieved": "c", "scores": {}}
|
|
@@ -165,3 +165,14 @@ def test_probe_reproduces_validator_via_shared_text_probes():
|
|
|
165
165
|
from ingestion_validation import text_probes
|
|
166
166
|
from ingestion_validation.validators import retrieval_quality
|
|
167
167
|
assert retrieval_quality._shared_paraphrases is text_probes.paraphrases
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_evidence_html_report_renders_and_escapes(tmp_path):
|
|
171
|
+
from ingestion_validation.report.audit import generate_evidence_report
|
|
172
|
+
payload = {"docs_probed": 1, "results": [
|
|
173
|
+
ev.EvidenceResult("duplicate_chunks", "<img onerror=x>.pdf", "CONFIRMED",
|
|
174
|
+
"1 exact-duplicate pair", {"pairs": []})]}
|
|
175
|
+
out = generate_evidence_report(payload, str(tmp_path / "ev.html"), project="wp")
|
|
176
|
+
html = open(out, encoding="utf-8").read()
|
|
177
|
+
assert "COMPASS Evidence" in html and "Confirmed" in html
|
|
178
|
+
assert "<img onerror=x>" not in html and "<img" in html # untrusted doc name escaped
|
|
@@ -709,3 +709,41 @@ def test_chunk_previews_kept_per_document_not_blanket_at_scale():
|
|
|
709
709
|
by = {c["name"]: c for c in payload["cases"]}
|
|
710
710
|
assert by["small"]["chunks"]["items"] # 2-chunk doc keeps its previews
|
|
711
711
|
assert by["big0"]["chunks"]["items"] == [] # 20-chunk doc is trimmed
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def _dash_item(name, status):
|
|
715
|
+
r = PipelineResult(document_id=name, file_name=name)
|
|
716
|
+
r.add_step(StepResult("Redis Chunks", status, "ok" if status == StepStatus.PASSED else "x",
|
|
717
|
+
checks=[{"label": "Chunk boundaries clean", "passed": status == StepStatus.PASSED,
|
|
718
|
+
"severity": "warn"}]))
|
|
719
|
+
r.finalize()
|
|
720
|
+
return {"name": name, "result": r}
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _embedded_report(html_path):
|
|
724
|
+
import json as _json
|
|
725
|
+
import pathlib
|
|
726
|
+
import re as _re
|
|
727
|
+
doc = pathlib.Path(html_path).read_text(encoding="utf-8")
|
|
728
|
+
m = _re.search(r"const REPORT = (\{.*?\});", doc, _re.S)
|
|
729
|
+
assert m is not None
|
|
730
|
+
pathlib.Path(html_path).unlink()
|
|
731
|
+
return _json.loads(m.group(1)), doc
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def test_evidence_embeds_in_report_keyed_by_document():
|
|
735
|
+
# `discover --with-evidence` embeds live proof keyed by doc; the modal renders it.
|
|
736
|
+
items = [_dash_item("Guide.pdf", StepStatus.FAILED)]
|
|
737
|
+
evidence = {"Guide.pdf": [{"check": "duplicate_chunks", "document": "Guide.pdf",
|
|
738
|
+
"verdict": "CONFIRMED", "summary": "1 dup pair", "details": {"n": 1}}]}
|
|
739
|
+
html = report_mod.generate_batch_dashboard(items, "reports/_t_ev.html", project="p", evidence=evidence)
|
|
740
|
+
data, doc = _embedded_report(html)
|
|
741
|
+
assert data["evidence"]["Guide.pdf"][0]["verdict"] == "CONFIRMED"
|
|
742
|
+
assert "live proof from the KB" in doc # the modal evidence section renders
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def test_report_without_evidence_stays_backward_compatible():
|
|
746
|
+
items = [_dash_item("Guide.pdf", StepStatus.PASSED)]
|
|
747
|
+
html = report_mod.generate_batch_dashboard(items, "reports/_t_noev.html", project="p")
|
|
748
|
+
data, _ = _embedded_report(html)
|
|
749
|
+
assert data["evidence"] == {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/baseline.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/chunk_coherence.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/citation.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/config/__init__.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/config_lint.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/content_inspect.py
RENAMED
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/corpus_health.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/diagnostics.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/embedding_drift.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/eval_interop.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/evidence.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/grounding.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/init_wizard.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/live_dashboard.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/mcp_server.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/models/__init__.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/notifications.py
RENAMED
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/profiler.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/graph.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/providers/source.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/remediation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/analysis.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/export.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/report/shell.py
RENAMED
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/text_probes.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/trend_tracker.py
RENAMED
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/ingestion_validation/validators/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_discovery_parallel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_retrieval_assertions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_universal_concerns.py
RENAMED
|
File without changes
|
|
File without changes
|
{compass_kb_validation-2.8.0 → compass_kb_validation-2.9.0}/tests/test_vectorstore_filters.py
RENAMED
|
File without changes
|