fidelity-planviewer: scaffold provider + CLI (seed + stub ingest)
## Context
UK workplace pension at planviewer.fidelity.co.uk has no public API; the SPA
calls a private JSON backend at prd.wiciam.fidelity.co.uk/cvmfe/api/*. Viktor
confirmed in DevTools that an OPTIONS preflight lists auth headers
(ch, fid, rid, sid, tbid, theosreferer, ua). Full reverse-engineering of the
endpoint paths is pending Viktor's POST cURL paste for transactions +
holdings views.
Until those endpoints are captured, ship the scaffold: provider module, CLI
commands, tests, docs. This unblocks installing Playwright in the image and
lets Viktor run the one-off seed command on his laptop ahead of the data
integration.
## This change
- broker_sync/providers/fidelity_planviewer.py
- FidelityCreds namedtuple (storage_state_path, plan_id).
- FidelitySessionError (401 → re-seed), FidelityProviderConfigError.
- FidelityPlanViewerProvider: .accounts() returns a single
WORKPLACE_PENSION account, .fetch() raises until endpoints are wired.
- broker_sync/cli.py
- fidelity-seed: launches headed Chromium so Viktor can log in and tick
"Remember device", then dumps storage_state.json.
- fidelity-ingest: stub matching the invest-engine / trading212 CLI
shape; reads storage_state + plan_id, pipes through the shared pipeline.
- tests/providers/test_fidelity_planviewer.py
- Asserts the single-account shape + the loud-failure guard.
- docs/providers/fidelity-planviewer.md
- Architecture diagram, one-time seed procedure, backfill + monthly
commands, alert runbook.
- pyproject.toml
- playwright ^1.47 as a first-class dep (used only by fidelity-seed and
later by the session-refresh step in fidelity-ingest).
## What is NOT in this change
- Endpoint wiring in provider.fetch() — blocked on DevTools POST cURL.
- Infra CronJob + Vault secret + Prometheus alert — lands once the first
manual backfill succeeds and we know the Chromium image size is fine.
- Dockerfile Chromium install — same trigger.
## Verification
### Automated
$ poetry run pytest tests/providers/test_fidelity_planviewer.py -v
2 passed in 0.08s
$ poetry run pytest -q
122 passed, 1 skipped in 1.07s
$ poetry run mypy broker_sync/providers/fidelity_planviewer.py broker_sync/cli.py
Success: no issues found in 2 source files
$ poetry run ruff check broker_sync/providers/fidelity_planviewer.py broker_sync/cli.py tests/providers/test_fidelity_planviewer.py
All checks passed!
### Manual (Viktor, later)
1. poetry install && poetry run playwright install chromium
2. poetry run broker-sync fidelity-seed --out /tmp/state.json
3. Chromium opens → log in → tick "Remember device" → press Enter
4. vault kv patch secret/broker-sync fidelity_storage_state=@/tmp/state.json
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c830856ba1
commit
832732a419
6 changed files with 508 additions and 1 deletions
42
tests/providers/test_fidelity_planviewer.py
Normal file
42
tests/providers/test_fidelity_planviewer.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from broker_sync.models import Account, AccountType
|
||||
from broker_sync.providers.fidelity_planviewer import (
|
||||
ACCOUNT_ID,
|
||||
FidelityCreds,
|
||||
FidelityPlanViewerProvider,
|
||||
FidelityProviderConfigError,
|
||||
)
|
||||
|
||||
|
||||
def test_accounts_exposes_single_workplace_pension_account() -> None:
|
||||
prov = FidelityPlanViewerProvider(FidelityCreds(
|
||||
storage_state_path="/tmp/x", plan_id="ABC123",
|
||||
))
|
||||
accounts = prov.accounts()
|
||||
assert accounts == [
|
||||
Account(
|
||||
id=ACCOUNT_ID,
|
||||
name="Fidelity UK Pension",
|
||||
account_type=AccountType.WORKPLACE_PENSION,
|
||||
currency="GBP",
|
||||
provider="fidelity-planviewer",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def test_fetch_raises_until_endpoints_captured() -> None:
|
||||
"""Until Viktor pastes the transactions/holdings cURLs, fetch() must fail
|
||||
loudly rather than silently importing nothing.
|
||||
|
||||
Swap this test for real parser tests once the API shapes are known and
|
||||
`FidelityPlanViewerProvider.fetch` is wired up against fixtures.
|
||||
"""
|
||||
prov = FidelityPlanViewerProvider(FidelityCreds(
|
||||
storage_state_path="/tmp/x", plan_id="ABC123",
|
||||
))
|
||||
with pytest.raises(FidelityProviderConfigError, match="endpoint paths"):
|
||||
async for _ in prov.fetch():
|
||||
pytest.fail("fetch should not yield before endpoints are configured")
|
||||
Loading…
Add table
Add a link
Reference in a new issue