Conversion funnel: count distinct users reaching each stage, then compute step-to-step conversion rates
Demonstrates count over a lambda, distinct per stage, and building a derived report list with map().
A Soma program from the data part of the verified corpus: 59 lines, cells Funnel, FunnelTests. It passes soma check with soma 3.2.4.
How it is built
The cell Funnel has no face and no memory. Handlers: hits (line 6), reached (line 21), report (line 26) and run (line 41).
The file also holds 1 test cell (FunnelTests) with 5 assertions, run by soma test.
The source
funnel.cell
// Conversion funnel: count distinct users reaching each stage, then compute
// step-to-step conversion rates. Demonstrates count over a lambda, distinct
// per stage, and building a derived report list with map().
cell Funnel {
on hits() {
return list(
map("user", "u1", "stage", "visit"),
map("user", "u2", "stage", "visit"),
map("user", "u3", "stage", "visit"),
map("user", "u4", "stage", "visit"),
map("user", "u1", "stage", "signup"),
map("user", "u2", "stage", "signup"),
map("user", "u3", "stage", "signup"),
map("user", "u1", "stage", "purchase"),
map("user", "u2", "stage", "purchase")
)
}
// Unique users at a given stage.
on reached(stage: String) {
return hits() |> filter_by("stage", "=", stage) |> distinct("user") |> len
}
// visit -> signup -> purchase counts and conversion %.
on report() {
let stages = list("visit", "signup", "purchase")
let out = list()
let prev = 0
let i = 0
while i < len(stages) {
let n = reached(stages[i])
let rate = if prev == 0 { 100 } else { floor(n * 100 / prev) }
out = out.push(map("stage", stages[i], "users", n, "rate", rate))
prev = n
i += 1
}
return out
}
on run() {
for r in report() { print("{r.stage}: {r.users} users ({r.rate}%)") }
return report()
}
}
cell test FunnelTests {
rules {
// 4 visited, 3 signed up, 2 purchased
assert reached("visit") == 4
assert reached("signup") == 3
assert reached("purchase") == 2
// signup conversion = floor(3*100/4) = 75
assert report()[1].rate == 75
// purchase conversion = floor(2*100/3) = 66
assert report()[2].rate == 66
}
}What the toolchain says
$ soma check funnel.cell
✓ All checks passed.$ soma test funnel.cell
test FunnelTests ...
✓ assert reached("visit") == 4
✓ assert reached("signup") == 3
✓ assert reached("purchase") == 2
✓ assert report()[1].rate == 75
✓ assert report()[2].rate == 66
5 tests: 5 passed, 0 failed$ soma verify funnel.cell
Verifying funnel.cell...
Funnel/termination:
⚠ handler `report`: while-loop without provable termination bound (write `while [loop_bound(N)] cond { … }`, or a for-loop over a bounded range)
⚠ `run` calls `Funnel.report`, which may not terminate
0 passed, 2 warnings, 0 failures
Temporal: 0 passed, 0 failed
VERIFY OK