Projects: the snapshot of a project's figures
check / check (push) Canceled after 0s

The project page opens on a snapshot: revenue, direct costs, the
contribution before overhead and its margin of revenue (a segment
margin: revenue less every cost traced to the project, fixed and
variable; B-37 item 7), in the black or red, and for on-spec work the
cost unrecovered if repaid. To date or this fiscal year. By month,
revenue and costs with the net to date in its own strip; costs by
category; the list colors each net by its sign. books.project carries
the same figures, and a parity test runs the viewer and the MCP on the
same books to the cent. Tests honor TMPDIR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-22 06:54:26 -06:00
1 parent d24653558c
commit 3eda95eb3c
23 files changed
+2231 -246

No files matched your search

+16 -6
View File
@@ -2076,24 +2076,34 @@ until the owner upgrades them.
record with nothing new journals nothing. A rename changes the label
every report prints, nothing else (not destructive, as a party's name).
- `books.projects { status? }` (books:read) → `{ through, projects: [{ ...project,
to_date: { through, revenue, expenses, net, unrecovered_cost } }] }`: every
to_date: { through, revenue, expenses, net, margin_pct, unrecovered_cost } }] }`: every
project (every class), by name ignoring capitals; `to_date` counts every
posted revenue and expense line tagged to it through today in the
company's time zone, closing entries left out; `unrecovered_cost` only
company's time zone, closing entries left out; `net` is its
contribution before overhead (B-37 item 7: revenue less every cost
tagged to it, fixed and variable alike, no company overhead allocated:
a segment margin in Garrison's sense, not a variable-cost contribution
margin); `margin_pct` is net as a percent of revenue to one place, half
to even ("64.0"), null when revenue is zero or less; `unrecovered_cost` only
for an on-spec project (else null): expenses less revenue, never below
zero.
- `books.project { project, start?, end? }` (books:read; omitted dates: the fiscal
year to date, B-30, as every period report) → `{ project, period: {
start, end, revenue, expenses, net, by_account: [{ code, name, type
start, end, revenue, expenses, net, margin_pct, by_account: [{ code, name, type
("revenue" | "expense"), amount }], by_month: [{ month ("YYYY-MM"),
start, end, revenue, expenses, net }] }, to_date (as above, through
end), entries: [{ entry_id, date, description, status, project_amount,
start, end, revenue, expenses, net, net_to_date }] }, to_date (as above, through
end, plus `by_account` to date, as the period's: its revenue accounts,
then its direct costs by expense account; always present here, `[]`
when nothing tagged nets to anything, and absent from
`books.projects`), entries: [{ entry_id, date, description, status, project_amount,
total, reversal_of?, receipts: [attachment_id] }], documents: [as
books.list_receipts], decisions: [as books.decisions] }`. The figures
are `ledger_core::project_figures`'s, the viewer's: only revenue and
expense lines count (a tagged asset purchase is listed with its entries,
its project amount zero); `by_month` holds the period's last 240 months
at most. `to_date` always counts every tagged line through `end`,
at most, each month's `net_to_date` the running net through its end
(every tagged line from the first day the books keep: the last month's
is `to_date.net`). `to_date` always counts every tagged line through `end`,
whatever the project's dates: job-to-date beside the period. The on-spec
`unrecovered_cost` is the "if repaid" figure, never a receivable (ASC
450-30-25-1). Entries are newest first, drafts included; decisions
+32 -3
View File
@@ -2545,7 +2545,7 @@ package). **Checked:** `ledger-core` `tests/tax_capital.rs`;
a class as a project ... Projects are entities too and can have name,
description, whatever other metadata you think they should have"; the
convention `link-to-projects` in the books: "whenever applicable, we link
things to [projects]"); CONVENTION (job costing, item 3); AUTHORITY (on
things to [projects]"); CONVENTION (job costing, item 3; the snapshot, item 7: a presentation choice from management accounting, not GAAP); AUTHORITY (on
spec, item 4: ASC 450-30-25-1; ASC 340-40-25-5 with B-22;
`sources/fasb-asc.md`, both quoted through Deloitte's DART roadmaps and
not yet re-read in the ASC basic view: an open item)
@@ -2599,11 +2599,39 @@ not yet re-read in the ASC basic view: an open item)
so when one is tagged.
6. Omitted dates mean what B-30 says (`books.project`'s period); to date
is always stated as such, beside it.
7. **Snapshot (a presentation choice, not GAAP).** A project's net is
shown as its *contribution before overhead*: its revenue less its
direct costs (every expense line tagged to it), with no company
overhead allocated, since item 3 allocates none. The owner's label is
"contribution"; what it means is revenue less the costs traceable to
the project, fixed and variable alike (the books don't split costs by
behavior), before the common costs no project causes. That is
Garrison, Noreen and Brewer's *segment margin* (*Managerial
Accounting*, segment reporting: a segment's sales less its variable
expenses and its traceable fixed expenses, the defining source here),
who warn against allocating common costs to judge a segment. It is
not Horngren's *contribution margin* (revenue less variable costs
only; Horngren, Datar and Rajan, *Cost Accounting: A Managerial
Emphasis*), which the books can't compute and the viewer doesn't
claim; "contribution" here is only the everyday sense, what the
project contributes toward overhead. So a project in the black can
still leave the company in the red. Its margin ("contribution
margin" on the page, `margin_pct` in the tools) is that contribution
as a percent of revenue, one place, half to even, and none without
revenue (zero or less). "In the black" is a contribution above zero,
"in the red" below, "break-even" lines tagged that net to zero (a
cost and its reversal). The running net by month is the net to date
through each month's end; its last is the net to date. No GAAP
standard defines or requires these figures (ASC 280's segment
reporting is for public entities' operating segments, not projects);
they come from item 3's, one function the tools and the viewer share.
**Enforced:** `ledger_core::classes::{ProjectFacts, ProjectStatus,
ProjectKind}`, `Ledger::{check_set_project, set_project,
replay_set_project, projects_of_party}`, `reports::{project_figures,
project_to_date, project_entries}`, `kernel::unrecovered_cost`;
project_to_date, project_entries, contribution_margin_pct}`,
`ProjectToDate::{net, margin_pct, any_tagged}`, `ProjectFigures::{months_net_to_date,
period_margin_pct, period_lines}`, `kernel::unrecovered_cost`;
`Store::{add_project, set_project, subject_feature}` (feature
`projects`); `books.add_project`, `books.update_project`,
`books.projects`, `books.project`; the viewer's Projects pages.
@@ -2612,4 +2640,5 @@ project_to_date, project_entries}`, `kernel::unrecovered_cost`;
`tests/projects.rs`; bookspec `projects-net-is-its-tagged-lines` and
`projects-on-spec-costs-are-expenses`; Kani `unrecovered_cost_laws`;
proptests `prop_project_nets_partition_the_pnl`,
`prop_project_set_round_trips`.
`prop_project_set_round_trips`, `prop_project_contribution_is_the_net_to_date`
(core) and `prop_snapshot_contribution_is_the_net_to_date` (viewer).
+111
View File
@@ -1917,6 +1917,117 @@ balance-sheet zero; `project_set_of` (newer vs corrupt);
feature check; the decision `projects` omit-when-empty;
`late_tag_note`'s `is_over`; `remember_footer`'s threshold.
## P3: the project snapshot (2026-10-22)
The owner's ask: a "mini financial snapshot" of one project on its page,
overhead left out, so it shows at a glance whether the project is in the
black or the red, with charts (rules.md B-37 item 7: contribution before
overhead, revenue less every traced cost, a segment margin in Garrison's
sense; a presentation choice, not GAAP). Every figure is ledger-core's:
`ProjectToDate::{net, margin_pct, any_tagged, revenue_by_account,
expenses_by_account, revenue_lines, expenses_lines}`,
`ProjectFigures::{months_net_to_date, period_margin_pct, period_lines}`
and `contribution_margin_pct`, which `books.project` and
`books.projects` now return too (`margin_pct`, `to_date.by_account`,
`by_month[].net_to_date`). No mutation run (P12, D-33; none on the GUI);
each test below was checked by breaking what it guards and watching it
fail.
- **ledger-core:** `tests/spec_projects.rs`
`contribution_margin_is_one_place_and_none_without_revenue` (64.0,
100.0, -150.0, half to even at 1.25 and 1.35, none at zero or negative
revenue, none, not a panic, on overflow),
`months_net_to_date_runs_from_before_the_months` (opens on last year's
deposit; a reimbursement above its cost leaves the expense below zero;
no entries: every month zero, no margin),
`a_reversal_nets_a_project_to_zero` (no account left, two lines
counted: tagged, breaking even; a period before both counts none) and
`closing_entries_are_left_out_of_project_figures` (a close's lines
aren't counted); `properties.rs`
`prop_project_contribution_is_the_net_to_date` (random tags, sides,
refunds and reimbursements over two expense accounts and a random
period: the accounts sum to the totals, the contribution is revenue
less expenses, each month's net to date is `project_to_date` through
its end and the last is the net to date, the counted lines are the
tagged revenue and expense lines to date and in the period, a margin
exists exactly when revenue is above zero and has the contribution's
sign, and `project_figures`' to-date is `project_to_date`'s).
`project_figures` takes the net before its first month from to date
less the months (one scan fewer); the proptest pins it.
- **ledger-mcp:** `tests/spec_projects.rs`
`project_snapshot_margin_by_account_and_running_net` (the period's and
to date's margin_pct, to_date.by_account, the 12 months' net_to_date
opening on the December deposit and ending on to_date.net, a loss's
negative margin, null without revenue, a project with nothing tagged
still carrying `to_date.by_account: []`, and books.projects carrying
the margin but not the accounts). `books.project`'s description stays
under 700 characters (`tool_descriptions_fit_700_characters`).
- **viewer and server on one book:** ledger-cli
`tests/projects_parity.rs`
`a_projects_snapshot_is_the_same_in_the_viewer_and_from_the_server`
(`wanderbooks dev-stdio`'s `books.project` against the viewer's
`page_figures` and `Snap` on the same books: to date's revenue,
expenses, net, margin_pct, unrecovered cost and by_account, every
month's figures and net_to_date over the chart's span, and the fiscal
year's; with a refund, a reversal and overhead; gui builds only).
- **ledger-gui:** `tests/projects.rs`
`a_profitable_projects_snapshot_is_ledger_cores` (and the Contribution
tile's hover note is `CONTRIBUTION_NOTE`),
`a_loss_making_project_is_in_the_red` (costs largest first: the
higher-coded account is the larger),
`an_on_spec_projects_snapshot_has_its_unrecovered_cost` (each tile's
own value, read under its label, equal to `Snap::to_date` of
`project_to_date`, the pill, the margin shown only with revenue,
overhead never counted),
`no_revenue_means_no_margin`,
`a_project_with_no_entries_has_nothing_tagged_yet`,
`a_cost_repaid_exactly_breaks_even` (no account left: break-even, not
"nothing tagged", on the page and the list),
`a_reimbursement_above_its_cost_leaves_costs_below_zero`,
`a_project_started_this_month_charts_one_month`,
`the_chart_runs_from_the_start_or_first_entry_and_caps` (the earlier of
start and first entry, a start after today, 24 months at most),
`the_running_net_is_each_months_net_to_date` (sweeping the pointer over
the bars and over the net's own strip shows each month's
`months_net_to_date`, in order),
`the_fiscal_year_switch_shows_the_default_period` (B-30's omitted dates,
as `books.project`, with fiscal-year costs unlike to date's; the pill
stays to date; switching writes nothing to the journal),
`a_fiscal_year_with_nothing_says_so`,
`the_list_cues_in_the_black_and_in_the_red` (each net painted in its
cue's color, never the names', still alphabetical), and the proptest
`prop_snapshot_contribution_is_the_net_to_date` (reversals too: a
reversed tagged line is tagged and breaks even); the earlier tests take
the new labels (Revenue, Contribution, Contribution margin). With
`SHOT_DIR` set each snapshot case is saved as a PNG; run them one at a
time then (`SHOT_DIR=... cargo test -p ledger-gui --test projects --
--test-threads 1`: rendering from tests run in parallel crashed with
SIGSEGV, 2026-10-22; without `SHOT_DIR` nothing renders and they run
side by side). `screens.rs` gains `project-templates`
(the demo's profitable project). Shots:
`~/.cache/wanderbooks/shots-p3-snapshot/` (`light/`, `dark/`, `w900/`,
`tests/`).
- **Every test honors TMPDIR.** `.cargo/config.toml` forced `[env]
TMPDIR = "/tmp"` for every cargo-run test (for `just mutants*`' speed),
so a TMPDIR set to keep tests off a small RAM /tmp was overridden;
it is gone. Every test makes its dirs with `std::env::temp_dir()` or
`tempfile` (both TMPDIR), and the binaries tests spawn with a cleared
environment write no temp files. A socket under a long TMPDIR is longer
than a socket address holds: every test connects through
`ledger_store::root::SocketPath`, as the servers bind (`raw_admin` in
`local_server.rs` didn't, and failed with TMPDIR under
`~/.cache/wanderbooks/p3-snapshot-tmp`). ledger-gui's `exports.rs`
downloads folder, shared by its tests, is removed as the binary exits
(`atexit`; `wb-*`, so `sweep-test-tmp` catches a killed run). Checked
by running `just check` with TMPDIR under `~/.cache/wanderbooks` and
finding no new test dir in /tmp. Mutation runs (`just mutants*`) now
put test books under that TMPDIR too, on disk: slower per mutant than
/tmp was; none is running (P12 paused).
**For P12 (mutation targets):** `contribution_margin_pct`'s `<=` and
rounding strategy; `project_figures`' opening net and running sum;
`profit_and_loss_counted`'s counts; `Standing::of`'s order.
## Benchmarks (baseline 2026-10-13)
No budget, by decision (the owner, 2026-10-13: the books do little work