Context ------- Every broker connector needs a uniform shape so the orchestrator can fan out without knowing provider-specific details. Normalisation (GBP conversion) lives outside providers on purpose — keeping providers native-currency-emitters means we can re-normalise historical activity when HMRC rates land without re-fetching from the broker. This change ----------- - providers/base.py: Provider Protocol with `accounts()` and async `fetch(since, before)` iterator. No abstract base class — duck-typed Protocol so each concrete provider stays independent. - normaliser.py: takes a native Activity + FxCache, returns a copy with amount_gbp/fx_rate_gbp/fx_rate_source filled in. Two modes: qty*price for BUY/SELL, amount for DIVIDEND/DEPOSIT/etc. - Namespace packages for providers/, providers/parsers/, sinks/ so future modules slot in cleanly. Test plan --------- ## Automated - poetry run pytest -q → 23 passed - poetry run mypy broker_sync tests → Success: no issues found in 14 source files - poetry run ruff check . → All checks passed! ## Manual Verification Not applicable at this layer.
32 lines
834 B
Python
32 lines
834 B
Python
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from datetime import datetime
|
|
from typing import Protocol
|
|
|
|
from broker_sync.models import Account, Activity
|
|
|
|
|
|
class Provider(Protocol):
|
|
"""Broker connector surface.
|
|
|
|
Each provider implementation is responsible for fetching raw broker
|
|
data, turning it into canonical `Activity` rows (with `account_id`
|
|
matching an `Account.id` from `accounts()`), and yielding them.
|
|
|
|
GBP conversion is performed by the shared normaliser, not here —
|
|
providers emit native currency and the caller converts.
|
|
"""
|
|
|
|
name: str
|
|
|
|
def accounts(self) -> list[Account]:
|
|
...
|
|
|
|
def fetch(
|
|
self,
|
|
*,
|
|
since: datetime | None = None,
|
|
before: datetime | None = None,
|
|
) -> AsyncIterator[Activity]:
|
|
...
|