From 7014c326d107fb546dddc0e51df7d2b09e998962 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Tue, 28 Apr 2026 12:30:16 -0700 Subject: [PATCH] add total value to equity/fixed income display --- src/commands/analysis.zig | 12 ++++++++---- src/tui/analysis_tab.zig | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/commands/analysis.zig b/src/commands/analysis.zig index 1afa8af..fe7bf1c 100644 --- a/src/commands/analysis.zig +++ b/src/commands/analysis.zig @@ -79,10 +79,10 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []co portfolio.totalCdFaceValue(), ); - try display(result, split.stock_pct, split.bond_pct, file_path, color, out); + try display(result, split.stock_pct, split.bond_pct, pf_data.summary.total_value, file_path, color, out); } -pub fn display(result: zfin.analysis.AnalysisResult, stock_pct: f64, bond_pct: f64, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { +pub fn display(result: zfin.analysis.AnalysisResult, stock_pct: f64, bond_pct: f64, total_value: f64, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { const label_width = fmt.analysis_label_width; const bar_width = fmt.analysis_bar_width; @@ -93,8 +93,12 @@ pub fn display(result: zfin.analysis.AnalysisResult, stock_pct: f64, bond_pct: f // Equities vs Fixed Income summary { + var eq_buf: [24]u8 = undefined; + var fi_buf: [24]u8 = undefined; + const eq_dollars = fmt.fmtMoneyAbs(&eq_buf, stock_pct * total_value); + const fi_dollars = fmt.fmtMoneyAbs(&fi_buf, bond_pct * total_value); try cli.setFg(out, color, cli.CLR_MUTED); - try out.print(" Equities {d:.1}% / Fixed Income {d:.1}%\n\n", .{ stock_pct * 100, bond_pct * 100 }); + try out.print(" Equities {d:.1}% ({s}) / Fixed Income {d:.1}% ({s})\n\n", .{ stock_pct * 100, eq_dollars, bond_pct * 100, fi_dollars }); try cli.reset(out, color); } @@ -237,7 +241,7 @@ test "display shows all sections" { .unclassified = @constCast(&unclassified), .total_value = 100000.0, }; - try display(result, 0.80, 0.20, "test.srf", false, &w); + try display(result, 0.80, 0.20, 100000.0, "test.srf", false, &w); const out = w.buffered(); try std.testing.expect(std.mem.indexOf(u8, out, "Portfolio Analysis") != null); try std.testing.expect(std.mem.indexOf(u8, out, "Asset Class") != null); diff --git a/src/tui/analysis_tab.zig b/src/tui/analysis_tab.zig index 80894a7..058d23e 100644 --- a/src/tui/analysis_tab.zig +++ b/src/tui/analysis_tab.zig @@ -74,7 +74,9 @@ pub fn buildStyledLines(app: *App, arena: std.mem.Allocator) ![]const StyledLine // Compute equity/fixed split from classification + portfolio var stock_pct: f64 = 0; var bond_pct: f64 = 0; + var total_value: f64 = 0; if (app.portfolio_summary) |summary| { + total_value = summary.total_value; if (app.portfolio) |pf| { const benchmark = @import("../analytics/benchmark.zig"); const cm_entries = if (app.classification_map) |cm| cm.entries else &.{}; @@ -89,7 +91,7 @@ pub fn buildStyledLines(app: *App, arena: std.mem.Allocator) ![]const StyledLine bond_pct = split.bond_pct; } } - return renderAnalysisLines(arena, app.theme, app.analysis_result, stock_pct, bond_pct); + return renderAnalysisLines(arena, app.theme, app.analysis_result, stock_pct, bond_pct, total_value); } /// Render analysis tab content. Pure function — no App dependency. @@ -99,6 +101,7 @@ pub fn renderAnalysisLines( analysis_result: ?zfin.analysis.AnalysisResult, stock_pct: f64, bond_pct: f64, + total_value: f64, ) ![]const StyledLine { var lines: std.ArrayList(StyledLine) = .empty; @@ -114,8 +117,14 @@ pub fn renderAnalysisLines( // Equities vs Fixed Income summary if (stock_pct > 0 or bond_pct > 0) { + var eq_buf: [24]u8 = undefined; + var fi_buf: [24]u8 = undefined; + const eq_dollars = fmt.fmtMoneyAbs(&eq_buf, stock_pct * total_value); + const fi_dollars = fmt.fmtMoneyAbs(&fi_buf, bond_pct * total_value); try lines.append(arena, .{ - .text = try std.fmt.allocPrint(arena, " Equities {d:.1}% / Fixed Income {d:.1}%", .{ stock_pct * 100, bond_pct * 100 }), + .text = try std.fmt.allocPrint(arena, " Equities {d:.1}% ({s}) / Fixed Income {d:.1}% ({s})", .{ + stock_pct * 100, eq_dollars, bond_pct * 100, fi_dollars, + }), .style = th.mutedStyle(), }); try lines.append(arena, .{ .text = "", .style = th.contentStyle() }); @@ -243,7 +252,7 @@ test "renderAnalysisLines with data" { .unclassified = &.{}, .total_value = 200000, }; - const lines = try renderAnalysisLines(arena, th, result, 0.80, 0.20); + const lines = try renderAnalysisLines(arena, th, result, 0.80, 0.20, 200000); // Should have header section + asset class items try testing.expect(lines.len >= 5); // Find "Portfolio Analysis" header @@ -266,7 +275,7 @@ test "renderAnalysisLines no data" { const arena = arena_state.allocator(); const th = theme.default_theme; - const lines = try renderAnalysisLines(arena, th, null, 0, 0); + const lines = try renderAnalysisLines(arena, th, null, 0, 0, 0); try testing.expectEqual(@as(usize, 5), lines.len); try testing.expect(std.mem.indexOf(u8, lines[3].text, "No analysis data") != null); }