diff --git a/src/commands/analysis.zig b/src/commands/analysis.zig index ede3362..ad89c22 100644 --- a/src/commands/analysis.zig +++ b/src/commands/analysis.zig @@ -4,9 +4,7 @@ const cli = @import("common.zig"); const fmt = cli.fmt; /// CLI `analysis` command: show portfolio analysis breakdowns. -pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { - _ = config; - +pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { // Load portfolio const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch { try cli.stderrPrint("Error: Cannot read portfolio file\n"); @@ -23,11 +21,12 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer const positions = try portfolio.positions(allocator); defer allocator.free(positions); - // Build prices map from cache + const syms = try portfolio.stockSymbols(allocator); + defer allocator.free(syms); + + // Build prices from cache var prices = std.StringHashMap(f64).init(allocator); defer prices.deinit(); - - // First pass: try cached candle prices for (positions) |pos| { if (pos.shares <= 0) continue; if (svc.getCachedCandles(pos.symbol)) |cs| { @@ -37,15 +36,16 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer } } } - // Build fallback prices for symbols without cached candle data - var manual_price_set = try zfin.valuation.buildFallbackPrices(allocator, portfolio.lots, positions, &prices); - defer manual_price_set.deinit(); - var summary = zfin.valuation.portfolioSummary(allocator, portfolio, positions, prices, manual_price_set) catch { - try cli.stderrPrint("Error computing portfolio summary.\n"); - return; + // Build summary via shared pipeline + var pf_data = cli.buildPortfolioData(allocator, portfolio, positions, syms, &prices, svc) catch |err| switch (err) { + error.NoAllocations, error.SummaryFailed => { + try cli.stderrPrint("Error computing portfolio summary.\n"); + return; + }, + else => return err, }; - defer summary.deinit(allocator); + defer pf_data.deinit(allocator); // Load classification metadata const dir_end = if (std.mem.lastIndexOfScalar(u8, file_path, '/')) |idx| idx + 1 else 0; @@ -78,10 +78,10 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer var result = zfin.analysis.analyzePortfolio( allocator, - summary.allocations, + pf_data.summary.allocations, cm, portfolio, - summary.total_value, + pf_data.summary.total_value, acct_map_opt, ) catch { try cli.stderrPrint("Error computing analysis.\n"); @@ -101,51 +101,22 @@ pub fn display(result: zfin.analysis.AnalysisResult, file_path: []const u8, colo try cli.reset(out, color); try out.print("========================================\n\n", .{}); - // Asset Class - try cli.setBold(out, color); - try cli.setFg(out, color, cli.CLR_HEADER); - try out.print(" Asset Class\n", .{}); - try cli.reset(out, color); - try printBreakdownSection(out, result.asset_class, label_width, bar_width, color); + const sections = [_]struct { items: []const zfin.analysis.BreakdownItem, title: []const u8 }{ + .{ .items = result.asset_class, .title = " Asset Class" }, + .{ .items = result.sector, .title = " Sector (Equities)" }, + .{ .items = result.geo, .title = " Geographic" }, + .{ .items = result.account, .title = " By Account" }, + .{ .items = result.tax_type, .title = " By Tax Type" }, + }; - // Sector - if (result.sector.len > 0) { - try out.print("\n", .{}); + for (sections, 0..) |sec, si| { + if (si > 0 and sec.items.len == 0) continue; + if (si > 0) try out.print("\n", .{}); try cli.setBold(out, color); try cli.setFg(out, color, cli.CLR_HEADER); - try out.print(" Sector (Equities)\n", .{}); + try out.print("{s}\n", .{sec.title}); try cli.reset(out, color); - try printBreakdownSection(out, result.sector, label_width, bar_width, color); - } - - // Geographic - if (result.geo.len > 0) { - try out.print("\n", .{}); - try cli.setBold(out, color); - try cli.setFg(out, color, cli.CLR_HEADER); - try out.print(" Geographic\n", .{}); - try cli.reset(out, color); - try printBreakdownSection(out, result.geo, label_width, bar_width, color); - } - - // By Account - if (result.account.len > 0) { - try out.print("\n", .{}); - try cli.setBold(out, color); - try cli.setFg(out, color, cli.CLR_HEADER); - try out.print(" By Account\n", .{}); - try cli.reset(out, color); - try printBreakdownSection(out, result.account, label_width, bar_width, color); - } - - // Tax Type - if (result.tax_type.len > 0) { - try out.print("\n", .{}); - try cli.setBold(out, color); - try cli.setFg(out, color, cli.CLR_HEADER); - try out.print(" By Tax Type\n", .{}); - try cli.reset(out, color); - try printBreakdownSection(out, result.tax_type, label_width, bar_width, color); + try printBreakdownSection(out, sec.items, label_width, bar_width, color); } // Unclassified diff --git a/src/main.zig b/src/main.zig index 7debe8a..3afe07a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -211,7 +211,7 @@ pub fn main() !u8 { break; } } - try commands.analysis.run(allocator, config, &svc, analysis_file, color, out); + try commands.analysis.run(allocator, &svc, analysis_file, color, out); } else { try cli.stderrPrint("Unknown command. Run 'zfin help' for usage.\n"); return 1;