From 5ee2151a4737dffc420616d028cd10b2c36f1600 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Fri, 10 Apr 2026 09:50:22 -0700 Subject: [PATCH] better heuristics on cash-like thingies --- src/commands/audit.zig | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/commands/audit.zig b/src/commands/audit.zig index efbd5a7..459dbc0 100644 --- a/src/commands/audit.zig +++ b/src/commands/audit.zig @@ -75,8 +75,10 @@ const FidelityCol = struct { const symbol = 2; const description = 3; const quantity = 4; + const last_price = 5; const current_value = 7; const cost_basis_total = 13; + const avg_cost_basis = 14; const type_col = 15; }; @@ -130,7 +132,9 @@ pub fn parseFidelityCsv(allocator: std.mem.Allocator, data: []const u8) ![]Broke // Note: Fidelity's Type column says "Cash" vs "Margin" to indicate // the account settlement type, NOT that the security is cash. // Only the ** suffix reliably identifies money market / cash holdings. - const is_cash = std.mem.endsWith(u8, symbol_raw, "**"); + // Also treat positions as cash when both price and cost basis are $1.00 + // (e.g. FDRXX "FID GOV CASH RESERVE" — no ** suffix). + const is_cash = std.mem.endsWith(u8, symbol_raw, "**") or isUnitPriceCash(cols[FidelityCol.last_price], cols[FidelityCol.avg_cost_basis]); // Strip ** suffix from money market symbols for display const symbol_clean = if (std.mem.endsWith(u8, symbol_raw, "**")) @@ -180,6 +184,14 @@ fn parseDollarAmount(raw: []const u8) ?f64 { return if (negative) -val else val; } +/// Returns true when both the last price and average cost basis parse to exactly $1.00, +/// indicating a money-market or cash-equivalent position (e.g. FDRXX). +fn isUnitPriceCash(price_raw: []const u8, cost_raw: []const u8) bool { + const price = parseDollarAmount(price_raw) orelse return false; + const cost = parseDollarAmount(cost_raw) orelse return false; + return price == 1.0 and cost == 1.0; +} + // ── Schwab CSV parser ─────────────────────────────────────── // // Parses the per-account positions CSV exported from Schwab's website @@ -1293,6 +1305,22 @@ test "parseFidelityCsv basic" { try std.testing.expectApproxEqAbs(@as(f64, 10000.00), positions[1].cost_basis.?, 0.01); } +test "parseFidelityCsv treats $1.00 price+cost as cash" { + const csv = + "Account Number,Account Name,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type\n" ++ + "Z123,Individual,FDRXX,FID GOV CASH RESERVE,8500,$1.00,,$8500.00,,,,,10%,$8500.00,$1.00,Cash,\n"; + + const allocator = std.testing.allocator; + const positions = try parseFidelityCsv(allocator, csv); + defer allocator.free(positions); + + try std.testing.expectEqual(@as(usize, 1), positions.len); + try std.testing.expectEqualStrings("FDRXX", positions[0].symbol); + try std.testing.expect(positions[0].is_cash); + try std.testing.expect(positions[0].quantity == null); + try std.testing.expectApproxEqAbs(@as(f64, 8500.00), positions[0].current_value.?, 0.01); +} + test "parseFidelityCsv stops at blank line" { const csv = "Account Number,Account Name,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type\n" ++