Skip to content

[ras.*]

A [ras.*] section is a resource allocation system: a named rule that does one thing, on one trigger, to the accounts of one or more groups. Together the [ras.*] sections are the water sharing plan — every credit, debit, reset, and announcement is authored here, in the model file, and nothing else moves a balance except a water user's take.

[ras.annual_reset]
targets = acc.entitlements
trigger = start_water_year(7)
action  = reset_allocation

[ras.announce]
targets = acc.entitlements
trigger = start_water_year(7)
action  = allocate(table.alloc_curve(node.paradise_dam.volume[-1, 0]))

Each section is three properties — to whom, when, and what.

Anatomy

Property Meaning
targets One or more account group references (acc.<group>), comma-separated. The action applies to every account in them.
trigger When the action fires (below).
action What it does (below).

RAS sections run at the top of every timestep, before the ordering and flow phases — so a user's orders and takes see the day's announcements. When several sections fire on the same day they run in file order, which is how priority tiers and reset-then-announce sequences are expressed. A section may sit anywhere in the file — above the nodes, below them, or beside the ones it concerns; only the order of the [ras.*] sections relative to each other matters.

Triggers

A trigger is either a calendar keyword or a dynamic expression:

Trigger Fires
every_step Every timestep.
start_month First timestep of each month.
start_year First timestep of each calendar year.
start_water_year(m) First timestep of month m (1–12) each year. m may be a literal or a const.* reference.
any expression Every timestep the expression is non-zero.

Expression triggers are level-semantic: the action applies on every step the condition holds, not once on the rising edge — "while the dam spills, forfeit 5%" is a daily debit for each day of the spill. Because a RAS runs before the flow phase, reading a value computed later in the step (a node output, a var) needs the previous-step offset: node.dam.spill[-1, 0] > 0. The exception is phase = ras var blocks — they share the top-of-step slot with the RAS sections, interleaved in file order, so an assessment written above a section reads bare: assess daily in a var, credit monthly with allocate(var.assessment.aa).

Actions

Stencilled actions apply to each target account independently. Arguments are expressions, evaluated once per firing:

Action Effect
set_full Balance → account size.
set_empty Balance → 0.
set(x) Balance → x (clamped to [0, size]).
set_fraction(x) Balance → x × size.
credit(x) Add x.
credit_fraction(x) Add x × the account's own size (negative x debits). Lets one block serve accounts of different sizes.
debit(x) Subtract x.
roll_cap(n) Roll an n-period cap: bank the closing period's debits, and credit back the debits expiring out of the window (those from n periods ago). Fired at a water-year trigger this is a rolling cap over n consecutive water years (Source's "Moving Water Year" usage limit); roll_cap(1) behaves as an annual cap.
scale(x) Multiply the balance by x.
reduce_to(x) Lower the balance to x if it is above (a carryover limit).

Every action writes only to its targets — "what can touch this account?" is answered by the sections that name it. Rules that move value between paired accounts are authored with self.pair, always writing the target side.

Per-account arguments — self

Inside an action argument — and only there — the expression may read the target account's own live state through self. The argument is then evaluated per target account, so one section can apply a per-account rule to a whole group:

[ras.co_limit]
targets = acc.entitlements
trigger = start_water_year(7)
action  = set(min(self.balance, table.co_limit(self.size)))

[ras.event_topup]
targets = acc.entitlements
trigger = node.gauge1.dsflow[-1, 0] > 500
action  = credit(clamp(80, 0, self.size - self.balance))   ; per-account headroom
Field Reads
self.balance The account's live balance at this point in the RAS sequence.
self.size The account's size.
self.allocation The account's allocation (balance + use since the last reset).
self.pair.balance / .size / .allocation The same three fields of the account's pair, from either end of the pairing. Requires every target account to be paired.

Several verbs are self sugar — set_full is set(self.size), credit_fraction(x) is credit(x * self.size), reduce_to(x) is set(min(self.balance, x)) — the named verbs stay because they carry the audit trail. The verb says what changes; the expression says by how much; writes never happen inside an expression.

Rules: self is rejected everywhere else (node properties, triggers, [var.*] definitions, [fn] bodies — write it directly in the action text); it has no history, so offsets (self.balance[-1, 0]) are errors; and allocate never takes it — an announcement is one percentage for the whole group. Without any self reference, arguments keep their evaluate-once-per-firing semantics.

The carryover recipe. End-of-water-year carryover is three composable sections in file order — grant the pools from their paired entitlements, write off on the conditions the plan names, reset the entitlements:

[ras.co_grant]
targets = acc.pools                       ; pool size = the carryover cap
trigger = start_water_year(7)
action  = set(fn.grant() * 0.9 * self.pair.balance)   ; fn.grant(): 0 in a denial year

[ras.co_writeoff_spill]
targets = acc.pools
trigger = node.dam1.level[-1, 0] >= const.fsl
action  = set_empty

[ras.ent_reset]
targets = acc.entitlements
trigger = start_water_year(7)
action  = reset_allocation                ; after the grant, in file order

A zero grant sets the pool to zero — a write-off, not a skip — and set() clamps at the pool's size, so the cap needs no extra clause.

Announcement actions implement announced allocation:

Action Effect
allocate(pct) Raise each account's allocation to pct% of its size, never lowering it. pct is a percentage (0–100; above 100 is allowed).
reset_allocation Start a new allocation period — balance and use-to-date both return to zero.

See Allocation systems for how allocate and reset_allocation combine into a working announced-allocation scheme, including priority tiers and resource assessment.

Recordable series

Series Meaning
ras.<name>.fired 1 on steps the trigger fired, else 0.
ras.<name>.pct The percentage an allocate action last announced (carried forward between firings).

Both are opt-in via [outputs], like any recorder.

Rules

  • Exactly one trigger and one action per section. Related steps are consecutive sections; there is no second ordering rule inside a section.
  • allocate takes a single target group.
  • Everything a RAS reads is an ordinary expression, so any resource assessment — storage volume, minimum inflows, a lookup curve, reserve balances — is authored, not built in.