diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md index bb4f8ec3..9199c39a 100644 --- a/docs/research/pre_work_error_ledger.md +++ b/docs/research/pre_work_error_ledger.md @@ -118,7 +118,7 @@ PyJHora issue #17 is a UI/encoding issue with a general mismatch report, not VP `shadbala_VPJainBook_tests` supplies no independently citable book edition, page, or numerical table. The product therefore calls it a VP Jain-attributed fixture, not a published worked example; it remains candidate replay data and cannot close a formula branch. ## ERR-082 | Public similar-case catalog coverage was mistaken for global coverage | active 2026-07-18 -The replayable public-case manifest currently contains 10 events: 5 career and 5 marriage. It contains no wealth or health events with the required birth source, event source, and replay contract. The product now returns available domains and requested uncovered domains rather than treating an empty result as evidence of no comparable case. +The replayable public-case manifest currently contains 10 events: 5 career and 5 marriage. A separate health `public_context_only` case may be shown for comparison, but it is not replayed calibration evidence. Wealth still has no case with the required birth source, event source, and replay contract. The product returns available domains and requested uncovered domains rather than treating an empty result as evidence of no comparable case. Prevention: add a new domain only after its public cases satisfy the same source/replay contract; never substitute loosely sourced celebrity notes or absence of a match for a negative conclusion. diff --git a/frontend/src/mastra/index.ts b/frontend/src/mastra/index.ts index 0cebd28c..28e7944e 100644 --- a/frontend/src/mastra/index.ts +++ b/frontend/src/mastra/index.ts @@ -112,6 +112,7 @@ Treat consumer_context as the authoritative answer policy: When reference_transparency is present: - Present candidate_windows and exact_triggers when relevant, but describe exact_triggers as technical trigger points, never guaranteed events. - Share a public case only when similar_public_cases.status is high_similarity_public_references_available. State the listed matching factors, dissimilar factors, event source URL, and that the case is reference-only. +- If a shared case has reference_status public_context_only, state that it has not been replayed for calibration and cannot increase timing confidence. - When similar_public_cases.coverage.requested_uncovered_domains is non-empty, say the current public-case catalog does not yet cover those themes; do not infer that no comparable real-world case exists. - When method_variants applies, present parallel methods and their source paths rather than silently picking one result as the only truth. - If should_lead_with_limitations is false, do not lead with limitations. If a limitation is relevant, put it in one short sentence at the end. diff --git a/frontend/tests/consultation-context.test.ts b/frontend/tests/consultation-context.test.ts index 8d2568db..b573d3a7 100644 --- a/frontend/tests/consultation-context.test.ts +++ b/frontend/tests/consultation-context.test.ts @@ -8,5 +8,6 @@ test("passes transparent public-case references into the agent context", () => { assert.match(source, /reference_transparency:\s*record\(data\.reference_transparency\)/); assert.match(source, /high_similarity_public_references_available/); assert.match(source, /requested_uncovered_domains/); + assert.match(source, /public_context_only/); assert.match(source, /exact_triggers as technical trigger points/); }); diff --git a/references/real_case_calibration/catalog.schema.json b/references/real_case_calibration/catalog.schema.json index 1377f301..1dd5a309 100644 --- a/references/real_case_calibration/catalog.schema.json +++ b/references/real_case_calibration/catalog.schema.json @@ -50,8 +50,8 @@ "properties": { "event_type": {"type": "string"}, "event_date": {"type": "string"}, - "domain": {"enum": ["career", "marriage"]}, - "expected_label": {"enum": ["career_status", "legal_marriage"]}, + "domain": {"enum": ["career", "marriage", "wealth", "health"]}, + "expected_label": {"enum": ["career_status", "legal_marriage", "wealth_event", "health_event"]}, "outcome": {"type": "string"}, "source_excerpt_note": {"type": "string"}, "source": { diff --git a/references/real_case_calibration/public_context_manifest.json b/references/real_case_calibration/public_context_manifest.json new file mode 100644 index 00000000..065876ad --- /dev/null +++ b/references/real_case_calibration/public_context_manifest.json @@ -0,0 +1,21 @@ +{ + "schema_version": "1.0", + "scope": "public_context_references_only", + "boundary": "These public cases may be shown when natal similarity is high. They are not replayed calibration cases and cannot raise a prediction score or validate a timing claim.", + "case_schema": "references/real_case_calibration/catalog.schema.json", + "cases": [ + { + "case_id": "kahlo_bus_injury_1925", + "subject": { + "name": "Frida Kahlo", "year": 1907, "month": 7, "day": 6, "hour": 8, "minute": 30, + "lat": 19.3333, "lon": -99.1667, "tz": -6.6111, "node_mode": "mean", + "birth_source": {"url": "https://www.astro.com/adbvip/adbvip_07_06.htm", "source_grade": "primary", "time_accuracy_rating": "AA", "evidence_basis": "BC/BR in hand"} + }, + "source": {"url": "https://www.astro.com/adbvip/adbvip_07_06.htm", "source_grade": "primary", "license_or_quote_boundary": "facts_and_short_summary_only"}, + "chart_signature": {"benchmark_role": "public_context_reference", "external_oracle_status": "not_replayed", "timezone_note": "Astro-Databank LMT m99w10"}, + "event_outcomes": [{"event_type": "serious_injury", "event_date": "1925", "domain": "health", "expected_label": "health_event", "outcome": "A bus accident at age 18 caused lifelong pain and medical problems.", "source_excerpt_note": "Public biography records the 1925 bus accident and its lasting health impact.", "source": {"url": "https://en.wikipedia.org/wiki/Frida_Kahlo", "source_grade": "verified_secondary"}}], + "similarity": {"score": 0.0, "matching_factors": ["not_user_similarity_benchmark"], "dissimilar_factors": []}, + "replay": {"outcome_replay_status": "pending", "do_not_use_for_prediction": true, "report_path": "", "conflict_notes": ["Public context reference only; health-domain outcome replay has not been run."]} + } + ] +} diff --git a/scripts/reference_transparency_contract.py b/scripts/reference_transparency_contract.py index e8b115af..491e1489 100644 --- a/scripts/reference_transparency_contract.py +++ b/scripts/reference_transparency_contract.py @@ -23,6 +23,7 @@ except ModuleNotFoundError: # pragma: no cover - direct script execution ROOT = Path(__file__).resolve().parents[1] DEFAULT_MANIFEST = ROOT / "references" / "real_case_calibration" / "replay_manifest.json" +DEFAULT_CONTEXT_MANIFEST = ROOT / "references" / "real_case_calibration" / "public_context_manifest.json" DOMAIN_HOUSES = { "career": "house_10", "marriage": "house_7", @@ -126,9 +127,14 @@ def _coverage(cases: list[dict[str, Any]], themes: list[str]) -> dict[str, list[ domains: set[str] = set() for case in cases: replay = case.get("replay") - if not isinstance(replay, dict) or replay.get("outcome_replay_status") != "replayed": + if not isinstance(replay, dict): continue - if replay.get("do_not_use_for_prediction") is True: + replay_status = replay.get("outcome_replay_status") + if replay_status == "replayed" and replay.get("do_not_use_for_prediction") is not True: + pass + elif replay_status == "pending" and replay.get("do_not_use_for_prediction") is True: + pass + else: continue for event in case.get("event_outcomes", []): if isinstance(event, dict) and isinstance(event.get("domain"), str): @@ -148,13 +154,20 @@ def select_similar_public_cases( threshold: float = HIGH_SIMILARITY_THRESHOLD, max_cases: int = 3, ) -> dict[str, Any]: - candidates = cases if cases is not None else _load_cases(DEFAULT_MANIFEST) + candidates = cases if cases is not None else ( + _load_cases(DEFAULT_MANIFEST) + _load_cases(DEFAULT_CONTEXT_MANIFEST) + ) selected: list[dict[str, Any]] = [] for case in candidates: replay = case.get("replay") - if not isinstance(replay, dict) or replay.get("outcome_replay_status") != "replayed": + if not isinstance(replay, dict): continue - if replay.get("do_not_use_for_prediction") is True: + replay_status = replay.get("outcome_replay_status") + if replay_status == "replayed" and replay.get("do_not_use_for_prediction") is not True: + reference_status = "calibration_replayed" + elif replay_status == "pending" and replay.get("do_not_use_for_prediction") is True: + reference_status = "public_context_only" + else: continue case_chart = _chart_for_case(case) if not isinstance(case_chart, dict): @@ -179,6 +192,7 @@ def select_similar_public_cases( "event_source": {"url": event_source.get("url"), "source_grade": event_source.get("source_grade")}, "similarity": similarity, "reference_only": True, + "reference_status": reference_status, "difference_notice": "相似仅限列出的 D1 特征;未比较层不得推断为相同。", }) selected.sort(key=lambda item: (-item["similarity"]["score"], item["case_id"] or "")) @@ -187,7 +201,10 @@ def select_similar_public_cases( "status": "high_similarity_public_references_available" if selected else "no_high_similarity_public_reference", "cases": selected, "threshold": threshold, - "manifest": "references/real_case_calibration/replay_manifest.json", + "manifest": [ + "references/real_case_calibration/replay_manifest.json", + "references/real_case_calibration/public_context_manifest.json", + ], "public_figures_only": True, "does_not_predict_user_outcome": True, "coverage": _coverage(candidates, themes), diff --git a/tests/test_real_case_replay_validator.py b/tests/test_real_case_replay_validator.py index fd9e6a24..5689c60d 100644 --- a/tests/test_real_case_replay_validator.py +++ b/tests/test_real_case_replay_validator.py @@ -19,6 +19,14 @@ def test_real_case_replay_manifest_contains_ten_research_grade_cases() -> None: assert result["birth_time_ratings"] == {"A": 2, "AA": 8} +def test_public_context_manifest_is_structured_but_not_replay_calibration() -> None: + result = validate_manifest(ROOT / "references/real_case_calibration/public_context_manifest.json") + assert result["status"] == "partial" + assert result["errors"] == [] + assert result["replay_ready_count"] == 0 + assert result["domain_counts"] == {"health": 1} + + def test_holdout_manifest_contains_ten_new_balanced_cases() -> None: batch1_path = ROOT / "references/real_case_calibration/replay_manifest.json" holdout_path = ROOT / "references/real_case_calibration/replay_manifest_holdout_v2.json" diff --git a/tests/test_reference_transparency_contract.py b/tests/test_reference_transparency_contract.py index 2cb62883..e6f63a6f 100644 --- a/tests/test_reference_transparency_contract.py +++ b/tests/test_reference_transparency_contract.py @@ -90,6 +90,44 @@ def test_default_public_manifest_can_surface_a_matching_replayed_case() -> None: assert selected["cases"][0]["reference_only"] is True +def test_pending_health_case_is_context_only_not_calibration() -> None: + user_chart = _chart("Leo", "Pisces", "Libra") + cases = [{ + "case_id": "public_health_context", + "subject": {"name": "Public Example"}, + "chart": _chart("Leo", "Pisces", "Libra"), + "source": {"url": "https://example.com/birth", "source_grade": "primary"}, + "event_outcomes": [{ + "domain": "health", "event_type": "serious_injury", "event_date": "1925-09", + "outcome": "Public health event", "source": {"url": "https://example.com/event", "source_grade": "verified_secondary"}, + }], + "replay": {"outcome_replay_status": "pending", "do_not_use_for_prediction": True}, + }] + + selected = select_similar_public_cases(user_chart, ["health"], cases=cases) + + assert selected["status"] == "high_similarity_public_references_available" + assert selected["cases"][0]["reference_status"] == "public_context_only" + assert selected["cases"][0]["reference_only"] is True + assert selected["coverage"]["available_event_domains"] == ["health"] + + +def test_default_manifest_exposes_kahlo_health_as_context_only() -> None: + from scripts.domain_calculation_service import compute_chart + + kahlo_chart = compute_chart({ + "year": 1907, "month": 7, "day": 6, "hour": 8, "minute": 30, + "lat": 19.3333, "lon": -99.1667, "tz": -6.6111, + "ayanamsa": "lahiri", "node_mode": "mean", + }) + + selected = select_similar_public_cases(kahlo_chart, ["health"]) + + assert [case["case_id"] for case in selected["cases"]] == ["kahlo_bus_injury_1925"] + assert selected["cases"][0]["reference_status"] == "public_context_only" + assert selected["coverage"]["available_event_domains"] == ["career", "health", "marriage"] + + def test_consultation_api_exposes_reference_transparency_contract() -> None: source = (ROOT / "scripts" / "jyotish_api_server.py").read_text(encoding="utf-8") assert "result['reference_transparency'] = build_reference_transparency_contract(" in source