From f1e9321bdc4a9bf85b5cfaf4688e9b4d725fc9e5 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Fri, 6 Mar 2026 13:51:19 -0800 Subject: [PATCH] move tax type to enum --- src/analytics/analysis.zig | 46 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/analytics/analysis.zig b/src/analytics/analysis.zig index ebd6eb9..b191cfe 100644 --- a/src/analytics/analysis.zig +++ b/src/analytics/analysis.zig @@ -17,10 +17,27 @@ pub const BreakdownItem = struct { weight: f64, // fraction of total (0.0 - 1.0) }; +/// Tax type classification for accounts. +pub const TaxType = enum { + taxable, + roth, + traditional, + hsa, + + pub fn label(self: TaxType) []const u8 { + return switch (self) { + .taxable => "Taxable", + .roth => "Roth (Post-Tax)", + .traditional => "Traditional (Pre-Tax)", + .hsa => "HSA (Triple Tax-Free)", + }; + } +}; + /// Account tax type classification entry, parsed from accounts.srf. pub const AccountTaxEntry = struct { account: []const u8, - tax_type: []const u8, + tax_type: TaxType, }; /// Parsed account metadata. @@ -31,7 +48,6 @@ pub const AccountMap = struct { pub fn deinit(self: *AccountMap) void { for (self.entries) |e| { self.allocator.free(e.account); - self.allocator.free(e.tax_type); } self.allocator.free(self.entries); } @@ -40,22 +56,13 @@ pub const AccountMap = struct { pub fn taxTypeFor(self: AccountMap, account: []const u8) []const u8 { for (self.entries) |e| { if (std.mem.eql(u8, e.account, account)) { - return taxTypeLabel(e.tax_type); + return e.tax_type.label(); } } return "Unknown"; } }; -/// Map raw tax_type strings to display labels. -fn taxTypeLabel(raw: []const u8) []const u8 { - if (std.mem.eql(u8, raw, "taxable")) return "Taxable"; - if (std.mem.eql(u8, raw, "roth")) return "Roth (Post-Tax)"; - if (std.mem.eql(u8, raw, "traditional")) return "Traditional (Pre-Tax)"; - if (std.mem.eql(u8, raw, "hsa")) return "HSA (Triple Tax-Free)"; - return raw; -} - /// Parse an accounts.srf file into an AccountMap. /// Each record has: account::,tax_type:: pub fn parseAccountsFile(allocator: std.mem.Allocator, data: []const u8) !AccountMap { @@ -63,7 +70,6 @@ pub fn parseAccountsFile(allocator: std.mem.Allocator, data: []const u8) !Accoun errdefer { for (entries.items) |e| { allocator.free(e.account); - allocator.free(e.tax_type); } entries.deinit(allocator); } @@ -76,7 +82,7 @@ pub fn parseAccountsFile(allocator: std.mem.Allocator, data: []const u8) !Accoun const entry = record.to(AccountTaxEntry) catch continue; try entries.append(allocator, .{ .account = try allocator.dupe(u8, entry.account), - .tax_type = try allocator.dupe(u8, entry.tax_type), + .tax_type = entry.tax_type, }); } @@ -287,13 +293,11 @@ test "parseAccountsFile" { try std.testing.expectEqualStrings("Unknown", am.taxTypeFor("Nonexistent")); } -test "taxTypeLabel" { - try std.testing.expectEqualStrings("Taxable", taxTypeLabel("taxable")); - try std.testing.expectEqualStrings("Roth (Post-Tax)", taxTypeLabel("roth")); - try std.testing.expectEqualStrings("Traditional (Pre-Tax)", taxTypeLabel("traditional")); - try std.testing.expectEqualStrings("HSA (Triple Tax-Free)", taxTypeLabel("hsa")); - // Unknown type returns raw string - try std.testing.expectEqualStrings("custom_type", taxTypeLabel("custom_type")); +test "TaxType.label" { + try std.testing.expectEqualStrings("Taxable", TaxType.taxable.label()); + try std.testing.expectEqualStrings("Roth (Post-Tax)", TaxType.roth.label()); + try std.testing.expectEqualStrings("Traditional (Pre-Tax)", TaxType.traditional.label()); + try std.testing.expectEqualStrings("HSA (Triple Tax-Free)", TaxType.hsa.label()); } test "mapToSortedBreakdown" {