# Expressions and quantities: pitfalls Wrong forms, silent outcomes, `LDC-E` diagnostics, typical mistakes of formalizers and AI agents, how to detect them. ## 1. Sum smaller than expected on equal summands `sum(collect a: Money …)` counts distinct values (`Set`); two equal incomes are one element, and the sum silently undercounts: no refusal, no warning. Write `collect all a: Money, m: Employee where …` — the second variable tells apart substitutions with equal values. Statics cannot see the difference: guard it with a test on two equal facts. The `research.expressions.counted_threshold` example deliberately gives three members equal 50 000 KZT payouts — the summary must be 150 000, not 50 000. ## 2. Generator name unbound `LDC-E1317`: in `collect all a: Money where household_member(h, m) …` the variable `m` comes from nowhere — declare it as a second comprehension binder (`collect all a: Money, m: Employee where …`). ## 3. Threshold of the wrong kind `n >= 10.5` with `n: Integer`, `Integer + Rational`, `Rational + Decimal`, `Date == Instant` — `LDC-E2108`. The threshold in the fact's kind; take a share one way for the whole computation. ## 4. Money divisor — a whole number `Money / Integer`, `Integer / Decimal` — `LDC-E2108`: a money divisor is `Decimal` (`12.0`, not `12`); same for `div_round`. A variable divisor `n: Integer` is lifted by multiplying `n * 1.0`, not by a separate "share" fact: feeding a share as a case fact takes the act's prescribed computation out of the act. ## 5. Rounding without a policy `round(x)` without `precision`/`mode` — `LDC-E2116`: both arguments mandatory, `mode` — the `"HALF_UP"` string. Type `Percentage` — `LDC-E2101`: a percent constant is declared `Decimal` (`const BONUS_RATE: Decimal = 25 percent`), because `25 percent` is the exact `Decimal` `0.25`. ## 6. Silent execution refusals instead of statics `g < Senior`, `500 kg > 100 m`, `1000 KZT + 5 USD` — `check` stays silent, the refusal comes at execution (`TYPE_ERROR`, `CURRENCY_MISMATCH`): the rule does not fire, the case carries an issue. Guard with a scenario test: a positive test shows the norm fires, a negative one shows the silence is exactly `NEITHER`, not a refusal. ## 7. Sum over an empty list is not zero `sum` over an empty `List` is `EMPTY_AGGREGATE`, not `0 KZT`: an empty sum has no currency, and the core does not invent one. Three resolutions: a norm-side policy — `sum(collect all …, empty: 0 KZT)`; an explicit aloud refusal — `empty: error` (indistinguishable in CLIR from no policy); the zero case by a second rule with `not_known` or closure. The policy kind must match the element kind: `empty: 0` over a `Money` collection — `LDC-E2119`, not "zero in tenge". `empty` on `count` and `precision`/`rounding`, not on `average` — `LDC-E2120`. ## 8. Division with rounding in two steps `round(base * div_round(a, b, 6, …), 2, …)` is double rounding, diverging from the norm: rounding the share separately and then the total is a mistake, not caution. The rounding is one — over the exact result (`div_round(base * (a / 1 KZT), b / 1 KZT, p, mode)`). A decimal share approximation (`s * 0.6667`) is the same mistake one class down: 30 000 × 0.6667 = 20 001, not 20 000. ## 9. Bare domain constant instead of a declaration A name in term position resolving to no declaration is an unresolved name, static refusal `LDC-E1330`: a "bare domain constant" is not a term form. An individual is named only by declaration: `const KZ: Jurisdiction = entity_ref("…")` — a constructor gives the value, `const` gives the name. AI agents love writing jurisdiction and currency names straight into terms — caught only by this check. ## 10. Cyclic constant A constant through itself is a static error, not a value: `CYCLIC_CONSTANT` before any conclusion. A constant through a constant is legal and unfolds recursively; a cycle is not. ## 11. AI-agent traps - Writing `Money / Integer` ("sum over a month count") instead of `div_round(s, n * 1.0, p, mode)` — item 4. - Declaring a percent with type `Percentage` instead of `Decimal` — item 5. - Counting `sum(collect …)` as a charge summary instead of `collect all` — item 1. - Converting units by division (`x / 3.6`) instead of `convert(value, num, den, unit)`; attaching a tag by multiplication instead of `with_unit`. - Expecting `round(s * (2 / 3), …)` to save the total: the product fails with `INEXACT_DIVISION` before `round` ever sees it — the norm's exact fraction needs `div_round(s * 2, 3.0, p, mode)`. ## 12. Off-grammar pattern and division by zero (from page 08-expressions) - `text_matches(t, …)` with other than two arguments — `LDC-E2103`; a literal pattern outside the pattern grammar — `LDC-E2117`. - `/` and `mod` by zero — `DIVISION_BY_ZERO`: an execution refusal, not a static one — the rule does not fire, the case carries the issue. - Inexact money and quantity quotients without a policy (`100 KZT / 3.0`, `100 KZT * (1 / 3)`) — `INEXACT_DIVISION`: divide under `div_round` with an explicit policy (see item 8).