1
0
Files

138 lines
7.1 KiB
Python

"""Lēmuma punkta strukturēšana ar noteikumiem (bez valodas modeļa): darbības veids, adresāti un termiņš.
Punkta teksts paliek burtiski; šie lauki ir tikai norādes, kas izgūtas no teksta:
kind — pēc punkta sākuma darbības vārda (Pieņemt zināšanai, Atbalstīt, Uzdot …);
parties — institūcijas, kas tekstā minētas datīvā („Ekonomikas ministrijai”; ne „… pašvaldībai piederošs”), ar VPK ID,
un pašvaldības ar ATVK kodu no Pašvaldību reģistra;
deadline — „līdz 2026. gada 10. novembrim” → 2026-11-10.
Ja kaut ko nevar droši noteikt, lauks paliek tukšs — neko nemin.
"""
import re
from lxml import etree
MONTHS = {"janvārim": 1, "februārim": 2, "martam": 3, "aprīlim": 4, "maijam": 5, "jūnijam": 6, "jūlijam": 7,
"augustam": 8, "septembrim": 9, "oktobrim": 10, "novembrim": 11, "decembrim": 12}
RE_DATE = re.compile(r"līdz\s+(\d{4})\.\s*gada\s+(\d{1,2})\.\s*(" + "|".join(MONTHS) + r")", re.I)
RE_DATE2 = re.compile(r"līdz\s+(\d{1,2})\.(\d{1,2})\.(\d{4})\.?")
# darbības vārds → darbības veids. Meklē pirmo no šiem vārdiem punkta tekstā (ārpus pēdiņām), jo punkts var sākties
# ar pamatojumu („Saskaņā ar … likuma 4. pantu atļaut …”, „Lai nodrošinātu …, atbalstīt …”).
KINDS = [
("pieņemt zināšanai", "note"), ("zināšanai", "note"), ("ņemt vērā", "note"),
("atzīt par spēku zaudējušu", "repeal"), ("atcelt", "repeal"),
("pieņemt", "adopt"), ("apstiprināt", "approve"), ("atbalstīt", "support"), ("neatbalstīt", "reject"),
("aicināt", "invite"), ("uzdot", "task"), ("piešķirt", "allocate"), ("atļaut", "permit"), ("pilnvarot", "authorise"),
("noteikt", "determine"), ("atzīt", "determine"), ("izteikt", "amend"), ("papildināt", "amend"), ("svītrot", "amend"),
("izdarīt", "amend"), ("grozīt", "amend"), ("nodot", "transfer"), ("pārņemt", "transfer"), ("izveidot", "establish"),
("iecelt", "appoint"), ("atbrīvot", "dismiss"), ("pirkt", "purchase"), ("iegādāties", "purchase"),
]
KIND_VALUES = sorted({k for _, k in KINDS} | {"task", "part", "provision"})
def dative_word(w):
lw = w.lower()
for a, b in (("ija", "ijai"), ("ais", "ajam"), ("ā", "ajai"), ("is", "im"), ("us", "um"), ("a", "ai"), ("e", "ei"),
("s", "am"), ("š", "am")):
if lw.endswith(a):
return w[: len(w) - len(a)] + b
return None
def dative(name):
"""„Valsts ieņēmumu dienests” → „Valsts ieņēmumu dienestam”; „Centrālā finanšu un līgumu aģentūra” → „Centrālajai finanšu un
līgumu aģentūrai”. Locās galvenais vārds (pēdējais) un noteiktās galotnes īpašības vārdi (-ais, -ā); ģenitīva vārdi paliek."""
words = name.split()
d = dative_word(words[-1])
if not d:
return None
out = [dative_word(w) if re.search(r"(ais|ā)$", w.lower()) else w for w in words[:-1]] + [d]
return " ".join(out)
def title_case(name):
"""Reģistrā budžeta iestāžu nosaukumi ir lielajiem burtiem; salīdzināšanai — mazajiem."""
return name.lower()
class Registers:
"""Valsts institūciju reģistrs (VPK ID) un Pašvaldību reģistrs (ATVK) → datīva formas."""
def __init__(self, org_xml, pas_xml):
self.forms = [] # (forma mazajiem burtiem, 'org'|'atvk', id)
r = etree.fromstring(org_xml)
for o in r:
if not isinstance(o.tag, str) or etree.QName(o).localname != "Organization" or o.get("validTo"):
continue
name = next((c.text for c in o if etree.QName(c).localname == "Name"), "") or ""
name = re.sub(r"^LATVIJAS REPUBLIKAS ", "", name)
if o.get("kind") in ("group", "office"):
continue
d = dative(title_case(name))
if d:
self.forms.append((d, "org", o.get("id")))
if o.get("kind") == "ministry" and title_case(name).endswith(" ministrija"):
stem = title_case(name)[: -len("ministrija")]
self.forms += [(stem + "ministram", "minister", o.get("id")), (stem + "ministrei", "minister", o.get("id"))]
self.forms.append(("ministru prezidentam", "org", "00-0001"))
p = etree.fromstring(pas_xml)
for m in p:
if not isinstance(m.tag, str) or etree.QName(m).localname != "Municipality":
continue
name = next(c.text for c in m if etree.QName(c).localname == "Name")
if m.get("kind") == "novads":
stem = name[: -len(" novads")]
forms = [f"{stem} novada pašvaldībai"]
else:
gen = name + "s" if name[-1] in "ae" else name
forms = [f"{gen} valstspilsētas pašvaldībai", f"{gen} pašvaldībai"]
for f in forms:
self.forms.append((f.lower(), "atvk", m.get("atvk")))
# garākās formas pirmās, lai „Valsts ieņēmumu dienestam” nesakristu ar īsāku formu tās iekšienē
self.forms.sort(key=lambda x: -len(x[0]))
def parties(self, text):
low, found, taken = text.lower(), [], []
for form, kind, ident in self.forms:
for m in re.finditer(r"(?<![\wāčēģīķļņšūž])" + re.escape(form) + r"(?![\wāčēģīķļņšūž])", low):
if any(m.start() < e and s < m.end() for s, e in taken):
continue
if re.match(r"\s+piederoš", low[m.end():]): # „pašvaldībai piederošo īpašumu” — piederība, ne adresāts
continue
taken.append((m.start(), m.end()))
found.append((m.start(), kind, ident, text[m.start():m.end()]))
seen, out = set(), []
for _, kind, ident, printed in sorted(found):
if (kind, ident) not in seen:
seen.add((kind, ident))
out.append((kind, ident, printed))
return out
def kind_of(text, parties, is_child=False):
"""Darbības veids. Apakšpunkts, kas turpina augstākā punkta teikumu (sākas ar mazo burtu), ir „part”."""
if is_child and text[:1].islower():
return "part"
low = re.sub(r'"[^"]*"|„[^”]*”', " ", text.lower())
low = re.sub(r"^lai [^,]*,\s*", "", low)
if parties and any(low.startswith(p[2].lower()) for p in parties):
return "task" # „Ekonomikas ministrijai … iesniegt …”
if re.match(r"^[a-zāčēģīķļņšūž ]+(ministram|ministrei|ministrijai|kancelejai|pašvaldībai)\b", low):
return "task"
best = None
for verb, k in KINDS:
m = re.search(r"(?<![a-zāčēģīķļņšūž])" + verb + r"(?![a-zāčēģīķļņšūž])", low)
if m and (best is None or m.start() < best[0]):
best = (m.start(), k)
return best[1] if best else "provision"
def deadline(text):
m = RE_DATE.search(text)
if m:
return f"{int(m.group(1)):04d}-{MONTHS[m.group(3).lower()]:02d}-{int(m.group(2)):02d}", m.group(0)
m = RE_DATE2.search(text)
if m:
return f"{int(m.group(3)):04d}-{int(m.group(2)):02d}-{int(m.group(1)):02d}", m.group(0)
return None, None