From a45de0b710fa5180f873fdb48b9153616a25caa4 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 15 Sep 2025 18:29:19 -0400 Subject: [PATCH] ib-related: cope with invalid txn timestamps That is inside embedded `.accounting.calc.dyn_parse_to_dt()` closure add an optional `_invalid: list` param to where we can report bad-timestamped records which we instead override and return as `from_timestamp(0.)` (when the parser loop falls through) and report later (in summary ) from the `.accounting.calc.iter_by_dt()` caller . Add some logging and an optional debug block for future tracing. --- piker/accounting/calc.py | 69 +++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/piker/accounting/calc.py b/piker/accounting/calc.py index ac4d9c22..aabb3031 100644 --- a/piker/accounting/calc.py +++ b/piker/accounting/calc.py @@ -22,7 +22,9 @@ you know when you're losing money (if possible) XD from __future__ import annotations from collections.abc import ValuesView from contextlib import contextmanager as cm +from functools import partial from math import copysign +from pprint import pformat from typing import ( Any, Callable, @@ -37,12 +39,16 @@ from pendulum import ( parse, ) +from ..log import get_logger + if TYPE_CHECKING: from ._ledger import ( Transaction, TransactionLedger, ) +log = get_logger(__name__) + def ppu( clears: Iterator[Transaction], @@ -238,6 +244,9 @@ def iter_by_dt( def dyn_parse_to_dt( tx: tuple[str, dict[str, Any]] | Transaction, + + debug: bool = False, + _invalid: list|None = None, ) -> DateTime: # handle `.items()` inputs @@ -250,11 +259,16 @@ def iter_by_dt( # get best parser for this record.. for k in parsers: if ( - isdict and k in tx - or getattr(tx, k, None) + (v := getattr(tx, k, None)) + or + ( + isdict + and + (v := tx.get(k)) + ) ): - v = tx[k] if isdict else tx.dt - assert v is not None, f'No valid value for `{k}`!?' + # TODO? remove yah? + # v = tx[k] if isdict else tx.dt # only call parser on the value if not None from # the `parsers` table above (when NOT using @@ -262,21 +276,54 @@ def iter_by_dt( # sort on it directly if ( not isinstance(v, DateTime) - and (parser := parsers.get(k)) + and + (parser := parsers.get(k)) ): - return parser(v) + ret = parser(v) else: - return v + ret = v + return ret + + else: + continue + + # XXX: should never get here.. else: - # XXX: should never get here.. - breakpoint() + if debug: + import tractor + with tractor.devx.maybe_open_crash_handler(): + raise ValueError( + f'Invalid txn time ??\n' + f'txn-id: {k!r}\n' + f'{k!r}: {v!r}\n' + ) + # assert v is not None, f'No valid value for `{k}`!?' - entry: tuple[str, dict] | Transaction + if _invalid is not None: + _invalid.append(tx) + return from_timestamp(0.) + + # breakpoint() + + entry: tuple[str, dict]|Transaction + invalid: list = [] for entry in sorted( records, - key=key or dyn_parse_to_dt, + key=key or partial( + dyn_parse_to_dt, + _invalid=invalid, + ), ): + if entry in invalid: + log.warning( + f'Ignoring txn w invalid timestamp ??\n' + f'{pformat(entry)}\n' + # f'txn-id: {k!r}\n' + # f'{k!r}: {v!r}\n' + ) + continue + # NOTE the type sig above; either pairs or txns B) yield entry