soma.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.

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.

Generated from soma describe on this file.

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

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ 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

Source file: /corpus/data/expense_tracker.cell · examples/corpus/data/expense_tracker.cell