data_updater_v2.data_clients.actions
actions.py — the corporate-action shape every client returns, and the cleanups one source makes on its own rows.
The shape (the splits / dividends tables’ columns, tables/actions.py):
split symbol · date (the first day on the new share count) · split_to · split_from · split_type (the vendor's label, None when unserved)dividend symbol · ex_date · amount (cash as paid) · period (our words: ``period_word``) · currency (None when the source states none) · declaration_date · record_date · payment_dateA cleanup never drops a row silently: it returns the rows kept and, apart, the rows it took
out ({"why": ..., "row": ...}), which the client hands on as RawResponse.dropped.
The cleanups (FMP’s rows, probed 2026-10-05/07 over every US dividend in 5 years and 373 liquid ETFs and stocks):
clean_dividends the same payout twice under two labels (same symbol, ex-date and amount: FDGFX Monthly + Quarterly, EWA Semi-Annual + Special, O Irregular + Monthly — 20 in 373 symbols, and EODHD carries each once) is one payout; on one ex-date two DIFFERENT amounts where exactly one row was declared is a correction beside the old row (OPRA 2025-07-08: 0.40 declared, 0.39 not; EODHD says 0.40) — the declared row stays. Two declared (or two undeclared) amounts on one day pass untouched: a regular and a special, or a doubt a second source settles.sound_splits any source's rows: a side zero or unread (no factor at all), a ratio of exactly one (ABEO 1998, FMP: no factor), the same split twice (every field alike) — taken out. 1,923 US names probed 2026-10-08: FMP 1 ratio of one, 0 twins, 0 zero sides; EODHD none. A fractional side (an ADR ratio change, 1.642 for 1) is a factor like any other.Functions
action_day
action_day(row: Mapping[str, Any]) -> dt.dateAn action row’s day: a dividend’s ex-date, a split’s date.
clean_dividends
clean_dividends(rows: Iterable[Mapping[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]One source’s dividend rows with its twins and its undeclared corrections taken out: (the rows kept, in their input order per day; the rows taken out, each with why).
period_word
period_word(value: Any) -> strA source’s period word in ours (‘unknown’ for none).
same_amount
same_amount(a: float | None, b: float | None) -> boolWhether two dividend amounts are the same cash, rounding apart.
sound_splits
sound_splits(rows: Iterable[Mapping[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]Any source’s split rows with the ones that carry no factor taken out: a side zero or unread, a ratio of exactly one, the same split twice (every field alike, the first kept).
split_ratio
split_ratio(row: Mapping[str, Any]) -> float | NoneA split row’s price factor, shares after per share before (2.0 for 2-for-1); None when a side is missing or zero.