From 05d36915b1c20f92b7ae5cf993ce4ede6c45db5f Mon Sep 17 00:00:00 2001 From: Guflly <145608489+Guflly@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:31:29 -0700 Subject: [PATCH 1/3] Classify negative cash invoices as taxes --- pytr/event.py | 10 +++------- tests/test_events.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/pytr/event.py b/pytr/event.py index 0aa8825..7cd9b7d 100644 --- a/pytr/event.py +++ b/pytr/event.py @@ -469,13 +469,9 @@ def from_dict(cls, event_dict: Dict[Any, Any]): elif eventTypeStr in ["SSP_CORPORATE_ACTION_INVOICE_CASH", "SSP_CORPORATE_ACTION_CASH"]: if subtitle == "Aufruf von Zwischenpapieren": event_type = PPEventType.SWAP - elif subtitle in [ - "Aktienprämiendividende", - "Bardividende", - "Bardividende korrigiert", - "Dividende Wahlweise", - "Tilgung", - ]: + elif eventTypeStr == "SSP_CORPORATE_ACTION_INVOICE_CASH" and value is not None and value < 0: + event_type = PPEventType.TAXES + elif subtitle_event_type_mapping.get(subtitle) is PPEventType.DIVIDEND: event_type = PPEventType.DIVIDEND else: event_type = PPEventType.TAXES diff --git a/tests/test_events.py b/tests/test_events.py index 67c5dad..2d64a23 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -2660,3 +2660,48 @@ def test_events(case): entry.setdefault("ISIN2", None) entry.setdefault("Stück2", None) assert transactions == rowtransactions + + +@pytest.fixture +def corporate_action_invoice_cash_event(): + with open(EVENTS_DIR / "bardividende.json", encoding="utf-8") as f: + return json.load(f) + + +def test_negative_corporate_action_invoice_cash_is_taxes(corporate_action_invoice_cash_event): + corporate_action_invoice_cash_event["amount"]["value"] = -2.24 + + event = Event.from_dict(corporate_action_invoice_cash_event) + + assert event.event_type is PPEventType.TAXES + + +def test_positive_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): + event = Event.from_dict(corporate_action_invoice_cash_event) + + assert event.event_type is PPEventType.DIVIDEND + + +def test_zero_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): + corporate_action_invoice_cash_event["amount"]["value"] = 0 + + event = Event.from_dict(corporate_action_invoice_cash_event) + + assert event.event_type is PPEventType.DIVIDEND + + +def test_missing_amount_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): + del corporate_action_invoice_cash_event["amount"] + + event = Event.from_dict(corporate_action_invoice_cash_event) + + assert event.event_type is PPEventType.DIVIDEND + + +def test_canceled_negative_corporate_action_invoice_cash_is_ignored(corporate_action_invoice_cash_event): + corporate_action_invoice_cash_event["amount"]["value"] = -2.24 + corporate_action_invoice_cash_event["status"] = "CANCELED" + + event = Event.from_dict(corporate_action_invoice_cash_event) + + assert event.event_type is None From 268322d0ea2a1c2653d06400b38317f1e138565a Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Sun, 9 Aug 2026 09:10:22 +0200 Subject: [PATCH 2/3] Further simplify the solution --- pytr/event.py | 6 +- tests/events/vorabpauschale_negative.json | 142 ++++++++++++++++++++++ tests/test_events.py | 61 +++------- 3 files changed, 162 insertions(+), 47 deletions(-) create mode 100644 tests/events/vorabpauschale_negative.json diff --git a/pytr/event.py b/pytr/event.py index 7cd9b7d..243090d 100644 --- a/pytr/event.py +++ b/pytr/event.py @@ -469,12 +469,10 @@ def from_dict(cls, event_dict: Dict[Any, Any]): elif eventTypeStr in ["SSP_CORPORATE_ACTION_INVOICE_CASH", "SSP_CORPORATE_ACTION_CASH"]: if subtitle == "Aufruf von Zwischenpapieren": event_type = PPEventType.SWAP - elif eventTypeStr == "SSP_CORPORATE_ACTION_INVOICE_CASH" and value is not None and value < 0: + elif value is not None and value < 0: event_type = PPEventType.TAXES - elif subtitle_event_type_mapping.get(subtitle) is PPEventType.DIVIDEND: - event_type = PPEventType.DIVIDEND else: - event_type = PPEventType.TAXES + event_type = PPEventType.DIVIDEND # Now try to deduct the event type from the title if we still don't have one if event_type is None and eventTypeStr not in events_known_ignored: diff --git a/tests/events/vorabpauschale_negative.json b/tests/events/vorabpauschale_negative.json new file mode 100644 index 0000000..3ebcbbe --- /dev/null +++ b/tests/events/vorabpauschale_negative.json @@ -0,0 +1,142 @@ +{ + "id": "61373a0b-3574-31be-855f-53edf4976c8f", + "timestamp": "2025-01-28T14:30:05.259+0000", + "title": "S&P 400 US Mid Cap (Acc)", + "icon": "logos/IE00B4YBJ215/v2", + "avatar": { + "asset": "logos/IE00B4YBJ215/v2", + "badge": null + }, + "badge": null, + "subtitle": "Vorabpauschale", + "amount": { + "currency": "EUR", + "value": -0.04, + "fractionDigits": 2 + }, + "subAmount": null, + "status": "EXECUTED", + "action": { + "type": "timelineDetail", + "payload": "61373a0b-3574-31be-855f-53edf4976c8f" + }, + "cashAccountNumber": "123456789", + "hidden": false, + "deleted": false, + "eventType": "SSP_CORPORATE_ACTION_CASH", + "source": "timelineTransaction", + "details": { + "id": "61373a0b-3574-31be-855f-53edf4976c8f", + "sections": [ + { + "title": "Du hast -0,04 € bezahlt", + "data": { + "icon": { + "asset": "logos/IE00B4YBJ215/v2", + "badge": null + }, + "subtitleText": "28 Jan. 2025 · 14:30", + "status": "executed" + }, + "type": "header" + }, + { + "title": "Übersicht", + "data": [ + { + "title": "Status", + "detail": { + "text": "Ausgeführt", + "functionalStyle": "EXECUTED", + "type": "status" + }, + "style": "plain" + }, + { + "title": "Event", + "detail": { + "text": "Vorabpauschale", + "displayValue": { + "text": "Vorabpauschale", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Wertpapier", + "detail": { + "text": "S&P 400 US Mid Cap (Acc)", + "displayValue": { + "text": "S&P 400 US Mid Cap (Acc)", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + } + ], + "type": "table" + }, + { + "title": "Geschäft", + "data": [ + { + "title": "Bruttoertrag", + "detail": { + "text": "0,00 €", + "displayValue": { + "text": "0,00 €", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Steuer", + "detail": { + "text": "-0,04 €", + "displayValue": { + "text": "-0,04 €", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Gesamt", + "detail": { + "text": "-0,04 €", + "displayValue": { + "text": "-0,04 €", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + } + ], + "type": "table" + }, + { + "title": "Dokumente", + "data": [ + { + "title": "Vorabpauschale", + "detail": "28.01.2025", + "action": { + "payload": "https://example.com/vorabpauschale.pdf", + "type": "browserModal" + }, + "id": "2e2ce1bd-6149-4ad6-9483-a57e619ca5d7", + "postboxType": "CA_VOPA_INVOICE" + } + ], + "type": "documents" + } + ] + } +} diff --git a/tests/test_events.py b/tests/test_events.py index 2d64a23..6ef0405 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -2618,6 +2618,24 @@ } ], }, + { + "filename": "vorabpauschale_negative.json", + "event_type": PPEventType.TAXES, + "title": "S&P 400 US Mid Cap (Acc)", + "isin": "IE00B4YBJ215", + "value": -0.04, + "taxes": 0.04, + "transactions": [ + { + "Datum": "2025-01-28T14:30:05", + "Typ": "Steuern", + "Wert": -0.04, + "Notiz": "S&P 400 US Mid Cap (Acc)", + "ISIN": "IE00B4YBJ215", + "Steuern": 0.04, + } + ], + }, ] @@ -2662,46 +2680,3 @@ def test_events(case): assert transactions == rowtransactions -@pytest.fixture -def corporate_action_invoice_cash_event(): - with open(EVENTS_DIR / "bardividende.json", encoding="utf-8") as f: - return json.load(f) - - -def test_negative_corporate_action_invoice_cash_is_taxes(corporate_action_invoice_cash_event): - corporate_action_invoice_cash_event["amount"]["value"] = -2.24 - - event = Event.from_dict(corporate_action_invoice_cash_event) - - assert event.event_type is PPEventType.TAXES - - -def test_positive_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): - event = Event.from_dict(corporate_action_invoice_cash_event) - - assert event.event_type is PPEventType.DIVIDEND - - -def test_zero_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): - corporate_action_invoice_cash_event["amount"]["value"] = 0 - - event = Event.from_dict(corporate_action_invoice_cash_event) - - assert event.event_type is PPEventType.DIVIDEND - - -def test_missing_amount_corporate_action_invoice_cash_is_dividend(corporate_action_invoice_cash_event): - del corporate_action_invoice_cash_event["amount"] - - event = Event.from_dict(corporate_action_invoice_cash_event) - - assert event.event_type is PPEventType.DIVIDEND - - -def test_canceled_negative_corporate_action_invoice_cash_is_ignored(corporate_action_invoice_cash_event): - corporate_action_invoice_cash_event["amount"]["value"] = -2.24 - corporate_action_invoice_cash_event["status"] = "CANCELED" - - event = Event.from_dict(corporate_action_invoice_cash_event) - - assert event.event_type is None From cda8c221392fa0278600603f3f6884899ad92d88 Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Sun, 9 Aug 2026 09:13:38 +0200 Subject: [PATCH 3/3] Whitespace --- tests/test_events.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_events.py b/tests/test_events.py index 6ef0405..13d8796 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -2678,5 +2678,3 @@ def test_events(case): entry.setdefault("ISIN2", None) entry.setdefault("Stück2", None) assert transactions == rowtransactions - -