# 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. ```law 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 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](/tutorials/packages/): ```law 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: ```law 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 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. ```law 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 ```law 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`: ```law 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](/tutorials/a3/). ## 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 A head the computer lacks. Replace `scale_total` in `LateFee`'s body with `scale_sum`: ```text 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. ## Next 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](/tutorials/a3/).