diff --git a/src/commands/analysis.zig b/src/commands/analysis.zig index 46a8e3e..c608da9 100644 --- a/src/commands/analysis.zig +++ b/src/commands/analysis.zig @@ -65,15 +65,7 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []co defer cm.deinit(); // Load account tax type metadata (optional) - const acct_path = std.fmt.allocPrint(allocator, "{s}accounts.srf", .{file_path[0..dir_end]}) catch return; - defer allocator.free(acct_path); - - var acct_map_opt: ?zfin.analysis.AccountMap = null; - const acct_data = std.fs.cwd().readFileAlloc(allocator, acct_path, 1024 * 1024) catch null; - if (acct_data) |ad| { - defer allocator.free(ad); - acct_map_opt = zfin.analysis.parseAccountsFile(allocator, ad) catch null; - } + var acct_map_opt: ?zfin.analysis.AccountMap = svc.loadAccountMap(file_path); defer if (acct_map_opt) |*am| am.deinit(); var result = zfin.analysis.analyzePortfolio( diff --git a/src/commands/audit.zig b/src/commands/audit.zig index 459dbc0..7e5111c 100644 --- a/src/commands/audit.zig +++ b/src/commands/audit.zig @@ -1155,18 +1155,8 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, args: []const [ defer portfolio.deinit(); // Load accounts.srf - const dir_end = if (std.mem.lastIndexOfScalar(u8, portfolio_path, std.fs.path.sep)) |idx| idx + 1 else 0; - const acct_path = std.fmt.allocPrint(allocator, "{s}accounts.srf", .{portfolio_path[0..dir_end]}) catch return; - defer allocator.free(acct_path); - - const acct_data = std.fs.cwd().readFileAlloc(allocator, acct_path, 1024 * 1024) catch { - try cli.stderrPrint("Error: Cannot read accounts.srf (needed for account number mapping)\n"); - return; - }; - defer allocator.free(acct_data); - - var account_map = analysis.parseAccountsFile(allocator, acct_data) catch { - try cli.stderrPrint("Error: Cannot parse accounts.srf\n"); + var account_map = svc.loadAccountMap(portfolio_path) orelse { + try cli.stderrPrint("Error: Cannot read/parse accounts.srf (needed for account number mapping)\n"); return; }; defer account_map.deinit(); diff --git a/src/service.zig b/src/service.zig index 7489705..b0db727 100644 --- a/src/service.zig +++ b/src/service.zig @@ -20,6 +20,7 @@ const EtfProfile = @import("models/etf_profile.zig").EtfProfile; const Config = @import("config.zig").Config; const cache = @import("cache/store.zig"); const srf = @import("srf"); +const analysis = @import("analytics/analysis.zig"); const TwelveData = @import("providers/twelvedata.zig").TwelveData; const Polygon = @import("providers/polygon.zig").Polygon; const Finnhub = @import("providers/finnhub.zig").Finnhub; @@ -1334,6 +1335,22 @@ pub const DataService = struct { fn isMutualFund(symbol: []const u8) bool { return symbol.len == 5 and symbol[4] == 'X'; } + + // ── User config files ───────────────────────────────────────── + + /// Load and parse accounts.srf from the same directory as the given portfolio path. + /// Returns null if the file doesn't exist or can't be parsed. + /// Caller owns the returned AccountMap and must call deinit(). + pub fn loadAccountMap(self: *DataService, portfolio_path: []const u8) ?analysis.AccountMap { + const dir_end = if (std.mem.lastIndexOfScalar(u8, portfolio_path, std.fs.path.sep)) |idx| idx + 1 else 0; + const acct_path = std.fmt.allocPrint(self.allocator, "{s}accounts.srf", .{portfolio_path[0..dir_end]}) catch return null; + defer self.allocator.free(acct_path); + + const data = std.fs.cwd().readFileAlloc(self.allocator, acct_path, 1024 * 1024) catch return null; + defer self.allocator.free(data); + + return analysis.parseAccountsFile(self.allocator, data) catch null; + } }; // ── Tests ───────────────────────────────────────────────────────── diff --git a/src/tui.zig b/src/tui.zig index 908c51b..18cb345 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -827,14 +827,7 @@ pub const App = struct { pub fn ensureAccountMap(self: *App) void { if (self.account_map != null) return; const ppath = self.portfolio_path orelse return; - const dir_end = if (std.mem.lastIndexOfScalar(u8, ppath, std.fs.path.sep)) |idx| idx + 1 else 0; - const acct_path = std.fmt.allocPrint(self.allocator, "{s}accounts.srf", .{ppath[0..dir_end]}) catch return; - defer self.allocator.free(acct_path); - - if (std.fs.cwd().readFileAlloc(self.allocator, acct_path, 1024 * 1024)) |acct_data| { - defer self.allocator.free(acct_data); - self.account_map = zfin.analysis.parseAccountsFile(self.allocator, acct_data) catch null; - } else |_| {} + self.account_map = self.svc.loadAccountMap(ppath); } /// Set or clear the account filter. Owns the string via allocator.