parsers/schwab: drop dead vest-release path
The _parse_vest_release path and _VEST_*_RE regexes never matched a real email in 4 years of inbox history (2022-08 → 2026-05, 188 Schwab emails surveyed). Schwab Stock Plan Services does not email release confirmations to the employee address for the workplace account — only the sell-to-cover trade-executed alert lands. Vest data must come from the META payslip via payslip-ingest (tracked as code-fqgr). Removed: - _VEST_SUBJECT_RE + 5 _VEST_*_RE regexes (heuristic, never validated) - _parse_vest_release function - VestParseResult dataclass - parse_schwab_email_full wrapper - _search_group helper (only used by vest path) - 3 dead tests + the _VEST_RELEASE fixture Kept models.VestEvent — the payslip→Wealthfolio sink in code-fqgr will need it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bb9e0d4567
commit
abf9fa7cb5
2 changed files with 13 additions and 236 deletions
|
|
@ -82,59 +82,3 @@ def test_price_with_commas_parses() -> None:
|
|||
html = _SELL.replace("$612.34", "$1,612.34")
|
||||
a = parse_schwab_email(html)[0]
|
||||
assert a.unit_price == Decimal("1612.34")
|
||||
|
||||
|
||||
# --- Vest-release parsing -------------------------------------------------
|
||||
|
||||
_VEST_RELEASE = """<html><body>
|
||||
<h2>Release Confirmation</h2>
|
||||
<p>
|
||||
Release Date: 15 Mar 2026
|
||||
Ticker: META
|
||||
Total Shares Released: 100.0
|
||||
Market Price: $612.34
|
||||
Shares Withheld for Taxes: 45
|
||||
Tax Withholding Amount: $27,555.30
|
||||
</p>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
def test_vest_release_returns_two_activities_and_vest_event() -> None:
|
||||
"""Release Confirmation yields a BUY (full vest) + SELL (sell-to-cover) + VestEvent."""
|
||||
from broker_sync.providers.parsers.schwab import parse_schwab_email_full
|
||||
|
||||
result = parse_schwab_email_full(_VEST_RELEASE)
|
||||
assert result.vest_event is not None
|
||||
assert result.vest_event.ticker == "META"
|
||||
assert result.vest_event.shares_vested == Decimal("100.0")
|
||||
assert result.vest_event.shares_sold_to_cover == Decimal("45")
|
||||
assert result.vest_event.fmv_at_vest_usd == Decimal("612.34")
|
||||
assert result.vest_event.tax_withheld_usd == Decimal("27555.30")
|
||||
assert result.vest_event.vest_date.date().isoformat() == "2026-03-15"
|
||||
assert result.vest_event.external_id.startswith("schwab:2026-03-15:META:VEST:")
|
||||
|
||||
assert len(result.activities) == 2
|
||||
buy = result.activities[0]
|
||||
assert buy.activity_type is ActivityType.BUY
|
||||
assert buy.quantity == Decimal("100.0")
|
||||
sell = result.activities[1]
|
||||
assert sell.activity_type is ActivityType.SELL
|
||||
assert sell.quantity == Decimal("45")
|
||||
assert sell.unit_price == Decimal("612.34")
|
||||
|
||||
|
||||
def test_vest_email_with_unparseable_body_returns_empty() -> None:
|
||||
"""Subject says Release Confirmation but fields missing → empty result, no crash."""
|
||||
from broker_sync.providers.parsers.schwab import parse_schwab_email_full
|
||||
|
||||
html = "<html><body>Release Confirmation — please contact support</body></html>"
|
||||
result = parse_schwab_email_full(html)
|
||||
assert result.vest_event is None
|
||||
assert result.activities == []
|
||||
|
||||
|
||||
def test_back_compat_parse_schwab_email_drops_vest_event() -> None:
|
||||
"""The legacy list[Activity] shape remains stable for existing callers."""
|
||||
acts = parse_schwab_email(_VEST_RELEASE)
|
||||
assert len(acts) == 2
|
||||
assert all(isinstance(a.activity_type, ActivityType) for a in acts)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue