clear up the attributions output
This commit is contained in:
parent
67351bc936
commit
f28d98f708
1 changed files with 41 additions and 28 deletions
|
|
@ -499,35 +499,46 @@ fn renderTotalsLine(out: *std.Io.Writer, color: bool, t: view.TotalsRow) !void {
|
|||
try cli.printIntent(out, color, c.style, " {s} {s}\n", .{ c.delta, c.pct });
|
||||
}
|
||||
|
||||
/// Render the contributions-vs-gains attribution line directly beneath
|
||||
/// the Liquid totals. Matches the email format:
|
||||
/// Render the investment-gains vs. cash-contributions breakdown of
|
||||
/// the liquid delta, stacked as two labeled rows directly beneath
|
||||
/// the Liquid line:
|
||||
///
|
||||
/// Attribution: +$30,148.02 delta = +$22,636.00 contributions + +$7,512.02 gains
|
||||
/// Investment gains: +$85,062.72
|
||||
/// Cash contributions: +$7,866.22
|
||||
///
|
||||
/// All three amounts are signed: negative contributions (net
|
||||
/// withdrawal) and negative gains (market loss) both print with a
|
||||
/// leading `-`. Indent of the label column aligns with "Liquid:".
|
||||
/// Both amounts are signed. Negative gains (market loss) and
|
||||
/// negative contributions (net withdrawal) print with a leading `-`
|
||||
/// in the negative color. Labels use the muted color to stay out of
|
||||
/// the way of the eye scan for the numbers.
|
||||
///
|
||||
/// Labels are padded to a fixed width so the `$` column aligns
|
||||
/// regardless of label length. Indent of 2 spaces matches the
|
||||
/// gainer/loser summary beneath the per-symbol table.
|
||||
///
|
||||
/// Math identity: `delta = gains + contributions`, so
|
||||
/// `gains = delta - contributions`. The caller derives `gains` from
|
||||
/// the liquid delta before this is called; we just render.
|
||||
fn renderAttributionLine(out: *std.Io.Writer, color: bool, delta: f64, attribution: view.Attribution) !void {
|
||||
var delta_buf: [32]u8 = undefined;
|
||||
var contrib_buf: [32]u8 = undefined;
|
||||
_ = delta; // delta is already shown on the Liquid row above; we
|
||||
// don't repeat it here. Kept in the signature so the
|
||||
// callsite stays readable (`renderAttributionLine(out,
|
||||
// color, cv.liquid.delta, a)`) and future revisions
|
||||
// that want to restate the Δ have it in scope.
|
||||
|
||||
var gains_buf: [32]u8 = undefined;
|
||||
const delta_str = view_hist.fmtSignedMoneyBuf(&delta_buf, delta);
|
||||
const contrib_str = view_hist.fmtSignedMoneyBuf(&contrib_buf, attribution.contributions);
|
||||
var contrib_buf: [32]u8 = undefined;
|
||||
const gains_str = view_hist.fmtSignedMoneyBuf(&gains_buf, attribution.gains);
|
||||
const contrib_str = view_hist.fmtSignedMoneyBuf(&contrib_buf, attribution.contributions);
|
||||
|
||||
// Sign-aware joiner between `contributions` and `gains`:
|
||||
// gains >= 0 → " + " (explicit addition).
|
||||
// gains < 0 → " " (the leading "-" on gains_str carries the sign;
|
||||
// avoids visual clutter of "+$X + -$Y").
|
||||
const joiner: []const u8 = if (attribution.gains >= 0) " + " else " ";
|
||||
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, "Attribution: ", .{});
|
||||
try cli.printGainLoss(out, color, delta, "{s}", .{delta_str});
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, " delta = ", .{});
|
||||
try cli.printGainLoss(out, color, attribution.contributions, "{s}", .{contrib_str});
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, " contributions{s}", .{joiner});
|
||||
try cli.printGainLoss(out, color, attribution.gains, "{s}", .{gains_str});
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, " gains\n", .{});
|
||||
// 19-char label column aligns the amount columns. "Investment
|
||||
// gains:" is 17 chars → 2 trailing pad; "Cash contributions:" is
|
||||
// 19 chars → 0 trailing pad. The 2-space gutter that follows
|
||||
// keeps the amounts clearly separated from the labels even on
|
||||
// narrow terminals.
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, " {s:<19} ", .{"Investment gains:"});
|
||||
try cli.printGainLoss(out, color, attribution.gains, "{s}\n", .{gains_str});
|
||||
try cli.printFg(out, color, cli.CLR_MUTED, " {s:<19} ", .{"Cash contributions:"});
|
||||
try cli.printGainLoss(out, color, attribution.contributions, "{s}\n", .{contrib_str});
|
||||
}
|
||||
|
||||
fn renderSymbolRow(out: *std.Io.Writer, color: bool, s: view.SymbolChange) !void {
|
||||
|
|
@ -738,12 +749,13 @@ test "renderCompare: attribution line when attribution is set" {
|
|||
try renderCompare(&stream, false, cv);
|
||||
const out = stream.buffered();
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Attribution:") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "+$30,148.02") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Investment gains:") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Cash contributions:") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "+$30,148.02") != null); // on the Liquid row above
|
||||
try testing.expect(std.mem.indexOf(u8, out, "+$22,636.00") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "+$7,512.02") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "contributions") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "gains") != null);
|
||||
// The old `Attribution:` prefix is gone.
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Attribution:") == null);
|
||||
}
|
||||
|
||||
test "renderCompare: no attribution line when attribution is null" {
|
||||
|
|
@ -765,7 +777,8 @@ test "renderCompare: no attribution line when attribution is null" {
|
|||
try renderCompare(&stream, false, cv);
|
||||
const out = stream.buffered();
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Attribution:") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Investment gains:") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, out, "Cash contributions:") == null);
|
||||
}
|
||||
|
||||
test "renderCompare: attribution handles negative gains" {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue