1
0
Files
Budget-as-Code/tools/calc.py
Rihards Gailums 71250e7b0e Valsts budžets kā kods v0.1: shēma, 2025. un 2026. gada budžeta likumi kā dati
Loģisks budžeta modelis (Source, Provision, Parameter, Actor, Purpose, Indicator, Rule, ClassItem, Allocation),
nevis likuma pielikumu izkārtojuma kopija. Abi likumi: teksts un visi 12 pielikumi.
Atpakaļsaderība: 2026 — 58 457 no 58 457, 2025 — 55 677 no 55 677 pielikumos drukāto skaitļu atjaunoti tikai no XML.
Avoti (likumi.lv, klasifikāciju MK noteikumi), rīki, datu līgums B15, dokumentācija.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016679RwmHsuTFfxt26wP6rk
2026-10-10 22:34:28 +00:00

91 lines
3.9 KiB
Python

"""Shared summation rules for the budget model (used by the parser for residuals and by the verifier).
A printed number in the law is reproduced by summing Allocation records that match its selector:
fund, flow, year, nature, untilEnd, purpose subtree, block, commitment kind, and classification codes.
Consolidation: a node's total includes an intra-fund transfer only if that node itself prints the transfer's code;
otherwise intra-fund transfers are eliminated (they cancel out inside the node).
"""
import collections
def kind_stem(k):
"""Commitment-kind codes are hierarchical in 2-digit groups: 0101100000 -> 010110."""
s = k or ''
while len(s) > 2 and s.endswith('00'):
s = s[:-2]
return s
class Calc:
def __init__(self, allocs, purpose_parent, cls_parent, intra_codes, blockcodes, purpose_holder=None):
self.purpose_parent = purpose_parent
self.purpose_holder = purpose_holder or {}
self.cls_parent = cls_parent
self.intra = intra_codes
self.blockcodes = {k: {f: set(v) for f, v in d.items()} for k, d in blockcodes.items()}
self.idx = collections.defaultdict(list)
for a in allocs:
self.add(a)
def chain(self, p):
out = set()
while p and p not in out:
out.add(p)
p = self.purpose_parent.get(p)
return out
def code_chain(self, k):
out = set()
while k and k not in out:
out.add(k)
k = self.cls_parent.get(k)
return out
def add(self, a):
a['_pchain'] = self.chain(a.get('purpose'))
a['_ck'] = f"{a['scheme']}:{a['code']}"
a['_cchain'] = self.code_chain(a['_ck'])
a['_intra'] = bool(a['_cchain'] & self.intra)
self.idx[(a['fund'], a['flow'], int(a['year']), a['nature'])].append(a)
def total(self, fund, flow, year, nature, purpose=None, block=None, codes=None, ckind=None, untilEnd=False,
blk=None, ekk_prefix=None, consolidated=None, holder_view=False):
"""holder_view: also count budget-level amounts (no purpose) whose holder administers the selected purpose."""
printed = self.blockcodes.get(blk, {}).get(flow) if blk else None
if consolidated is None:
consolidated = purpose is None
codes = set(codes) if codes else None
s = 0.0
for f in ([fund] if fund else ['basic', 'special']):
for a in self.idx.get((f, flow, int(year), nature), ()):
if a.get('partOf'):
continue
if bool(a.get('untilEnd')) != bool(untilEnd):
continue
if purpose and purpose not in a['_pchain']:
if not (holder_view and not a.get('purpose') and a.get('holder') and a.get('holder') == self.purpose_holder.get(purpose)):
continue
if block and a.get('block') != block:
continue
if ckind and not (a.get('commitmentKind') or '').startswith(kind_stem(ckind)):
continue
if ekk_prefix and not (a['scheme'] == 'ekk' and a['code'].startswith(ekk_prefix)):
continue
ck, cc = a['_ck'], a['_cchain']
if printed is not None:
shown = ck in printed
if not shown and a['_intra']:
continue
if codes is not None:
if not (ck in codes or (not shown and cc & codes)):
continue
elif codes is not None:
if not (cc & codes):
continue
if consolidated and a['_intra'] and ck not in codes:
continue
elif consolidated and a['_intra']:
continue
s += float(a['amount'])
return s