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
38 changes: 36 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,9 +928,36 @@ def account_facts_sync():
client = None
try:
client = READ_ONLY_ACCOUNT_FACTS_CLIENT_BUILDER()
expected_account = config.account_id
try:
broker_accounts = client.account_numbers()
except Exception:
broker_accounts = None
if (
isinstance(expected_account, str)
and isinstance(broker_accounts, list)
and expected_account not in broker_accounts
and len(broker_accounts) == 1
and isinstance(broker_accounts[0], str)
and broker_accounts[0]
):
# RUNTIME_TARGET account_selector may be a deployment label (e.g. platform id)
# rather than the Firstrade broker account number. For single-account brokers,
# bind the sole returned account for read-only facts publish only.
sole = broker_accounts[0]
app.logger.warning(
"account_facts_sync sole_account_fallback selector=%s sole=%s",
mask_account_id(expected_account),
mask_account_id(sole),
)
config = bind_account_facts_runtime_selector(
replace(config, account_id=None),
sole,
)
expected_account = config.account_id
observation = collect_firstrade_account_facts(
client,
expected_account=config.account_id,
expected_account=expected_account,
include_positions=False,
)
payload = build_firstrade_account_snapshot(
Expand All @@ -939,10 +966,17 @@ def account_facts_sync():
account_scope=str(runtime_target.account_scope),
)
except AccountFactsUnavailable as exc:
accounts_len = -1
try:
if client is not None:
accounts_len = len(client.account_numbers())
except Exception:
accounts_len = -1
app.logger.warning(
"account_facts_sync collect failed reason=%s expected=%s",
"account_facts_sync collect failed reason=%s expected=%s accounts_len=%s",
exc.reason_code,
mask_account_id(config.account_id or ""),
accounts_len,
)
return jsonify({"ok": False, "error": exc.reason_code}), 503
except AccountFactsPublishError as exc:
Expand Down
43 changes: 43 additions & 0 deletions tests/test_account_facts_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,46 @@ def test_collection_failure_is_safe_and_client_still_closes(monkeypatch):
assert response.get_json() == {"ok": False, "error": "balances_unavailable"}
assert client.closed is True
assert published == []


def test_sync_endpoint_falls_back_to_sole_broker_account_when_selector_unmatched(monkeypatch):
_configure(monkeypatch, FIRSTRADE_ACCOUNT="firstrade")
monkeypatch.setattr(main, "_runtime_settings", lambda: SimpleNamespace(runtime_target=SimpleNamespace(
platform_id="firstrade",
account_selector=("firstrade",),
account_scope="us",
)))

class SoleAccountClient(FakeClient):
def account_numbers(self):
return ["synthetic-native-id"]

client = SoleAccountClient()
monkeypatch.setattr(main, "READ_ONLY_ACCOUNT_FACTS_CLIENT_BUILDER", lambda: client)
collector_calls = []

def collect(_client, *, expected_account, include_positions):
collector_calls.append({"expected_account": expected_account, "include_positions": include_positions})
return {
"status": "available",
"platform": "firstrade",
"account_selector_status": "matched",
"broker_account_id": expected_account,
"observed_started_at": "2026-10-08T08:00:00+00:00",
"observed_finished_at": "2026-10-08T08:00:03+00:00",
"balances": {"currency": "USD", "provider_equity": "100.00", "cash_balance": "50.00"},
}

monkeypatch.setattr(main, "collect_firstrade_account_facts", collect)
published = []
monkeypatch.setattr(main, "publish_firstrade_account_snapshot", lambda payload, cfg: published.append((payload, cfg)))

response = main.app.test_client().post(
"/account-facts-sync",
headers={"Authorization": "Bearer synthetic-facts-token"},
)
assert response.status_code == 200
assert collector_calls == [{"expected_account": "synthetic-native-id", "include_positions": False}]
assert published[0][0]["broker_account_id"] == "synthetic-native-id"
assert published[0][1].account_id == "synthetic-native-id"
assert client.closed is True