Open Standard · Working Draft
mekaniskt·kontrakt

Worked examples ·  04 of 07

School renovation

A municipality's school renovation under AB 04-family terms, exercising milestone payments, retention, delay penalties, weekly ÄTA changes, an independent inspector, a back-to-back subcontract, and multi-year index adjustment at once.

SIZE
very complex
PROFILE
mk-se-public
STRESSES
Every section of the spec at once: milestones, retention, vite, ÄTA change management, a back-to-back child contract, index math, and a third-party inspector.

A kommun renovates a school: totalentreprenad, AB 04-family terms, a milestone betalplan, materials arriving by despatch advice, ÄTA changes landing weekly, an independent besiktningsman deciding when done is done, 10% retention until approval, vite per started week of delay, a subcontractor back-to-back underneath, and an entreprenadindex adjusting prices across two years. This is the stress test where every section of the spec must show up for work at the same time. It mostly holds; three things creak.

Profile: mk-se-public. Prose backbone: AB 04-style allmänna bestämmelser as the soft-term layer (hashed bilaga); the executable layer below implements the administrativa föreskrifter.

The contract (abridged to the load-bearing blocks)

mechanical-contract v0
id:       mk-se-2027-001900
title:    Totalentreprenad ombyggnad Parkskolan
prose:    kontrakt-parkskolan.pdf sha256:...     # AB 04-family AF + kontrakt
annex:    [ "AB 04 (soft layer)", "§X ansvar och försäkring", "force majeure" ]

parties:
  beställare:      org 212000-1355 "Lunds kommun"      sign eIDAS-qualified-seal
  entreprenör:     org 556111-2233 "ByggBolaget AB"    sign eIDAS-qualified-seal
  besiktningsman:  APPOINTED, not signed at genesis     # the third-party
    slot: accredited per art:rise-certified-inspectors  # approver - a party bound
    appointed-by: both, via inspector.appoint port      # by later mutual event

money: currency SEK  vat 25%  rounding per-line half-up 0.01

state:
  pool kontraktssumma    capacity 24_000_000 SEK
       stages available|reserved|retained|disbursed     # 'retained' is not
                                                        # in §3.4's stage set
  pool ata.budget        capacity 2_400_000 SEK         # pre-authorized ÄTA
                                                        # headroom (10%) as a POOL,
                                                        # not per-change transforms
  register deliveries    shape despatch-line
  register milestones    from betalplan
  register ata-orders    shape { scope: text, amount: money, approved-by: id }

fixings:
  index e84   series SCB.entreprenadindex.E84   base 2027-01   signed-by scb-ingest

artifacts:
  betalplan       class contract-bilaga   pin v1    # milestone rows -> event-dues (§3.4)
  apd-plan        class contract-bilaga   pin v2    # soft: site plan, hashed
  ue-contract     mk-se-2027-001901       pin sha256:...   # the back-to-back child

ports:
  in  material.delivery   from entreprenör   payload peppol.despatch-advice   # BEAst-
                                                                    # profiled in practice
  in  milestone.claim     from entreprenör   payload signed-claim
  in  ata.propose         from entreprenör   payload signed-scope+price
  in  ata.approve         from beställare.projektledare  payload signed-decision
  in  inspector.appoint   from both          payload signed-appointment       # appointed slot
  in  besiktning.utlåtande from besiktningsman  payload signed-inspection     # slut/garanti
  in  invoice.submit      from entreprenör   payload peppol.bis-billing-3
  in  payment.confirm     from beställare    payload iso20022.pain.002
  out receipt.emit        to submitter       always
  out notice.emit         to both            on rule-alerts

approval:
  milestone.claim:   require besiktningsman utlåtande for slutmilstolpe,
                     else 1 of beställare.projektledare
  ata.propose:
    if amount <= 100_000 SEK and ata.budget remaining >= amount
                       -> require 1 of beställare.projektledare   # an EVENT,
    otherwise          -> require transform (ändringsavtal)       # not an amendment
                                                                  # - until the pool
cadence:                                                          # or bounds run out
  invoice.submit     at-most 1 per month
  due payment        within 30 days of invoice accept
  due färdigställande by 2028-08-01                     # kontraktstiden
    breach: vite 0.5% of kontraktssumma per started week, max 10%   # computed from
                                                        # the log: (slutbesiktning
