docs← Back to article

Markdown for LLMs

Shared computation as a dependency

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# 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/).