Shared computation as a dependency
The archive’s overdue fee counts by tiers: the first two thousand tenge
of the base in full, everything above at half. The archive did not invent
the progressive scale: income tax, duties, and tariffs count the same
way. Whoever rewrites it in each act repeats the same computation with
different mistakes. Hence the tree holds shared applied computations in
the shared-computation tree: five packages of the calc.*
family — tiers, quotas, obligations, accruals, allocation —
with no jurisdiction, no source, and no [metadata]. The scale is
applied by calc.tiers; its real consumer in the corpus is the Tax Code
of Kazakhstan, three scales of article 363.
The boundary between the computer and the act runs along law, and it is
this page’s point: the computer receives conditions explicitly and
returns the calculation; norms, rates, tier bounds, calendar, and
rounding stay with the subject package. calc.tiers knows not one
number of the conditions.
language "law.core" version "0.2";package tutorial.archive version "0.15.0";namespace "urn:law:tutorial:archive";
import tutorial.city version "0.1.0";import calc.tiers version "0.1.0";
entity Person;
relation billing_year(p: Person, year: Integer) kind empirical { key(p); }relation days_overdue(p: Person, days: Integer) kind empirical { key(p); }relation late_fee_base(p: Person, base: Money) kind institutional { key(p); }relation late_fee(p: Person, amount: Money) kind institutional { key(p); }
const LATE_FEE_CAP: Money = 5000 KZT;The base from a foreign act, the scale from the computer
Section titled “The base from a foreign act, the scale from the computer”The archive computes the fee base itself: overdue days multiply the city’s daily charge, read from the foreign act as on the last page:
rule LateFeeBase strict { label ru-KZ unofficial "База платы за просрочку: дни просрочки по дневной плате города на год счёта"; for p: Person; for d: Integer; for y: Integer; for unit: Money; when days_overdue(p, d) and billing_year(p, y) and tutorial.city::late_fee_unit(y, unit); then late_fee_base(p, d * unit);}The label reads: “Overdue-fee base: overdue days at the city daily charge for the billing year”.
calc.tiers opens its API with relations: for input it takes the scale,
its mode and rounding, tiers with bounds and rates, and a “apply the
scale to the base” request; for output it gives the total, the base
split across tiers, and each tier’s contribution. The archive declares
the scale and tiers as constants — individuals of the computer’s types:
const LateFeeScale: calc.tiers::Scale = entity_ref("urn:law:tutorial:archive#LateFeeScale") { label ru-KZ unofficial "шкала платы за просрочку";};const LateFeeFullTier: calc.tiers::Tier = entity_ref("urn:law:tutorial:archive#LateFeeFullTier") { label ru-KZ unofficial "ступень полного размера: база до 2 000 тенге";};const LateFeeHalfTier: calc.tiers::Tier = entity_ref("urn:law:tutorial:archive#LateFeeHalfTier") { label ru-KZ unofficial "ступень половинного размера: база свыше 2 000 тенге";};const LATE_FEE_FULL_UPTO: Money = 2000 KZT;The labels read: “overdue-fee scale”; “full-size tier: base up to 2,000 tenge”; “half-size tier: base above 2,000 tenge”.
The act writes the scale conditions
Section titled “The act writes the scale conditions”The archive writes each condition as an ordinary rule with a head in
a foreign pub relation. That is what the computer/act boundary looks
like in code: rules set the Graduated mode (each tier’s rate applies to
its own part of the base), the scale floor, the stage and policy of
rounding, two tiers with bounds and rates.
rule LateFeeScaleMode strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::scale_mode(LateFeeScale, calc.tiers::Graduated);}
rule LateFeeScaleFloor strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::scale_floor(LateFeeScale, 0 KZT);}
rule LateFeeScaleRoundingStage strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::scale_rounding_stage(LateFeeScale, calc.tiers::TotalOnly);}
rule LateFeeScaleRoundingPolicy strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::scale_rounding_policy(LateFeeScale, 0, "HALF_UP");}
rule LateFeeFullTierOf strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_of(LateFeeFullTier, LateFeeScale);}
rule LateFeeFullTierFrom strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_from(LateFeeFullTier, 0 KZT);}
rule LateFeeFullTierBoundKind strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_bound_kind(LateFeeFullTier, calc.tiers::Bounded);}
rule LateFeeFullTierUpto strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_upto(LateFeeFullTier, LATE_FEE_FULL_UPTO);}
rule LateFeeFullTierRate strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_rate(LateFeeFullTier, 1 / 1);}
rule LateFeeHalfTierOf strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_of(LateFeeHalfTier, LateFeeScale);}
rule LateFeeHalfTierFrom strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_from(LateFeeHalfTier, LATE_FEE_FULL_UPTO);}
rule LateFeeHalfTierBoundKind strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_bound_kind(LateFeeHalfTier, calc.tiers::OpenTop);}
rule LateFeeHalfTierRate strict { for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::tier_rate(LateFeeHalfTier, 1 / 2);}Thirteen rules for one scale is many, and deliberately so: each
condition is named, bound to a premise, and visible in the proof graph as
the archive’s rule rather than hiding in call parameters. Rates are
written as exact Rational fractions; a tier’s lower bound is included,
the upper not; the last tier is open at the top.
Apply and read
Section titled “Apply and read”rule LateFeeScaleApplied strict { label ru-KZ unofficial "Шкала применяется к базе платы за просрочку"; for p: Person; for base: Money; when late_fee_base(p, base); then calc.tiers::scale_applied_to(LateFeeScale, base);}
rule LateFee strict { label ru-KZ unofficial "Плата за просрочку: итог шкалы, но не более потолка"; for p: Person; for base: Money; for total: Money; when late_fee_base(p, base) and calc.tiers::scale_total(LateFeeScale, base, total); then late_fee(p, min(total, LATE_FEE_CAP));}The labels read: “The scale applies to the overdue-fee base” and “Overdue fee: the scale total, capped”.
| Overdue days | Base | Scale total | late_fee |
|---|---|---|---|
| 3 | 600 | 600 | 600 KZT |
| 15 | 3,000 | 2,000 + 500 | 2500 KZT |
| 60 | 12,000 | 2,000 + 5,000 | 5000 KZT — the cap |
The test from this page, byte for byte; the world is the page plus the
pinned bytes of tutorial.city and calc.tiers:
test "три дня просрочки — 600, вся база в первой ступени" { given { context { decision_time @2026-04-20T09:00:00+05:00; knowledge_time @2026-04-20T09:00:00+05:00; legal_time @2026-04-20; timezone "Asia/Almaty"; } assert billing_year(entity_ref("urn:tutorial:ivanova"), 2026) { id "assert-year"; origin case_input; } assert days_overdue(entity_ref("urn:tutorial:ivanova"), 3) { id "assert-days"; origin case_input; } } evaluate truth(late_fee(entity_ref("urn:tutorial:ivanova"), 600 KZT)); expect truth_status == TRUE_ONLY; expect evaluation_status == COMPUTED;}The test name reads: “Three days overdue — 600, the whole base in the first tier.”
The test asks only the archive’s own heads: the .lawtest runner does
not allow qualified references in a query, so the scale total is read
through late_fee, not through calc.tiers::scale_total. In the
practicum package the cap does not arrive as a constant: an A3 parameter
sets it by date of law, told on the next page.
What the computer does not decide
Section titled “What the computer does not decide”calc.tiers does not know whether the scale applies to the case, which
edition is in force, or whether the total is collectible. It silently
chooses neither rounding, nor floor, nor rates: fail to name the rounding
policy at TotalOnly and the scale proves unfit, scale_wellformed is
not derived, and the defect gets a name in scale_defect. All five
catalogue packages are arranged so: before extracting a new computation,
find its place by the computation catalogue — package, expansion,
template, core, or data — and read the family passport (calc.tiers
is P05).
The shared-computation tree is held separately: corpus scanners do not see it. When the computer gains a consumer in the corpus, it is registered by hand — with a regression family and ratchets — and the Tax Code walked this path first.
Compiler refusals
Section titled “Compiler refusals”A head the computer lacks. Replace scale_total in LateFee’s body with
scale_sum:
error LDC-E1105: calc.tiers::scale_sum: символ не экспортирован пакетом(§24: межпакетные ссылки — только на `pub`-декларации; экспортировано: …)The diagnostic reads: ‘symbol not exported by the package (cross-package references only to pub declarations; exported: …)’.
A missing enum member — calc.tiers::Progressive instead of Graduated
— is the same refusal: the computer’s export is closed by a list, and
a typo in someone else’s name does not pass silently. Both refusals
belong to the language under the practicum context.
The fee cap changes by year: those are data, not a norm, and they belong in an A3 sidecar next to the package — parameters and tables.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.