# nb-05 — Who counts as a suitable applicant *Northbridge course, intermediate ([nb-01](/tutorials/northbridge/nb-01-first-permit/) → [nb-02](/tutorials/northbridge/nb-02-missing-fact/) → [nb-03](/tutorials/northbridge/nb-03-exceptions/) → [nb-04](/tutorials/northbridge/nb-04-permit-fee/) → [nb-05](/tutorials/northbridge/nb-05-suitable-applicant/)). Northbridge is synthetic: every act, district, household and payment below is fictional and unofficial, and nothing here claims real municipal deployment or legal validity. Engine `law 0.1.0`, semantics `law.core/0.2`, std `0.2.0`.* ## Situation The Northbridge permit office keeps a priority shortlist called *suitable applicant*: a central-district resident from a large household is offered the first free appointment. Meanwhile the cashier totals fee payments, and the ledger is messy — Ann's three identical 10 EUR instalments were recorded as three separate receipts. Two questions reach the same desk: who belongs to the group, and what was actually paid? This article turns both into Law DSL constructs: a **definition** (a named shorthand for a condition), a **classification** (a label attached when conditions hold), and **aggregates** (`count` and `sum` folding a collection into one value) — including the Set-versus-List pair that decides whether identical payments are merged or added up. ## Prerequisites [nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/): facts, one rule, one question, and `law test` as the way to check a claim. [nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/): `Money` values such as `10 EUR`, and the shared constant `LARGE_HOUSEHOLD` (the integer `4`, defined once in the calculations package and reused here). Everything below either asks a *truth question* (does the label hold?) or computes a *value* (how many? how much?) and stores it in a relation — no duties, evidence, or time machinery is required. ## Minimal example Excerpt 1 — from `packs/examples/language-demo/permits/package.law` (lines 53–55; imports, relations and the other rules cut): ```law definition central_resident(a: Applicant) exact { when demo.northbridge.vocabulary::resident(a) and demo.northbridge.vocabulary::lives_in(a, demo.northbridge.vocabulary::central); } ``` A **definition** gives a shorthand name to a condition. `exact` means the name holds exactly when the body holds: resident *and* living in the central district, nothing more, nothing less. In the code, `when` carries the full condition — two facts about `a`, joined by `and` — while the header names the shorthand. Excerpt 2 — from the same file (lines 57–62; surrounding rules cut): ```law rule LargeHousehold strict { for a: Applicant; when demo.northbridge.vocabulary::resident(a) and count(collect m: Applicant where demo.northbridge.vocabulary::household_member(a, m)) >= demo.northbridge.calculations::LARGE_HOUSEHOLD; then large_household(a); } ``` `collect m: Applicant where ...` gathers every household member of `a` into a **Set**: each distinct member appears once. `count(...)` is an **aggregate** — it folds the whole collection into one number — and the rule compares it against the shared threshold `4`. Follow the `when` condition: the collection, the count, and the comparison with `LARGE_HOUSEHOLD` all sit in that one clause. Excerpt 3 — from the same file (lines 117–119; presumptions and fictions cut): ```law classification central_senior(a: Applicant) strict { when central_resident(a) and large_household(a); } ``` A **classification** attaches a group label when its body holds. Here it stacks the two previous constructs: the definition plus the aggregate rule. `central_senior` is the article's "suitable applicant". Its body holds just two names — the shorthand and the rule conclusion do all the work defined above. Excerpt 4 — from the same file (lines 201–215; position rules cut): ```law rule FeeTotalSet strict { for a: Applicant; for k: Integer; for v: Money; when fee_payment(a, k, v); then fee_total_set(a, sum(collect w: Money, j: Integer where fee_payment(a, j, w))); } rule FeeTotalAll strict { for a: Applicant; for k: Integer; for v: Money; when fee_payment(a, k, v); then fee_total_all(a, sum(collect all w: Money, j: Integer where fee_payment(a, j, w))); } ``` Same three premises, same `sum` aggregate — one keyword apart. `collect` builds a **Set** (identical entries merged); `collect all` keeps every entry, duplicates included (List behavior). Compare the two rules line by line: apart from their names, the `collect` versus `collect all` inside `sum(...)` is the only difference. The next section shows the observable result: `10 EUR` versus `30 EUR` on identical input. ## Command and result The permits package is checked with one command: ```sh law test packs/examples/language-demo/permits ``` Observed result (engine `law 0.1.0`): ```text law test demo.northbridge.permits: мир demo.northbridge.permits, demo.northbridge.calculations, demo.northbridge.vocabulary ok [demo.northbridge.permits] tests/permits.lawtest / general rule: resident with a car ok [demo.northbridge.permits] tests/permits.lawtest / unless removes the conclusion ok [demo.northbridge.permits] tests/permits.lawtest / denial wins by priority ok [demo.northbridge.permits] tests/permits.lawtest / without priority the conflict persists ok [demo.northbridge.permits] tests/permits.lawtest / defeater removes support without negation ok [demo.northbridge.permits] tests/permits.lawtest / definition: central resident ok [demo.northbridge.permits] tests/permits.lawtest / aggregate: large household ok [demo.northbridge.permits] tests/permits.lawtest / decision term: 30 days ok [demo.northbridge.permits] tests/permits.lawtest / without a policy the term is not computed ok [demo.northbridge.permits] tests/permits.lawtest / fee for three months ok [demo.northbridge.permits] tests/permits.lawtest / positions: duty and prohibition active ok [demo.northbridge.permits] tests/permits.lawtest / classification: central resident from a large household ok [demo.northbridge.permits] tests/permits.lawtest / classification: not from the centre — no match ok [demo.northbridge.permits] tests/permits.lawtest / presumption holds by default ok [demo.northbridge.permits] tests/permits.lawtest / evidence rebuts the presumption ok [demo.northbridge.permits] tests/permits.lawtest / fiction: notice deemed received ok [demo.northbridge.permits] tests/permits.lawtest / constraint reports the violation ok [demo.northbridge.permits] tests/permits.lawtest / set ignores duplicates: three times 10 — total 10 ok [demo.northbridge.permits] tests/permits.lawtest / list counts duplicates: three times 10 — total 30 ok [demo.northbridge.permits] tests/permits.lawtest / duty satisfied ok [demo.northbridge.permits] tests/permits.lawtest / duty violated ok [demo.northbridge.permits] tests/permits.lawtest / outcome unknown — no violation ok [demo.northbridge.permits] tests/permits.lawtest / maintenance holds while the window is open ok [demo.northbridge.permits] tests/permits.lawtest / maintenance violated by counterexample ok [demo.northbridge.permits] tests/permits.lawtest / without a certificate the post-window outcome is unknown ok [demo.northbridge.permits] tests/permits.lawtest / power exercised lawfully ok [demo.northbridge.permits] tests/permits.lawtest / power without grounds does not operate ok [demo.northbridge.permits] tests/permits.lawtest / liberty and immunity итого: 28 проверено, 28 прошли, 0 не прошли, 0 не исполнены; код 0 ``` The package also passes the static check: ```sh law engine check packs/examples/language-demo/permits/package.law ``` ```text check OK: packs/examples/language-demo/permits/package.law ``` Six of the 28 tests carry this article: | Test | Evaluates | Expects | |---|---|---| | `definition: central resident` | `central_resident(ann)` | `TRUE_ONLY` | | `aggregate: large household` | `large_household(ann)`, 4 members | `TRUE_ONLY` | | `classification: central resident from a large household` | `central_senior(ann)` | `TRUE_ONLY` | | `classification: not from the centre — no match` | `central_senior(bob)`, outer zone | `NEITHER` | | `set ignores duplicates: three times 10 — total 10` | `fee_total_set(ann, 10 EUR)` | `TRUE_ONLY` | | `list counts duplicates: three times 10 — total 30` | `fee_total_all(ann, 30 EUR)` | `TRUE_ONLY` | Ann is resident, lives in the centre, and has four recorded household members (`m1`–`m4`), so the definition holds, the count reaches the threshold of 4, and the classification fires. Bob lives in the outer zone, so the classification body fails — and the answer is `NEITHER` (no label derived), not a refusal (see [nb-02: Why a missing fact is not a refusal](/tutorials/northbridge/nb-02-missing-fact/) for the status meanings). The last two rows decide the cashier's question: the same three receipts total `10 EUR` under Set collection and `30 EUR` when duplicates are kept. The "Changed condition" section unpacks why. All 28 tests pass: every answer matched its expectation. The summary is in Russian: 28 checked, 28 passed, 0 failed, 0 skipped, exit code 0. The first line names the worlds under test (`мир` is "world"): the permits package together with the calculations and vocabulary packages it reads. ## Why this construct The task is to decide group membership from combinations of conditions (district plus household size), and to fold repeated records into totals where duplicates may or may not count. A definition names one reusable condition instead of repeating the conjunction in every rule; a classification stacks such conditions into one group label; an aggregate turns "how many / how much" into a value the rule can compare or store. Each does exactly one of the three jobs. Writing the district conjunction inline in every rule computes the same truth but duplicates the condition — one district rename then touches every rule. Writing the household-size check as four separate member premises cannot express "at least four" without enumerating combinations; `count` states the threshold directly. Totalling payments with one shared receipt fact instead of three cannot distinguish "paid once" from "paid three times". The proof is the six tests above — each membership verdict and each total is executed by `law test`, including the pair that differs only in the collector keyword (`10 EUR` vs `30 EUR`). What they do not prove: that central-district large households *deserve* priority, or that three receipts mean three real payments. The tests prove the machine classifies and totals the recorded facts as declared; they say nothing about office policy or the honesty of the ledger. All persons and payments are fictional data. ## Changed condition Change one keyword: `collect` → `collect all`. The input is identical in both tests — three `fee_payment` facts, each `10 EUR`, differing only in receipt number (`1`, `2`, `3`): - `fee_total_set(ann, 10 EUR)` is `TRUE_ONLY`: the Set collector merges the three identical `10 EUR` entries into one, and `sum` folds a single entry — total `10 EUR`. - `fee_total_all(ann, 30 EUR)` is `TRUE_ONLY`: the duplicate-preserving collector keeps all three entries, and `sum` adds `10 + 10 + 10` — total `30 EUR`. Same facts, same aggregate function, different collector, different total — and the test names state the semantics outright ("set ignores duplicates", "list counts duplicates"). ## Typical mistake Totalling repeatable payments with a plain `collect`. A newcomer copies the household-count pattern (`count(collect ...)`, where Set semantics is correct — four distinct members are four members) into the cashier's sum. The observable consequence: three recorded 10 EUR instalments total `10 EUR` instead of `30 EUR`, and the office would credit Ann one third of what she paid. The fix is to match the collector to the question. Set collection answers "which distinct things exist?" (members, districts, categories) — use plain `collect`. Adding up events that may repeat (payments, deliveries, readings) needs `collect all`, or duplicates silently vanish. When in doubt, write the pair as this package does — one rule per collector — and let the two tests disagree loudly. ## Limits - **Shorthands derive, they do not evidence.** A definition and a classification restate given facts under a new name; neither creates evidence nor overrides a missing fact. Bob's `NEITHER` is absence of a label, not a verdict against him. - **Aggregates need bound collections.** `count` and `sum` fold the stated `where` selection — members of one applicant, payments of one applicant — never the whole register. An unbound or wrongly scoped selection folds the wrong rows, and no warning repairs the scope. - **`sum` over `Money` keeps the currency.** `10 + 10 + 10 EUR` is `30 EUR`, exactly (cf. [nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/)); mixed currencies do not silently merge. - **Verified profile:** engine `law 0.1.0`, semantics `law.core/0.2`, std `0.2.0`. Set-versus-duplicate-preserving behavior is a fact about this implementation, not a claim about collection semantics in every language. ## Exercise Without running the engine, predict, then check with `law test`: 1. Ann is a central resident with four recorded household members. What is the status of `truth(central_senior(ann))`, and which two package elements must both hold for it? 2. Bob is a resident living in the outer zone. What is the status of `truth(central_senior(bob))` — `TRUE_ONLY`, `FALSE_ONLY`, `NEITHER`, or `BOTH` — and which test name confirms it? 3. Three receipts of `10 EUR` each: what does the Set collector total, and what does the duplicate-preserving collector total? Which two test names pin the two numbers? 4. Ann pays `10 EUR` twice (two receipts) instead of three times. What would the two totals become, and why does the Set total stay unchanged while the other moves? Write down each prediction first; run the suite for questions 1–3 and explain any miss in one sentence. Question 4 has no suite test — answer it by reasoning from the Set-vs-`collect all` rule, then compare with the checkable solution: [the nb-05 solution](/tutorials/northbridge/solutions/nb-05-solutions/). ## Sources - Teaching package: `packs/examples/language-demo/permits/package.law` (definition `central_resident`, rule `LargeHousehold`, classification `central_senior`, rules `FeeTotalSet`, `FeeTotalAll`; threshold `LARGE_HOUSEHOLD` from `packs/examples/language-demo/calculations/package.law`). - Scenarios: `packs/examples/language-demo/permits/tests/permits.lawtest` (`definition: central resident`, `aggregate: large household`, `classification: central resident from a large household`, `classification: not from the centre — no match`, `set ignores duplicates: three times 10 — total 10`, `list counts duplicates: three times 10 — total 30`). - Level 1 — Northbridge use: this article (fictional priority shortlist and fee ledger). - Level 2 — domain template: when group membership combines a place condition with a size threshold, name the place with a definition, count the members with a Set aggregate, and stack both under one classification; when totalling repeatable events, sum with `collect all` and keep a Set-collector rule beside it wherever distinctness (rather than volume) is the question. - Level 3 — confirmed external formalization: guardian's transactions for an incapacitated person (Civil Code of Kazakhstan, art. 26(2)) — package `kz.corpus.civilcode`, `corpus/laws/kz/codes/civil-code/02b-opeka-i-popechitelstvo.law:121-137`, construct entitlement lowered to a conditional `power` (`SovershatSdelkiOtImeniNedeesposobnogo` with holder/over/exercise/ effect/`valid_when`). A person-class consequence gated by conditions, with every head variable bound in the rule body — the same quantified-rule discipline as `LargeHousehold` and the fee-total rules; and no duty to transact exists, so the entitlement is a conditional power, not an obligation.
Evidence and scope of the external example Documented in `docs/research/constructs/14-claim-entitlement-correlatives/corpus-forms.en.md`, section 2 (rated exemplary there). Verification confirms the named construct at the cited lines only, by direct source read; it claims nothing about deployment, runtime behavior, or legal correctness.