diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68adfd7..ee82c8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,8 +151,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 env: - QPK_UNIFIED_NET_REPORT_SHA: b23511cbb28b72743e35512ef4b3c73f1cd43c23 + QPK_UNIFIED_NET_REPORT_SHA: b6b51d5c32594c47cc1b3ebc0c1ed226e9db887f UES_REQUIRE_UNIFIED_NET_REPORT: "1" + UES_REQUIRE_NET_PATH_DIAGNOSTICS: "1" steps: - name: Checkout uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 @@ -174,11 +175,15 @@ jobs: import inspect, json, os from importlib.metadata import distribution from quant_platform_kit.research_stats.unified_report import build_unified_net_report + from quant_platform_kit.research_stats.net_path_diagnostics import build_net_path_diagnostics installed = json.loads(distribution("quant-platform-kit").read_text("direct_url.json")) assert installed["vcs_info"]["commit_id"] == os.environ["QPK_UNIFIED_NET_REPORT_SHA"] assert "include_outcome_diagnostics" in inspect.signature(build_unified_net_report).parameters report = build_unified_net_report([-.1, 0.0, .2], include_outcome_diagnostics=True) assert len(report.metrics) == 12 and report.no_order is True + path = build_net_path_diagnostics([-.5, 0.0, 1.0]) + assert path.metric("longest_recovered_drawdown_duration_observations").value == 3 + assert path.no_order is True and path.live_ready is False print("Required latest unified/outcome report capability and immutable source verified.") PY @@ -194,6 +199,8 @@ jobs: cases = list(root.iter("testcase")) assert cases and any("unified_cli_uses_actual_selected_net_returns" in c.attrib["name"] for c in cases) assert any("unified_hand_math" in c.attrib["name"] for c in cases) + assert any("net_path_actual_cli_31_books" in c.attrib["name"] for c in cases) + assert any("net_path_hand_math_and_censoring" in c.attrib["name"] for c in cases) assert not any(list(root.iter(tag)) for tag in ("skipped", "failure", "error")) print("Actual CLI chain and positive hand-math tests executed with no skipped/failed cases.") PY diff --git a/docs/research/c3_self_financing_combo_ledger.zh-CN.md b/docs/research/c3_self_financing_combo_ledger.zh-CN.md index 184e54b..ab262f8 100644 --- a/docs/research/c3_self_financing_combo_ledger.zh-CN.md +++ b/docs/research/c3_self_financing_combo_ledger.zh-CN.md @@ -167,3 +167,31 @@ DSR 分别保留 `UNCOMPUTABLE/missing_gross_returns` 和 更新报告已有的真实 `implementation_sha256` 及其派生 `evidence_digest`, 不得硬编码旧身份以假称完整字节不变。未开启诊断时除此必要来源身份 之外,历史报告数值/字段/行为保持;旧 RS artifact 和 trial ledger 不重写。 + +### 同窗口路径观察诊断(独立显式选择) + +同一历史 CLI 可额外选择 `--include-net-path-diagnostics`,要求 +`--historical-account-report`;三个 Python 报告入口相应参数 +`include_net_path_diagnostics=False`。不请求时不导入新能力、不增加字段。 +显式请求而旧 QPK 不具备能力时,在读取私有 pack/snapshot 前返回 +`PARKED/NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE`,不会仅返回旧报告。 +该参数与 `--include-unified-net-report` 独立,可以同时选择;原十二项不变。 + +每本现有报告附 `net_path_diagnostics` 和同窗口 basis,直接使用该本已验证 +ledger 的净简单收益,不再扣费用、不增加初始 NAV 收益观察、不改变 +路径、参数、费用、排序或账本。四项是最长 underwater 观察段、最长 +已完成回撤恢复时长、窗口末 underwater 观察段和最长严格负净收益观察段。 +单位是 return observations,不能称作日历钟点、逐笔交易连败或未来恢复预测。 +零收益在 underwater 时延续,同时中断严格负收益连亏;恢复时长包含 +恢复观察。无已恢复样本为 null;末段未恢复显式 right-censored。 + +QPK 以校验后所供浮点收益的精确相对财富乘积判断峰值,不使用 epsilon +把微小回撤圆整为恢复。这里的 NAV=1 是窗口归一化起点而非账户余额; +此前峰值/连亏与完整日历均未证明,`window_only/prior_history=unknown`。 +截短窗口不证明原回撤恢复,也不证明账户全历史最长连亏。原报告的 +REPORT_ONLY、DEVELOPMENT_ONLY、no_order、data/oos unqualified 及晋级拒绝保持。 + +既有 research-unified-net-consumer CI 在自身隔离环境绑定不可变 QPK 新能力, +同时强制旧十二项和新路径报告实际 CLI/手算用例、零 skip;生产依赖 pin +与其他两个 research overlays 保留。验收只使用已有生成协议夹具,不运行 +私有 cloud workflow、不新增策略试验、PIT/OOS 或生产采用声明。 diff --git a/scripts/run_c3_batch_a_existing_member_baseline.py b/scripts/run_c3_batch_a_existing_member_baseline.py index 0288162..2eef6ed 100644 --- a/scripts/run_c3_batch_a_existing_member_baseline.py +++ b/scripts/run_c3_batch_a_existing_member_baseline.py @@ -123,6 +123,8 @@ def main(argv: Sequence[str] | None = None) -> int: parser.add_argument("--annual-minimum-acceptable-return", type=float, default=None) parser.add_argument("--include-unified-net-report", action="store_true", help="Explicit research-only B3 sidecar with per-session outcome diagnostics; requires historical-account-report and latest QPK.") + parser.add_argument("--include-net-path-diagnostics", action="store_true", + help="Opt-in window-local net-return path counts; requires historical-account-report and the research capability.") parser.add_argument( "--private-root", type=Path, @@ -130,15 +132,20 @@ def main(argv: Sequence[str] | None = None) -> int: help=argparse.SUPPRESS, # legacy flag retained only to emit a clear park reason ) args = parser.parse_args(argv) - if args.include_unified_net_report: + for requested, builder_name, unavailable_reason in ( + (args.include_unified_net_report, "_unified_net_report_builder", "UNIFIED_NET_REPORT_CAPABILITY_UNAVAILABLE"), + (args.include_net_path_diagnostics, "_net_path_diagnostics_builder", "NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE"), + ): + if not requested: + continue if not args.historical_account_report: reason = "HISTORICAL_REPORT_OPT_IN_REQUIRED" else: - from us_equity_strategies.research.tqqq_core_trial_journal import _unified_net_report_builder + from us_equity_strategies.research import tqqq_core_trial_journal as journal try: - _unified_net_report_builder() + getattr(journal, builder_name)() except ValueError: - reason = "UNIFIED_NET_REPORT_CAPABILITY_UNAVAILABLE" + reason = unavailable_reason else: reason = None if reason is not None: @@ -190,7 +197,8 @@ def main(argv: Sequence[str] | None = None) -> int: identity_path=args.snapshot_input_root / "tqqq_qqq" / "object_identity.json"), annual_risk_free_rate=args.annual_risk_free_rate, annual_minimum_acceptable_return=args.annual_minimum_acceptable_return, - include_unified_net_report=args.include_unified_net_report) + include_unified_net_report=args.include_unified_net_report, + include_net_path_diagnostics=args.include_net_path_diagnostics) except (ValueError, TypeError, ArithmeticError): # Never echo private paths, provider bodies or an arbitrary exception. result = {"status": "PARKED", "reason_codes": ["HISTORICAL_ACCOUNT_REPORT_REFUSED"], diff --git a/src/us_equity_strategies/research/c3_batch_a_existing_member_baseline.py b/src/us_equity_strategies/research/c3_batch_a_existing_member_baseline.py index 1628950..5d7591b 100644 --- a/src/us_equity_strategies/research/c3_batch_a_existing_member_baseline.py +++ b/src/us_equity_strategies/research/c3_batch_a_existing_member_baseline.py @@ -571,6 +571,7 @@ def report_historical_batch_a_baselines( *, frozen_member_pack: Mapping[str, object], soxl_snapshot, tqqq_snapshot, annual_risk_free_rate: float, annual_minimum_acceptable_return: float, include_unified_net_report: bool = False, + include_net_path_diagnostics: bool = False, ) -> dict: """Opt-in source-bound historical control, retaining the old comparison. @@ -583,12 +584,19 @@ def report_historical_batch_a_baselines( from .batch_a_member_pack import materialize_batch_a_member_pack from .c3_capital_path import scale_member_budgets_to_cash from .c3_self_financing_combo_ledger import build_historical_self_financing_ledger - from .tqqq_core_trial_journal import compute_historical_research_metrics, _unified_net_report_builder + from .tqqq_core_trial_journal import ( + compute_historical_research_metrics, _unified_net_report_builder, + _net_path_diagnostics_builder, + ) if type(include_unified_net_report) is not bool: raise ValueError("UNIFIED_NET_REPORT_OPT_IN_INVALID") + if type(include_net_path_diagnostics) is not bool: + raise ValueError("NET_PATH_DIAGNOSTICS_OPT_IN_INVALID") if include_unified_net_report: _unified_net_report_builder() + if include_net_path_diagnostics: + _net_path_diagnostics_builder() if not all(isinstance(item, PriceSnapshotV2) for item in (soxl_snapshot, tqqq_snapshot)): raise ValueError("HISTORICAL_SNAPSHOTS_REQUIRED") @@ -645,7 +653,8 @@ def book_report(identity, weights, rebalances, fee): "metric_report": compute_historical_research_metrics( ledger, input_binding=source_binding, annual_risk_free_rate=annual_risk_free_rate, annual_minimum_acceptable_return=annual_minimum_acceptable_return, - include_unified_net_report=include_unified_net_report)} + include_unified_net_report=include_unified_net_report, + include_net_path_diagnostics=include_net_path_diagnostics)} comparisons = [] by_id = {row["baseline_id"]: row for row in legacy["c3_comparison"]["baselines"]} diff --git a/src/us_equity_strategies/research/tqqq_core_trial_journal.py b/src/us_equity_strategies/research/tqqq_core_trial_journal.py index dab447b..9642225 100644 --- a/src/us_equity_strategies/research/tqqq_core_trial_journal.py +++ b/src/us_equity_strategies/research/tqqq_core_trial_journal.py @@ -101,6 +101,7 @@ def compute_historical_research_metrics( annual_risk_free_rate: float, annual_minimum_acceptable_return: float, start_session: date | None = None, end_session: date | None = None, include_unified_net_report: bool = False, + include_net_path_diagnostics: bool = False, ) -> dict: """Report historical modeled sleeves; binding is not PIT/OOS qualification. @@ -121,7 +122,8 @@ def compute_historical_research_metrics( annual_risk_free_rate=annual_risk_free_rate, annual_minimum_acceptable_return=annual_minimum_acceptable_return, start_session=start_session, end_session=end_session, - include_unified_net_report=include_unified_net_report) + include_unified_net_report=include_unified_net_report, + include_net_path_diagnostics=include_net_path_diagnostics) report.update({"data_qualified": False, "oos_qualified": False, "evidence_scope": "HISTORICAL_SOURCE_SEEN_DEVELOPMENT_MODELED_SLEEVES", "input_binding": json.loads(json.dumps(input_binding, allow_nan=False))}) @@ -150,16 +152,35 @@ def _unified_net_report_builder(): return builder +def _net_path_diagnostics_builder(): + """Load the optional pure research capability only after explicit opt-in.""" + name = "quant_platform_kit.research_stats.net_path_diagnostics" + try: + module = importlib.import_module(name) + except ModuleNotFoundError as exc: + if exc.name not in {"quant_platform_kit.research_stats", name}: + raise + raise ValueError("NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE") from None + builder = getattr(module, "build_net_path_diagnostics", None) + if not callable(builder): + raise ValueError("NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE") + return builder + + def _compute_account_metrics( ledger: ResearchDailyLedger, *, calendar_id: str, contract_version: str, annual_risk_free_rate: float, annual_minimum_acceptable_return: float, start_session: date | None, end_session: date | None, include_unified_net_report: bool = False, + include_net_path_diagnostics: bool = False, ) -> dict: """One arithmetic body shared by the explicit report-only input contracts.""" if type(include_unified_net_report) is not bool: raise ValueError("UNIFIED_NET_REPORT_OPT_IN_INVALID") + if type(include_net_path_diagnostics) is not bool: + raise ValueError("NET_PATH_DIAGNOSTICS_OPT_IN_INVALID") unified_builder = _unified_net_report_builder() if include_unified_net_report else None + path_builder = _net_path_diagnostics_builder() if include_net_path_diagnostics else None if ledger.calendar_id != calendar_id or ledger.periods_per_year != 252: raise ValueError("RESEARCH_BASIS_INVALID") rates = (annual_risk_free_rate, annual_minimum_acceptable_return) @@ -265,6 +286,19 @@ def _compute_account_metrics( "legacy_rf_adjusted_annualized_metrics_replaced": False, "inference_parameters_role": "ESTIMATOR_DEFAULTS_NOT_ACCEPTANCE_THRESHOLDS", } + if path_builder is not None: + report["net_path_diagnostics"] = path_builder(returns).to_dict() + report["net_path_diagnostics_basis"] = { + "metric_role": "REPORT_ONLY", "return_basis": "RAW_NET", + "observation_unit": "STRATEGY_ACCOUNT_SESSION_SIMPLE_NET_RETURN", + "frequency": "RETURN_OBSERVATIONS_NOT_CALENDAR_TIME_OR_TRADES", + "calendar_id": calendar_id, "real_calendar_verified": False, + "start_session": start.isoformat(), "end_session": end.isoformat(), + "zero_return_sessions": "INCLUDED", "initial_nav_is_return_observation": False, + "initial_nav": "WINDOW_NORMALIZED_NOT_ACCOUNT_BALANCE", + "costs_deducted_again": False, "prior_history": "UNKNOWN", + "legacy_metrics_replaced": False, + } return report diff --git a/tests/test_c3_batch_a_existing_member_baseline.py b/tests/test_c3_batch_a_existing_member_baseline.py index 1b45e22..937da15 100644 --- a/tests/test_c3_batch_a_existing_member_baseline.py +++ b/tests/test_c3_batch_a_existing_member_baseline.py @@ -908,3 +908,195 @@ def test_unified_api_requires_explicit_boolean_before_source_replay(value): report_historical_batch_a_baselines(frozen_member_pack={}, soxl_snapshot=None, tqqq_snapshot=None, annual_risk_free_rate=0.0, annual_minimum_acceptable_return=0.0, include_unified_net_report=value) + + +def _net_path_capability(): + name = "quant_platform_kit.research_stats.net_path_diagnostics" + try: + module = importlib.import_module(name) + except ModuleNotFoundError as exc: + if exc.name not in {"quant_platform_kit.research_stats", name}: + raise + builder = None + else: + builder = getattr(module, "build_net_path_diagnostics", None) + if not callable(builder): + builder = None + if os.environ.get("UES_REQUIRE_NET_PATH_DIAGNOSTICS") == "1": + assert builder is not None, "Required net-path capability unavailable" + return builder + + +def test_net_path_flag_requires_historical_mode_before_input_read(tmp_path, capsys, monkeypatch): + monkeypatch.setattr(Path, "read_text", lambda *a, **k: pytest.fail("wrong mode read input")) + assert batch_a_cli.main(["--include-net-path-diagnostics", "--frozen-member-pack", + str(tmp_path / "private.json")]) == 2 + output = capsys.readouterr().out + assert json.loads(output)["reason_codes"] == ["HISTORICAL_REPORT_OPT_IN_REQUIRED"] + assert str(tmp_path) not in output + + +@pytest.mark.parametrize("missing_kind", ["missing_module", "missing_builder"]) +def test_net_path_capability_refused_before_input_read(tmp_path, capsys, monkeypatch, missing_kind): + from types import SimpleNamespace + from us_equity_strategies.research import tqqq_core_trial_journal as journal + original = importlib.import_module + name = "quant_platform_kit.research_stats.net_path_diagnostics" + + def unavailable(requested, *args, **kwargs): + if requested == name: + if missing_kind == "missing_module": + raise ModuleNotFoundError("PRIVATE_DEPENDENCY_MARKER", name=name) + return SimpleNamespace() + return original(requested, *args, **kwargs) + + monkeypatch.setattr(journal.importlib, "import_module", unavailable) + monkeypatch.setattr(Path, "read_text", lambda *a, **k: pytest.fail("missing capability read input")) + assert batch_a_cli.main(["--historical-account-report", "--include-net-path-diagnostics", + "--frozen-member-pack", str(tmp_path / "private.json")]) == 2 + output = capsys.readouterr().out + assert json.loads(output)["reason_codes"] == ["NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE"] + assert "PRIVATE_DEPENDENCY_MARKER" not in output and str(tmp_path) not in output + + +def test_net_path_default_is_lazy_and_preserves_existing_report(historical_sources, capsys, monkeypatch): + from us_equity_strategies.research import tqqq_core_trial_journal as journal + expected = json.loads(json.dumps(_historical_result(historical_sources))) + monkeypatch.setattr(journal, "_net_path_diagnostics_builder", + lambda: pytest.fail("default touched optional net-path capability")) + assert batch_a_cli.main(_historical_cli_args(historical_sources)) == 0 + result = json.loads(capsys.readouterr().out) + assert result == expected + assert "net_path_diagnostics" not in json.dumps(result) + + +def test_net_path_actual_cli_31_books_selected_returns_and_legacy_preserved(historical_sources, capsys, monkeypatch): + from us_equity_strategies.research import tqqq_core_trial_journal as journal + builder = _net_path_capability() + baseline = json.loads(json.dumps(_historical_result(historical_sources))) + before = {p: p.read_bytes() for p in historical_sources[0].rglob("*") if p.is_file()} + captured = [] + if builder is not None: + def actual(net): + captured.append(tuple(net)) + return builder(net) + monkeypatch.setattr(journal, "_net_path_diagnostics_builder", lambda: actual) + code = batch_a_cli.main([*_historical_cli_args(historical_sources), "--include-net-path-diagnostics"]) + output = capsys.readouterr().out + current = json.loads(output) + if builder is None: + assert code == 2 and current["reason_codes"] == ["NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE"] + assert "historical_account_report" not in current + return + assert code == 0 + report = current["historical_account_report"] + reports = [book["metric_report"] for row in report["baselines"] for book in row["books"].values()] + reports += [book["metric_report"] for book in report["standalone"].values()] + assert len(captured) == len(reports) == 31 + for item in reports: + path = item.pop("net_path_diagnostics") + basis = item.pop("net_path_diagnostics_basis") + assert path["schema_version"] == "qsl.net_path_diagnostics.v1" + assert path["status"] == "DEVELOPMENT_ONLY" and path["purpose"] == "REPORT_ONLY" + assert path["no_order"] is True and path["live_ready"] is False + assert path["observations"] == item["observation_count"] + assert path["unit"] == "return_observations" and path["prior_history"] == "unknown" + assert basis["costs_deducted_again"] is False + assert basis["initial_nav_is_return_observation"] is False + assert basis["start_session"] == item["start_session"] and basis["end_session"] == item["end_session"] + assert basis["real_calendar_verified"] is False + assert "unified_net_report" not in item + current.pop("evidence_digest") + baseline.pop("evidence_digest") + assert current == baseline + assert all(len(net) == reports[0]["observation_count"] for net in captured) + assert before == {p: p.read_bytes() for p in historical_sources[0].rglob("*") if p.is_file()} + for private in (str(historical_sources[0]), '"positions"', '"daily_returns"', '"net_returns"'): + assert private not in output + + +@pytest.mark.parametrize("navs,expected", [ + ((100.0, 50.0, 50.0, 100.0), (2, 3, 0, 1, False)), + ((100.0, 90.0, 81.0, 81.0), (3, None, 3, 2, True)), + ((100.0, 100.0, 100.0), (0, None, 0, 0, False)), + ((100.0, 150.0, 75.0, 75.0, 150.0), (2, 3, 0, 1, False)), +]) +def test_net_path_hand_math_and_censoring(navs, expected): + from us_equity_strategies.research.tqqq_core_trial_journal import compute_historical_research_metrics + ledger = _unified_hand_ledger(navs) + kwargs = {"input_binding": {"schema": "qsl.batch-a-historical-input-binding.v1", "pack_digest": "a" * 64}, + "annual_risk_free_rate": 0.0, "annual_minimum_acceptable_return": 0.0, + "include_net_path_diagnostics": True} + if _net_path_capability() is None: + with pytest.raises(ValueError, match="^NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE$"): + compute_historical_research_metrics(ledger, **kwargs) + return + result = compute_historical_research_metrics(ledger, **kwargs) + path = result["net_path_diagnostics"] + metrics = {m["name"]: m for m in path["metrics"]} + names = ("longest_underwater_run_observations", "longest_recovered_drawdown_duration_observations", + "terminal_underwater_run_observations", "longest_negative_net_return_run_observations") + for name, number in zip(names, expected[:4], strict=True): + assert metrics[name]["value"] == number + if number is None: + assert metrics[name]["status"] == "UNCOMPUTABLE" + assert metrics[name]["reason_code"] == "no_recovered_drawdown" + else: + assert metrics[name]["status"] == "COMPUTED" + assert metrics[names[2]]["details"]["right_censored"] is expected[4] + assert path["observations"] == len(navs) - 1 + assert path["scope"] == "window_only" and path["prior_history"] == "unknown" + json.dumps(result, allow_nan=False) + + +def test_net_path_exact_window_does_not_reconstruct_pre_window_peak(): + from us_equity_strategies.research.tqqq_core_trial_journal import compute_historical_research_metrics + ledger = _unified_hand_ledger((100.0, 50.0, 50.0, 100.0)) + kwargs = {"input_binding": {"schema": "qsl.batch-a-historical-input-binding.v1", "pack_digest": "a" * 64}, + "annual_risk_free_rate": 0.0, "annual_minimum_acceptable_return": 0.0, + "start_session": ledger.days[1].session_date, "end_session": ledger.days[2].session_date, + "include_net_path_diagnostics": True} + if _net_path_capability() is None: + with pytest.raises(ValueError, match="^NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE$"): + compute_historical_research_metrics(ledger, **kwargs) + return + result = compute_historical_research_metrics(ledger, **kwargs) + path = result["net_path_diagnostics"] + metrics = {m["name"]: m for m in path["metrics"]} + assert path["observations"] == result["observation_count"] == 2 + assert metrics["longest_underwater_run_observations"]["value"] == 0 + assert metrics["longest_recovered_drawdown_duration_observations"]["value"] is None + assert path["prior_history"] == "unknown" + + +@pytest.mark.parametrize("value", [None, 1, "true"]) +def test_net_path_api_requires_explicit_boolean_before_replay(value): + from us_equity_strategies.research.c3_batch_a_existing_member_baseline import report_historical_batch_a_baselines + with pytest.raises(ValueError, match="^NET_PATH_DIAGNOSTICS_OPT_IN_INVALID$"): + report_historical_batch_a_baselines(frozen_member_pack={}, soxl_snapshot=None, tqqq_snapshot=None, + annual_risk_free_rate=0.0, annual_minimum_acceptable_return=0.0, + include_net_path_diagnostics=value) + + +def test_net_path_and_unified_sidecars_are_independent_and_keep_twelve_metrics(historical_sources, capsys): + code = batch_a_cli.main([*_historical_cli_args(historical_sources), + "--include-unified-net-report", "--include-net-path-diagnostics"]) + result = json.loads(capsys.readouterr().out) + if _unified_capability() is None or _net_path_capability() is None: + assert code == 2 + assert result["reason_codes"][0] in { + "UNIFIED_NET_REPORT_CAPABILITY_UNAVAILABLE", "NET_PATH_DIAGNOSTICS_CAPABILITY_UNAVAILABLE"} + assert "historical_account_report" not in result + return + assert code == 0 + report = result["historical_account_report"] + books = [book for row in report["baselines"] for book in row["books"].values()] + books += list(report["standalone"].values()) + assert len(books) == 31 + for book in books: + metrics = book["metric_report"] + assert len(metrics["unified_net_report"]["metrics"]) == 12 + assert len(metrics["net_path_diagnostics"]["metrics"]) == 4 + assert metrics["unified_net_report"]["observations"] == metrics["net_path_diagnostics"]["observations"] + assert metrics["unified_net_report"]["no_order"] is metrics["net_path_diagnostics"]["no_order"] is True + assert metrics["unified_net_report"]["live_ready"] is metrics["net_path_diagnostics"]["live_ready"] is False