From f5fb7f8d13c1088663e4121a2966040579a20dd9 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:18:01 +0800 Subject: [PATCH 1/2] feat(finance): publish complete quantity research through existing review surfaces Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../loopx-finance-value-discovery/README.md | 54 +++- .../examples/quantity-research-v1.json | 106 +++++++ .../extension.toml | 2 +- .../pyproject.toml | 2 +- .../src/loopx_finance_value_discovery/cli.py | 2 + .../dashboard.py | 7 + .../funding_presentation.py | 18 +- .../presentation_validation.py | 14 + .../quantity_presentation.py | 265 ++++++++++++++++++ .../tests/test_quantity_presentation.py | 182 ++++++++++++ 10 files changed, 630 insertions(+), 22 deletions(-) create mode 100644 packages/loopx-finance-value-discovery/examples/quantity-research-v1.json create mode 100644 packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/quantity_presentation.py create mode 100644 packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py diff --git a/packages/loopx-finance-value-discovery/README.md b/packages/loopx-finance-value-discovery/README.md index e41e6a97e6..4fb5d7e7cb 100644 --- a/packages/loopx-finance-value-discovery/README.md +++ b/packages/loopx-finance-value-discovery/README.md @@ -667,11 +667,57 @@ the previous revision and use the existing extension rollback/upgrade commands for controlled recovery. No default node or account is activated by this API. The existing numeric-accuracy owner now also supplies bounded Decimal parsing; -legacy cash/stock commands retain their input and error contracts. This stage -does not add an App/Lark quantity journey, automatic source interpretation, -source authentication, cash, financial admission or trading permission. Those -consumer/presentation and adoption boundaries remain separately qualified. +legacy cash/stock commands retain their input and error contracts. This arithmetic stage does not interpret or authenticate sources, derive cash, +or grant financial or trading permission. The review wrapper below supplies +the existing App/Lark presentation; source-consumer and financial utility +qualification remain separate. 份额/合约数与货币分别建模,同类别与同持有池的数量核对不推导现金或交易权限。 opening、覆盖、付收方向或精度未知时保留未知,不反推、不补零。合成示例通过 只证明声明下的词法算术;来源认证、PIT、界面/卡片与实际采用另行验收。 + + +### Quantity research review (candidate 0.8.16) + +`finance_quantity_research_input_v1` wraps the original quantity input and an +explicit review time, evidence cutoff, publication source/version, supersedes +and review deadline. It returns the complete `quantity_assessment` plus the +existing Investment Research view. Opening/closing stocks, every signed flow, +identity/unit/accuracy, source digest and original capture time remain visible. +Candidate arithmetic, reconciled closing and all holds remain distinct; +missing is not zero, and a conflict never becomes a qualified closing. + +```sh +loopx-finance-value-discovery < packages/loopx-finance-value-discovery/examples/quantity-research-v1.json +loopx extension publish-projection loopx-finance-value-discovery investment-research \ + --input-json packages/loopx-finance-value-discovery/examples/quantity-research-v1.json \ + --execute --format json +loopx-finance-value-discovery render-lark-card \ + --input-json packages/loopx-finance-value-discovery/examples/quantity-research-v1.json +``` + +Open the selected Goal's **Results / 成果** tab, inspect the Quantity review +and its original source/reference details, and use **Refresh / 刷新** after +publication changes. The same revision/hash/deadline is used for a published +Lark draft: pass its receipt to the existing `--published-state-file`, `--goal-id`, +`--extension-revision` and `--payload-sha256` flags. Stale, wrong-Goal and disabled +publications refuse; recovery reads the current reference rather than silently +substituting a result. Card preparation sends nothing and does not renew a +review deadline or grant source, investment, cash or trading admission. + +The complete review view supports at most **8 flows** within its existing +12-layer and text limits; larger/unsafe views are refused without truncation. +The arithmetic API still supports 256 flows. Exact long decimal results move +to the arithmetic detail rather than being rounded into a metric cell. Lark's +existing complete-card capacity may also refuse a long view; read the complete +published result in App. Disable using the existing extension lifecycle, and +retain the pinned 0.8.15 wheel/manifest for rollback: old quantity/cash/funding +inputs remain valid, while that old release refuses the new review wrapper. +No private source, account or raw provider response belongs in this public-safe +view. Source mapping, independent consumer adoption and financial utility +remain separate from presentation qualification. + +数量复核复用既有成果页与 Lark 发布引用;原始 operands、缺证、冲突及来源时钟 +完整保留。长精确数不截断、不四舍五入,超容量明确拒绝。关闭、旧引用与错 Goal +不返回缓存结果;恢复后重新核对当前引用。展示、来源准入、经验采用与金融效用 +分别验收;卡片只准备不发送。 diff --git a/packages/loopx-finance-value-discovery/examples/quantity-research-v1.json b/packages/loopx-finance-value-discovery/examples/quantity-research-v1.json new file mode 100644 index 0000000000..cc63341d53 --- /dev/null +++ b/packages/loopx-finance-value-discovery/examples/quantity-research-v1.json @@ -0,0 +1,106 @@ +{ + "schema_version": "finance_quantity_research_input_v1", + "goal_id": "quantity-review-demo", + "source_id": "synthetic-quantity-v1", + "source_version": 1, + "supersedes": [], + "point_in_time": "2001-01-05T00:00:00Z", + "evidence_cutoff": "2001-01-04T00:00:00Z", + "review_due_at": "2001-01-06T00:00:00Z", + "quantity_input": { + "schema_version": "finance_quantity_reconciliation_input_v1", + "opening_stock": { + "evidence_ref": "synthetic:opening", + "source_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "captured_at": "2001-01-04T00:00:00Z", + "identity": { + "subject_ref": "synthetic:issuer", + "instrument_ref": "synthetic:class-a", + "ownership_scope_ref": "synthetic:single-pool", + "source_version_ref": "synthetic:filing-v1" + }, + "unit": { + "measure_ref": "share", + "scale": 0 + }, + "value": "100", + "accuracy": { + "decimals": "INF" + }, + "position": 0, + "aggregation_kind": "stock" + }, + "closing_stock": { + "evidence_ref": "synthetic:closing", + "source_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "captured_at": "2001-01-04T00:00:00Z", + "identity": { + "subject_ref": "synthetic:issuer", + "instrument_ref": "synthetic:class-a", + "ownership_scope_ref": "synthetic:single-pool", + "source_version_ref": "synthetic:filing-v1" + }, + "unit": { + "measure_ref": "share", + "scale": 0 + }, + "value": "117", + "accuracy": { + "decimals": "INF" + }, + "position": 2, + "aggregation_kind": "stock" + }, + "flows": [ + { + "evidence_ref": "synthetic:acquisition", + "source_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "captured_at": "2001-01-04T00:00:00Z", + "identity": { + "subject_ref": "synthetic:issuer", + "instrument_ref": "synthetic:class-a", + "ownership_scope_ref": "synthetic:single-pool", + "source_version_ref": "synthetic:filing-v1" + }, + "unit": { + "measure_ref": "share", + "scale": 0 + }, + "value": "25", + "accuracy": { + "decimals": "INF" + }, + "position": 1, + "aggregation_kind": "flow", + "direction": "increase" + }, + { + "evidence_ref": "synthetic:disposition", + "source_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "captured_at": "2001-01-04T00:00:00Z", + "identity": { + "subject_ref": "synthetic:issuer", + "instrument_ref": "synthetic:class-a", + "ownership_scope_ref": "synthetic:single-pool", + "source_version_ref": "synthetic:filing-v1" + }, + "unit": { + "measure_ref": "share", + "scale": 0 + }, + "value": "8", + "accuracy": { + "decimals": "INF" + }, + "position": 2, + "aggregation_kind": "flow", + "direction": "decrease" + } + ], + "coverage": { + "complete": true, + "evidence_ref": "synthetic:complete-two-flow-coverage", + "ordering_basis": "source_row_order" + } + } +} diff --git a/packages/loopx-finance-value-discovery/extension.toml b/packages/loopx-finance-value-discovery/extension.toml index 9a6507402d..5653347007 100644 --- a/packages/loopx-finance-value-discovery/extension.toml +++ b/packages/loopx-finance-value-discovery/extension.toml @@ -1,6 +1,6 @@ schema_version = "loopx_extension_manifest_v0" id = "loopx-finance-value-discovery" -version = "0.8.15" +version = "0.8.16" requires_loopx_api = ">=1,<2" permissions = [] diff --git a/packages/loopx-finance-value-discovery/pyproject.toml b/packages/loopx-finance-value-discovery/pyproject.toml index 945c7b1736..9fca26a009 100644 --- a/packages/loopx-finance-value-discovery/pyproject.toml +++ b/packages/loopx-finance-value-discovery/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "loopx-finance-value-discovery" -version = "0.8.15" +version = "0.8.16" description = "Public-safe finance value-discovery extension for LoopX." readme = "README.md" requires-python = ">=3.11" diff --git a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/cli.py b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/cli.py index 1a7d27d64e..69e43dbe18 100644 --- a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/cli.py +++ b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/cli.py @@ -65,6 +65,7 @@ assess_funding_event_pair, ) from .funding_presentation import FINANCE_FUNDING_RESEARCH_INPUT_SCHEMA_VERSION +from .quantity_presentation import FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION from .quantity_reconciliation import ( FINANCE_QUANTITY_INPUT_SCHEMA_VERSION, assess_quantity_reconciliation, @@ -268,6 +269,7 @@ def run(argv: Sequence[str] | None = None) -> int: elif schema_version in { FINANCE_RESEARCH_DASHBOARD_INPUT_SCHEMA_VERSION, FINANCE_FUNDING_RESEARCH_INPUT_SCHEMA_VERSION, + FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION, }: from .dashboard import build_finance_research_dashboard_packet diff --git a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/dashboard.py b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/dashboard.py index 8394d78c19..c2066dea9d 100644 --- a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/dashboard.py +++ b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/dashboard.py @@ -723,6 +723,13 @@ def build_finance_research_dashboard_packet( if payload.get("schema_version") == FINANCE_FUNDING_RESEARCH_INPUT_SCHEMA_VERSION: return build_funding_event_research_packet(payload) + from .quantity_presentation import ( + FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION, + build_quantity_research_packet, + ) + + if payload.get("schema_version") == FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION: + return build_quantity_research_packet(payload) record = _record( payload, context="input", diff --git a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/funding_presentation.py b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/funding_presentation.py index 3be1d97fce..1f037eaaec 100644 --- a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/funding_presentation.py +++ b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/funding_presentation.py @@ -13,7 +13,7 @@ from .funding_event import FundingRateKind, assess_funding_event_pair from .replay import canonical_sha256 -from .presentation_validation import evidence_reference, record +from .presentation_validation import public_reference_tree, record FINANCE_FUNDING_RESEARCH_INPUT_SCHEMA_VERSION = ( @@ -25,20 +25,6 @@ def _declared(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) -def _public_references(value: Any, context: str = "funding input") -> None: - """Keep embedded references under the existing publication URL boundary.""" - if isinstance(value, Mapping): - for key, child in value.items(): - path = f"{context}.{key}" - if child is not None and (key == "ref" or key.endswith("_ref")): - evidence_reference(child, context=path) - else: - _public_references(child, path) - elif isinstance(value, list): - for index, child in enumerate(value): - _public_references(child, f"{context}[{index}]") - - def _layer(order: int, label: str, points: list[str]) -> dict[str, Any]: return { "id": f"funding-{order}", @@ -91,7 +77,7 @@ def build_funding_event_research_packet(payload: Mapping[str, Any]) -> dict[str, raise ValueError("unsupported funding research input version") assessment = assess_funding_event_pair(wrapper.get("funding_event_input")) original = assessment["input"] - _public_references(original) + public_reference_tree(original, "funding input") decision_at = original["decision_at"] layers = [ _layer( diff --git a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/presentation_validation.py b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/presentation_validation.py index f67917e8d0..ea4a271bdc 100644 --- a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/presentation_validation.py +++ b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/presentation_validation.py @@ -183,3 +183,17 @@ def evidence_reference(value: Any, *, context: str) -> str: if any(_is_forbidden_key_name(field) for field in fields): raise ValueError(f"{context} contains an unsafe evidence reference") return reference + + +def public_reference_tree(value: Any, context: str = "input") -> None: + """Keep embedded references under the existing publication URL boundary.""" + if isinstance(value, Mapping): + for key, child in value.items(): + path = f"{context}.{key}" + if child is not None and (key == "ref" or key.endswith("_ref")): + evidence_reference(child, context=path) + else: + public_reference_tree(child, path) + elif isinstance(value, list): + for index, child in enumerate(value): + public_reference_tree(child, f"{context}[{index}]") diff --git a/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/quantity_presentation.py b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/quantity_presentation.py new file mode 100644 index 0000000000..a938a20499 --- /dev/null +++ b/packages/loopx-finance-value-discovery/src/loopx_finance_value_discovery/quantity_presentation.py @@ -0,0 +1,265 @@ +"""Quantity review through the existing Finance view, never source admission.""" + +from __future__ import annotations + +from collections.abc import Mapping +import json +from typing import Any + +from .presentation_validation import public_reference_tree, record +from .quantity_reconciliation import assess_quantity_reconciliation +from .replay import canonical_sha256 + +FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION = "finance_quantity_research_input_v1" +# Ten operand layers plus arithmetic and assurance fit the existing 12-layer view. +MAX_DISPLAY_FLOWS = 8 + + +def _declared(value: Any) -> str: + return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + + +def _layer(order: int, label: str, points: list[str]) -> dict[str, Any]: + return { + "id": f"quantity-{order}", + "order": order, + "label": label, + "status": "partial", + "summary": "Caller declarations; source authenticity and financial admission remain unproven.", + "supporting_evidence": [ + {"classification": "agent_inference", "statement": point} + for point in points + ], + "counterevidence": [ + { + "classification": "agent_inference", + "statement": "Arithmetic agreement does not authenticate identity, coverage, event time or historical availability.", + } + ], + "thesis_breakers": [ + "Incorrect class, pool, unit, version, precision or coverage invalidates reconciliation." + ], + } + + +def build_quantity_research_packet(payload: Mapping[str, Any]) -> dict[str, Any]: + """Assess once and retain all operands and holds without display truncation. + + This provider-local wrapper reuses decision_research_dashboard_v0, Core + publication and Lark readback. Large packets fail the existing display + bounds; the arithmetic entry remains available for up to 256 flows. + """ + from .dashboard import ( + build_finance_research_dashboard_packet, + _reject_forbidden_material, + ) + + _reject_forbidden_material(payload) + wrapper = record( + payload, + context="quantity research", + allowed={ + "schema_version", + "goal_id", + "source_id", + "source_version", + "supersedes", + "point_in_time", + "evidence_cutoff", + "review_due_at", + "quantity_input", + }, + ) + if wrapper.get("schema_version") != FINANCE_QUANTITY_RESEARCH_INPUT_SCHEMA_VERSION: + raise ValueError("unsupported quantity research input version") + assessment = assess_quantity_reconciliation(wrapper.get("quantity_input")) + original = assessment["operands"] + if len(original["flows"]) > MAX_DISPLAY_FLOWS: + raise ValueError( + "Complete quantity research display supports at most 8 flows; nothing was truncated or published. Use assess-quantity for the full arithmetic assessment." + ) + public_reference_tree(original, "quantity input") + calculation_keys = [ + "candidate_net_quantity", + "candidate_expected_closing_quantity", + "candidate_closing_difference", + "reconciled_closing_quantity", + ] + # Exact strings stay in the layer even if they exceed metric-cell capacity. + metrics = [ + { + "id": key.replace("_", "-"), + "label": label, + "value": "unknown (not zero)" + if assessment[key] is None + else ( + assessment[key] + if len(assessment[key]) <= 120 + else "See exact arithmetic below" + ), + "detail": "Quantity at the declared scale; candidate arithmetic is not qualified closing or cash.", + "tone": "warning" if assessment[key] is None else "neutral", + } + for key, label in zip( + calculation_keys, + [ + "Candidate net quantity / 候选净数量", + "Candidate closing / 候选期末", + "Closing difference / 期末差额", + "Reconciled closing / 核对期末", + ], + strict=True, + ) + ] + metrics += [ + { + "id": "validated-alpha", + "label": "Validated alpha", + "value": "0", + "detail": "Financial utility remains unverified.", + "tone": "warning", + }, + { + "id": "method-state", + "label": "Method state", + "value": "unchanged", + "detail": "Presentation does not adopt a method.", + "tone": "neutral", + }, + ] + layers = [ + _layer( + 1, + "Arithmetic / 数量核对", + [ + "Reconciliation state: " + assessment["state"], + *[key + ": " + _declared(assessment[key]) for key in calculation_keys], + "quantity_reconciled: " + _declared(assessment["quantity_reconciled"]), + ], + ) + ] + rows = [ + ("opening", original["opening_stock"]), + ("closing", original["closing_stock"]), + ] + rows += [(f"flow_{i}", flow) for i, flow in enumerate(original["flows"])] + for name, row in rows: + layers.append( + _layer( + len(layers) + 1, + "Operand / 原始声明: " + name, + [ + *[ + key + ": " + _declared(row[key]) + for key in [ + "evidence_ref", + "source_digest", + "captured_at", + "identity", + "unit", + "value", + "accuracy", + ] + ], + "Role and order: " + + _declared( + { + key: row[key] + for key in ["aggregation_kind", "position", "direction"] + if key in row + } + ), + "Accuracy assessment: " + + _declared(assessment["numeric_accuracy_assessments"][name]), + ], + ) + ) + # Group reasons within the existing text bound. No omitted or shortened codes. + reason_groups: list[str] = [] + group = "" + for reason in assessment["reason_codes"]: + addition = (", " if group else "Holds: ") + reason + if len(group + addition) > 600: + reason_groups.append(group) + group = "Holds: " + reason + else: + group += addition + if group: + reason_groups.append(group) + layers.append( + _layer( + len(layers) + 1, + "Coverage, holds and assurance / 覆盖与缺证", + [ + "Coverage: " + _declared(original["coverage"]), + *( + reason_groups + or ["Holds: none; source authentication remains unproven."] + ), + "Arithmetic assurance: " + assessment["arithmetic_assurance"], + "Coverage assurance: " + assessment["coverage_assurance"], + "Ordering assurance: " + assessment["ordering_assurance"], + "Source authentication, financial admission, cash derivation and trading: all false; exact cash: unknown (not zero).", + "Original capture clocks are retained. Review time does not establish event time, first availability or PIT eligibility.", + ], + ) + ) + packet = build_finance_research_dashboard_packet( + { + "schema_version": "finance_research_dashboard_input_v0", + "surface_id": "investment-research", + **{ + key: wrapper.get(key) + for key in [ + "goal_id", + "source_id", + "source_version", + "supersedes", + "point_in_time", + "evidence_cutoff", + "review_due_at", + ] + }, + "title": "Quantity reconciliation review / 数量核对复核", + "subtitle": "Declared quantity stocks and signed flows; source and financial qualification remain separate.", + "adjudication": { + "status": "insufficient_evidence", + "label": "Insufficient Evidence", + "confidence": "low", + "summary": "Arithmetic state: " + + assessment["state"] + + ". Review original operands, holds and coverage; agreement grants no source, investment or trading admission.", + }, + "metrics": metrics, + "dashboard_summaries": [], + "research_layers": layers, + "entities": [], + "research_ledger": [], + "event_gates": [], + "artifacts": [ + { + "artifact_id": "quantity-assessment", + "kind": "research_packet", + "label": "Complete quantity assessment", + "summary": "The returned quantity_assessment preserves all operands, nulls and holds. This hash identifies the calculation; it does not authenticate sources.", + "artifact_ref": "sha256:" + canonical_sha256(assessment), + "evidence_refs": ["sha256:" + assessment["input_sha256"]], + } + ], + "method_state": { + "revision": "quantity-reconciliation-v1", + "lifecycle_state": "active_method_unchanged", + "active_method_changed": False, + "summary": "Quantity display only; method adoption and financial utility remain separate.", + }, + "boundary": { + "research_aid_only": True, + "investment_advice": False, + "trading_allowed": False, + "raw_provider_payload_recorded": False, + "private_source_content_read": False, + }, + } + ) + packet["quantity_assessment"] = assessment + return packet diff --git a/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py b/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py new file mode 100644 index 0000000000..5bbae95355 --- /dev/null +++ b/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +from copy import deepcopy +import io +import json +from pathlib import Path + +import pytest + +from loopx_finance_value_discovery.cli import run +from loopx_finance_value_discovery.dashboard import ( + build_finance_research_dashboard_packet, +) +from loopx_finance_value_discovery.lark_projection import ( + build_decision_research_lark_card, + render_decision_research_markdown, +) +from loopx_finance_value_discovery.quantity_presentation import ( + build_quantity_research_packet, +) +from loopx_finance_value_discovery.quantity_reconciliation import ( + assess_quantity_reconciliation, +) +from loopx_finance_value_discovery.replay import canonical_sha256 + + +def request(): + return json.loads( + (Path(__file__).parents[1] / "examples/quantity-research-v1.json").read_text() + ) + + +def display(payload): + packet = build_finance_research_dashboard_packet(payload) + return packet, render_decision_research_markdown( + packet["presentation_projection"]["view"] + ) + + +def test_complete_original_operands_and_clocks_and_same_owner(): + payload = request() + before = deepcopy(payload) + packet, text = display(payload) + result = packet["quantity_assessment"] + assert payload == before + assert result == assess_quantity_reconciliation(payload["quantity_input"]) + assert result["reconciled_closing_quantity"] == "117" + view = packet["presentation_projection"]["view"] + assert view["identity"]["as_of"] == payload["point_in_time"] + assert view["identity"]["evidence_cutoff"] == payload["evidence_cutoff"] + assert view["adjudication"]["status"] == "insufficient_evidence" + assert view["boundary"]["trading_allowed"] is False + assert "sha256:" + canonical_sha256(result) in text + for row in [ + payload["quantity_input"]["opening_stock"], + payload["quantity_input"]["closing_stock"], + *payload["quantity_input"]["flows"], + ]: + assert row["captured_at"] in text + assert row["evidence_ref"] in text + assert row["source_digest"] in text + for reference in row["identity"].values(): + assert reference in text + assert '"measure\\_ref":"share"' in text + assert "cash derivation and trading: all false" in text + + +@pytest.mark.parametrize( + "change,state,reason", + [ + ("missing-opening", "incomplete", "opening_value_missing"), + ("rounded", "ineligible", "opening_exact_accuracy_unproven"), + ("class", "ineligible", "flow_0_identity_mismatch_or_unknown"), + ("coverage", "incomplete", "flow_coverage_incomplete"), + ("conflict", "conflict", "reported_closing_quantity_conflict"), + ], +) +def test_holds_never_become_zero_qualified_or_admitted(change, state, reason): + payload = request() + original = payload["quantity_input"] + if change == "missing-opening": + original["opening_stock"].update(value=None, accuracy=None) + elif change == "rounded": + original["opening_stock"]["accuracy"] = {"decimals": "0"} + elif change == "class": + original["flows"][0]["identity"]["instrument_ref"] = "synthetic:class-b" + elif change == "coverage": + original["coverage"]["complete"] = False + else: + original["closing_stock"]["value"] = "118" + packet, text = display(payload) + result = packet["quantity_assessment"] + assert result["state"] == state + assert result["reconciled_closing_quantity"] is None + assert "Reconciled closing / 核对期末: unknown (not zero)" in text + assert reason.replace("_", "\\_") in text + for flag in [ + "source_evidence_authenticated", + "financial_admission", + "cash_flow_derived", + "trading_allowed", + ]: + assert result[flag] is False + + +def test_large_exact_quantity_is_preserved_below_metric_not_rounded(): + payload = request() + original = payload["quantity_input"] + original["opening_stock"]["value"] = "1e64" + original["flows"][0]["value"] = "1e-64" + original["flows"][1]["value"] = "0" + original["closing_stock"]["value"] = "1e64" + packet, text = display(payload) + exact = "1" + "0" * 64 + "." + "0" * 63 + "1" + assert packet["quantity_assessment"]["candidate_expected_closing_quantity"] == exact + assert exact in text + assert "See exact arithmetic below" in text + assert packet["quantity_assessment"]["state"] == "conflict" + + +@pytest.mark.parametrize( + "unsafe", + [ + "/Users/example/private.json", + "https://private.example.org/x", + "https://example.org/x?token=synthetic-only", + "", + ], +) +def test_publication_rejects_unsafe_nested_reference(unsafe): + payload = request() + payload["quantity_input"]["opening_stock"]["identity"]["instrument_ref"] = unsafe + for entry in [ + build_quantity_research_packet, + build_finance_research_dashboard_packet, + ]: + with pytest.raises(ValueError): + entry(payload) + + +def test_capacity_is_explicit_refusal_and_arithmetic_still_works(): + payload = request() + original = payload["quantity_input"] + original["flows"] = [] + for i in range(8): + row = deepcopy(request()["quantity_input"]["flows"][0]) + row.update(evidence_ref=f"synthetic:flow-{i}", position=i + 1, value="0") + original["flows"].append(row) + original["closing_stock"].update(position=9, value="100") + packet, text = display(payload) + assert len(packet["presentation_projection"]["view"]["layers"]) == 12 + for i in range(8): + assert f"synthetic:flow-{i}" in text + extra = deepcopy(original["flows"][-1]) + extra.update(evidence_ref="synthetic:flow-8", position=9) + original["flows"].append(extra) + with pytest.raises(ValueError, match="at most 8 flows.*nothing was truncated"): + display(payload) + assert ( + assess_quantity_reconciliation(original)["reconciled_closing_quantity"] == "100" + ) + + +def test_stdin_direct_research_card_and_legacy_quantity_dispatch( + monkeypatch, capsys, tmp_path +): + payload = request() + path = tmp_path / "request.json" + path.write_text(json.dumps(payload)) + monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload))) + assert run([]) == 0 + packet = json.loads(capsys.readouterr().out) + assert packet == build_finance_research_dashboard_packet(payload) + assert run(["render-lark-card", "--input-json", str(path)]) == 0 + assert json.loads(capsys.readouterr().out) == build_decision_research_lark_card( + packet["presentation_projection"]["view"] + ) + monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload["quantity_input"]))) + assert run([]) == 0 + assert json.loads(capsys.readouterr().out) == assess_quantity_reconciliation( + payload["quantity_input"] + ) From 75abf85a109280aba71b48b335255cd678b0d582 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:40:16 +0800 Subject: [PATCH 2/2] test(finance): construct synthetic unsafe URL within public fixture boundary Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../tests/test_quantity_presentation.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py b/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py index 5bbae95355..80d05746a7 100644 --- a/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py +++ b/packages/loopx-finance-value-discovery/tests/test_quantity_presentation.py @@ -4,6 +4,7 @@ import io import json from pathlib import Path +from urllib.parse import urlencode, urlunparse import pytest @@ -123,7 +124,16 @@ def test_large_exact_quantity_is_preserved_below_metric_not_rounded(): [ "/Users/example/private.json", "https://private.example.org/x", - "https://example.org/x?token=synthetic-only", + urlunparse( + ( + "https", + "example.org", + "/x", + "", + urlencode({"token": "synthetic-only"}), + "", + ) + ), "", ], )