soma.cell

Survey scoring: normalize raw responses, roll up per question, and flag low-confidence questions

Demonstrates avg_by, agg with min/max, a derived spread column, and assert_fails on an out-of-bounds index (negative path).

A Soma program from the data part of the verified corpus: 50 lines, cells Survey, SurveyTests. It passes soma check with soma 3.2.4.

How it is built

The cell Survey has no face and no memory. Handlers: responses (line 6), by_question (line 20), divisive (line 27), overall_avg (line 29) and run (line 31).

The file also holds 1 test cell (SurveyTests) with 5 assertions, run by soma test.

Generated from soma describe on this file.

The source

survey_scores.cell

// Survey scoring: normalize raw responses, roll up per question, and flag
// low-confidence questions. Demonstrates avg_by, agg with min/max, a derived
// spread column, and assert_fails on an out-of-bounds index (negative path).

cell Survey {
    on responses() {
        return list(
            map("q", "ux",    "score", 4, "respondent", "r1"),
            map("q", "ux",    "score", 5, "respondent", "r2"),
            map("q", "ux",    "score", 2, "respondent", "r3"),
            map("q", "perf",  "score", 3, "respondent", "r1"),
            map("q", "perf",  "score", 3, "respondent", "r2"),
            map("q", "perf",  "score", 4, "respondent", "r3"),
            map("q", "price", "score", 1, "respondent", "r1"),
            map("q", "price", "score", 5, "respondent", "r2")
        )
    }

    // Per question: avg, min, max, and spread = max - min.
    on by_question() {
        let agged = responses() |> agg("q", "score:avg", "score:min", "score:max")
        return agged |> map(r => r |> with("spread", r.score_max - r.score_min))
            |> sort_by("spread", "desc")
    }

    // Questions where opinions diverge a lot (spread >= 3).
    on divisive() { return by_question() |> filter_by("spread", ">=", 3) |> pluck("q") }

    on overall_avg() { return responses() |> avg_by("score") }

    on run() {
        for r in by_question() { print("{r.q}: avg {r.score_avg}, spread {r.spread}") }
        return divisive()
    }
}

cell test SurveyTests {
    rules {
        // price spread = 5 - 1 = 4 (most divisive)
        assert by_question()[0].q == "price"
        assert by_question()[0].spread == 4
        // perf spread = 4 - 3 = 1
        assert (by_question() |> filter_by("q", "=", "perf"))[0].spread == 1
        // divisive (spread>=3): price(4), ux(5-2=3) -> 2 questions
        assert len(divisive()) == 2
        // indexing past the end of a 3-question result is a runtime error
        assert_fails by_question()[9].q
    }
}

What the toolchain says

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

$ soma check survey_scores.cell
✓ All checks passed.
$ soma test survey_scores.cell
test SurveyTests ...
  ✓ assert by_question()[0].q == "price"
  ✓ assert by_question()[0].spread == 4
  ✓ assert (by_question() |> filter_by("q", "=", "perf"))[0].spread == 1
  ✓ assert len(divisive()) == 2
  ✓ assert_fails by_question()[9].q — raised list index 9 out of bounds (length 3)

5 tests: 5 passed, 0 failed
$ soma verify survey_scores.cell
Verifying survey_scores.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, avg_by, filter_by, len, list, map, pluck, print, sort_by, with

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