Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions notifications/unified_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ def comparable(v):
**({"block_reason": block_reason} if block_reason else {})}
if account_snapshot is not None:
previous_snapshot = (prior or {}).get("account_snapshot", account_snapshot)
empty_stock = {"holdings_scope": "stocks_only", "holdings": [], "holdings_complete": True}
for enriched, legacy in ((account_snapshot, previous_snapshot), (previous_snapshot, account_snapshot)):
# The old archive projection dropped this explicit empty list.
if (isinstance(enriched, dict)
and all(enriched.get(key) == value for key, value in empty_stock.items())
and legacy == {key: value for key, value in enriched.items() if key not in empty_stock}):
previous_snapshot = account_snapshot = enriched
break
record["account_snapshot"] = account_snapshot if previous_snapshot == account_snapshot else None
events[fact["dedupe_key"]] = record
changed = events != old["events"]
Expand Down Expand Up @@ -910,6 +918,9 @@ def _archived_account_snapshot(report, selector, started, now):
{"symbol": row["symbol"], "quantity": float(row["quantity"]), "asset_type": "stock"}
for row in positions
])
elif positions == []:
# The account-facts projector admits an empty list only with explicit stock coverage.
snapshot.update(holdings_scope=POSITIONS_SCOPE, holdings=[], holdings_complete=True)
return snapshot if len(snapshot) > 1 else None


Expand Down
32 changes: 31 additions & 1 deletion tests/test_notification_qrs_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def archive_report(run, orders=()):
return source


def export_archive(monkeypatch, reports, *, seed_unknown=False, target=TARGET):
def export_archive(monkeypatch, reports, *, seed_unknown=False, target=TARGET, replay=None):
state, candidates = StateStore(), CandidateStore()
monkeypatch.setattr(adapter, "_state_store", lambda env=None: state)
monkeypatch.setattr(adapter, "GCSCandidateStore", lambda *args, **kwargs: candidates)
Expand All @@ -72,6 +72,8 @@ def read(args):
monkeypatch.setenv(adapter.FLAG, "true")
for key, value in env.items():
monkeypatch.setenv(key, value)
if replay:
adapter.persist_archived_reports([replay], env, now=NOW + timedelta(hours=2, minutes=21))
if reports[0]["summary"]["execution_status"] == "completed":
assert adapter.publish_monitor(kind="healthy_heartbeat", report=reports[0],
now=NOW + timedelta(hours=2, minutes=21), send=lambda: pytest.fail("healthy monitor must not send"))
Expand Down Expand Up @@ -139,6 +141,34 @@ def test_archive_exports_only_in_run_snapshots_for_the_bound_account(qrs, monkey
assert "account_daily_facts" not in rows[0]


@pytest.mark.parametrize("complete", [True, False])
def test_archive_retains_empty_stock_inventory_only_with_explicit_coverage(qrs, monkeypatch, tmp_path, complete):
source, target = archive_snapshot_report("U0000001")
source["summary"]["account_facts"].update(broker_reported_positions=[],
broker_reported_positions_complete=complete)
payload = export_archive(monkeypatch, [source], seed_unknown=True, target=target)
rows, _ = intake(qrs, tmp_path, payload)
snapshot, = rows[0]["account_daily_facts"]["snapshots"]
assert snapshot["equity"] == 125.5
if complete:
assert snapshot["holdings"] == [] and snapshot["holdings_scope"] == "stocks_only"
assert snapshot["holdings_complete"] is True
else:
assert "holdings" not in snapshot and "holdings_complete" not in snapshot


def test_empty_stock_snapshot_can_enrich_legacy_archive_observation(qrs, monkeypatch, tmp_path):
source, target = archive_snapshot_report("U0000001")
source["summary"]["account_facts"]["broker_reported_positions"] = []
enriched = deepcopy(source)
enriched["summary"]["account_facts"]["broker_reported_positions_complete"] = True
latest = export_archive(monkeypatch, [source], seed_unknown=True, target=target, replay=enriched)
rows, _ = intake(qrs, tmp_path, latest)
assert len(rows) == 1
assert rows[0]["order_count"] == 0
assert rows[0]["account_daily_facts"]["snapshots"][0]["holdings"] == []


def test_archive_snapshot_reaches_the_scheduled_account_renderer(monkeypatch):
qrs_checkout = os.environ.get("QRS_DAILY_REPORT_CHECKOUT")
if not qrs_checkout:
Expand Down
Loading