rule on milestone.claim:                                # godkänd date - due date)
  if no matching milestone in betalplan       -> reject "not in betalplan"
  if milestone requires deliveries not in register -> reject "no delivery behind claim"
  amount := milestone.amount indexed-by e84 at claim.period      # index math, §2 style
  reserve kontraktssumma amount * 90% -> due disburse within 30d # 90% flows
  reserve kontraktssumma amount * 10% -> retained                # 10% held
  accept

rule on ata.approve:
  if ref not in ata-orders proposed           -> reject "unknown ÄTA"
  draw ata.budget amount                      # headroom consumed, receipted
  record ata-orders approved; extend milestones per payload   # bounded, declared
  accept

rule on besiktning.utlåtande:
  which payload.result is
    godkänd:                                  # slutbesiktning approved:
      release retained -> due disburse within 30 days     # retention comes home
      open garantitid 5 years                             # AB 04-family: 5y work,
    godkänd-med-anmärkning:                               # 2y varor - the finally
      due åtgärda anmärkningar within 60 days             # ladder below
    underkänd:
      vite clock keeps running; due ny slutbesiktning
  accept

escalation:
  on obligation-breached | pool-exhausted | ata.budget exhausted:
    level 1  projektmöte both        resolve-within 10 working-days
    level 2  styrgrupp               20 working-days, milestone.claim suspended
    level 3  mediation per prose     draws frozen, receipts keep flowing
  finally:
    garantiåtagande     5y arbete / 2y varor per prose    # survival obligations:
    garantibesiktning   due within garantitid per prose   # outlive termination
    försäkring + ansvar per AB 04 soft layer

# ── the back-to-back (a second contract, referenced) ─────────────
# ue-contract mk-se-2027-001901: elentreprenad
#   ports: in order.receive from contract mk-se-2027-001900 port ue.order (§7.1)
#   terms: mirrors upstream milestones BY HASH REFERENCE - flow-down without
#          hand-copying; its own vite, its own escalation
#   saga:  HE milestone underkänd -> UE rectification obligation opens; compensation
#          legs declared per §7.3 - no distributed ACID, receipts embed foreign
#          receipt hashes, the dispute is two chains laid side by side

What this example stresses

Edge Spec section
Milestones as event-dues from a betalplan artifact §3.4
Fork/join: slutbesiktning godkänd = one event releasing retention AND opening garantitid AND closing vite §3.7
Third-party approver (besiktningsman) with competence-register binding §4.1, §8.5
Change management at real-world cadence: ÄTA pool vs transforms §6.4
Back-to-back child contract, saga compensation §7.1, §7.3
Index math over multi-year validity §2, §5
Vite + retention computed purely from the log §3.2, §3.4
Soft-term layer carrying AB 04 where “skäligt” lives §2 three tiers

Questions this example forced

  • ÄTA volume vs amendment ceremony. A real project produces tens to hundreds of ÄTA. §6.4 says amendments are signed transforms - correct for structure, absurd at ÄTA cadence (nobody re-signs the contract weekly). Proposed resolution, adopted in the example: anticipated change classes are declared as ports + pools at genesis (an ata.propose/approve port pair drawing a bounded ata.budget pool with per-item approval thresholds); transforms are reserved for changes the contract did NOT anticipate (scope restructuring, party change, bounds exhausted). The boundary is crisp and lintable: within declared headroom = event; beyond = transform. This also mirrors legal reality - ÄTA rights ARE contract terms in AB 04, not amendments.
  • The appointed third party. The besiktningsman is neither party, signs no genesis, yet holds an approval slot that gates 2.4M SEK of retention. Proposed: a declared party slot - typed (competence register binding, §5.6), filled by a later mutual event (inspector.appoint), replaceable the same way. Genesis binds the slot’s constraints; the appointment binds the person. Same pattern serves mediators (level 3) and the beneficiary discussion - the party model wants roles: signatory | appointed | beneficiary | executor. Needs a §-home in the spec.
  • Pool stages are not a closed set. §3.4 names available|reserved|disbursed; retention needs retained, escrow wanted its own, and factoring will want assigned. Proposed: stages are declared per pool (free names, declared legal transitions, linter checks conservation across every transition path) with available|reserved|disbursed as the profile default. Cheap generalization, but it touches the §3.4 vocabulary.

The verdict this example was built to deliver: complexity composes without new machinery. Milestones, retention, vite, ÄTA, back-to-back, index, inspection - every one lands on an existing primitive or a declared parameterization of one. The three creaks are all in the same place: the spec’s vocabularies (stages, party roles, change classes) are slightly too closed for the biggest contracts. Loosen by declaration, never by computation.