Expense report over a List<Map> of line items
A normalize step tags each row as fixed vs variable, then aggregates spend per category and per type. Demonstrates a block-lambda classifier, agg, sum_by, and max_by together.
A Soma program from the data part of the verified corpus: 62 lines, cells ExpenseReport, ExpenseReportTests. It passes soma check with soma 3.2.4.
How it is built
The cell ExpenseReport has no face and no memory. Handlers: items (line 6), classified (line 19), by_category (line 26), by_kind (line 32), total (line 38), biggest (line 40) and run (line 42).
The file also holds 1 test cell (ExpenseReportTests) with 5 assertions, run by soma test.
The source
expense_tracker.cell
// Expense report over a List<Map> of line items. A normalize step tags each
// row as fixed vs variable, then aggregates spend per category and per type.
// Demonstrates a block-lambda classifier, agg, sum_by, and max_by together.
cell ExpenseReport {
on items() {
return list(
map("category", "rent", "amount", 1200),
map("category", "food", "amount", 25),
map("category", "travel", "amount", 120),
map("category", "food", "amount", 40),
map("category", "rent", "amount", 1200),
map("category", "travel", "amount", 15),
map("category", "utilities","amount", 90)
)
}
// Tag rows: rent/utilities are fixed, the rest variable.
on classified() {
return items() |> map(r => {
let kind = if r.category == "rent" || r.category == "utilities" { "fixed" } else { "variable" }
r |> with("kind", kind)
})
}
on by_category() {
return items()
|> agg("category", "amount:sum", "amount:count")
|> sort_by("amount_sum", "desc")
}
on by_kind() {
return classified()
|> agg("kind", "amount:sum")
|> sort_by("amount_sum", "desc")
}
on total() { return items() |> sum_by("amount") }
on biggest() { return max_by(items(), "amount") }
on run() {
for r in by_category() { print("{r.category}: {r.amount_sum} ({r.amount_count}x)") }
return total()
}
}
cell test ExpenseReportTests {
rules {
// food = 25 + 40 = 65
assert (by_category() |> filter_by("category", "=", "food"))[0].amount_sum == 65
// rent appears twice
assert (by_category() |> filter_by("category", "=", "rent"))[0].amount_count == 2
// total = 1200+25+120+40+1200+15+90 = 2690
assert total() == 2690
// fixed = rent(2400) + utilities(90) = 2490
assert (by_kind() |> filter_by("kind", "=", "fixed"))[0].amount_sum == 2490
// biggest single line is rent at 1200
assert biggest().amount == 1200
}
}What the toolchain says
$ soma check expense_tracker.cell
✓ All checks passed.$ soma test expense_tracker.cell
test ExpenseReportTests ...
✓ assert (by_category() |> filter_by("category", "=", "food"))[0].amount_sum == 65
✓ assert (by_category() |> filter_by("category", "=", "rent"))[0].amount_count == 2
✓ assert total() == 2690
✓ assert (by_kind() |> filter_by("kind", "=", "fixed"))[0].amount_sum == 2490
✓ assert biggest().amount == 1200
5 tests: 5 passed, 0 failed$ soma verify expense_tracker.cell
Verifying expense_tracker.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)Builtins used
agg, filter_by, list, map, max_by, print, sort_by, sum_by, with