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:
23 files changed
+2231
-246
No files matched your search
+16
-6
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user