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"}),
+ "",
+ )
+ ),
"",
],
)