Astraea v1.0 — multi-agent WA family-law assistant
Eight specialist agents over a 16-book verified WA law corpus (RAG with citations), per-user document vault, WA court-form PDF auto-fill, comms missions with DV safety guard, no-KYC auth, TTS. Self-hosted: Flask + SQLite + Ollama, stdlib-only RAG. Includes README, LICENSE (MIT + not-legal-advice notice), DEPLOY runbook, .gitignore.
This commit is contained in:
98
forms.py
Normal file
98
forms.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Astraea form filling — overlay the user's answers onto official WA court forms
|
||||
with pymupdf (no PDF MCP exists; pymupdf is the editor). Field maps are
|
||||
auto-detected from the flat forms and cached in forms/fieldmap.json.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import pymupdf
|
||||
|
||||
FORMS_DIR = "/opt/astraea/forms"
|
||||
FIELDMAP_PATH = os.path.join(FORMS_DIR, "fieldmap.json")
|
||||
|
||||
FORM_NAMES = {
|
||||
"fl200": {"name": "Summons (FL Divorce 200)", "file": "fl200.pdf",
|
||||
"description": "Notifies your spouse the divorce case has started."},
|
||||
"fl201": {"name": "Petition for Dissolution (FL Divorce 201)", "file": "fl201.pdf",
|
||||
"description": "The main form that starts your divorce case."},
|
||||
"fl001": {"name": "Confidential Information (FL All Family 001)", "file": "fl001.pdf",
|
||||
"description": "Private contact/safety info for the court."},
|
||||
"fl140": {"name": "Parenting Plan (FL All Family 140)", "file": "fl140.pdf",
|
||||
"description": "Residential schedule and decision-making for your children."},
|
||||
"wscss": {"name": "Child Support Worksheets (WSCSS)", "file": "wscss.pdf",
|
||||
"description": "Calculates child support under RCW 26.19."},
|
||||
"fl131": {"name": "Financial Declaration (FL All Family 131)", "file": "fl131.pdf",
|
||||
"description": "Your income, assets, and debts."},
|
||||
"fl231": {"name": "Findings & Conclusions (FL Divorce 231)", "file": "fl231.pdf",
|
||||
"description": "The court's factual findings."},
|
||||
"fl241": {"name": "Final Divorce Order (FL Divorce 241)", "file": "fl241.pdf",
|
||||
"description": "The final decree ending your marriage."},
|
||||
"fl211": {"name": "Response to Petition (FL Divorce 211)", "file": "fl211.pdf",
|
||||
"description": "Your reply to your spouse's petition."},
|
||||
"fl223": {"name": "Motion for Temporary Order (FL Divorce 223)", "file": "fl223.pdf",
|
||||
"description": "Ask for temporary custody, support, or protection orders."},
|
||||
}
|
||||
|
||||
|
||||
def _load_fieldmap():
|
||||
try:
|
||||
with open(FIELDMAP_PATH) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
FIELDMAP = _load_fieldmap()
|
||||
|
||||
|
||||
def list_forms():
|
||||
out = []
|
||||
for fid, spec in FORM_NAMES.items():
|
||||
fm = FIELDMAP.get(fid, [])
|
||||
fields = [{"key": f["key"], "label": f.get("label", f["key"]),
|
||||
"type": f.get("type", "text")} for f in fm]
|
||||
out.append({"id": fid, "name": spec["name"], "description": spec["description"],
|
||||
"fields": fields})
|
||||
return out
|
||||
|
||||
|
||||
def fill_form(form_id, values):
|
||||
"""Overlay `values` (dict field_key -> text) onto the form, return the filled PDF path."""
|
||||
spec = FORM_NAMES.get(form_id)
|
||||
fm = FIELDMAP.get(form_id, [])
|
||||
if not spec:
|
||||
return None
|
||||
src = os.path.join(FORMS_DIR, spec["file"])
|
||||
if not os.path.exists(src):
|
||||
return None
|
||||
fmap = {f["key"]: f for f in fm}
|
||||
doc = pymupdf.open(src)
|
||||
for key, text in values.items():
|
||||
f = fmap.get(key)
|
||||
if not f or not text:
|
||||
continue
|
||||
page = doc[f.get("page", 0)]
|
||||
if f.get("type") == "checkbox":
|
||||
v = str(text).strip().lower()
|
||||
if v in ("x", "true", "yes", "1", "checked", "check", "on"):
|
||||
x, y = f["x"], f["y"]
|
||||
page.draw_line(pymupdf.Point(x + 1, y + 1), pymupdf.Point(x + 9, y + 9),
|
||||
color=(0, 0, 0), width=1.3)
|
||||
page.draw_line(pymupdf.Point(x + 9, y + 1), pymupdf.Point(x + 1, y + 9),
|
||||
color=(0, 0, 0), width=1.3)
|
||||
else:
|
||||
text = str(text).strip()
|
||||
if not text:
|
||||
continue
|
||||
page.insert_text((f["x"], f["y"]), text, fontsize=f.get("size", 11),
|
||||
fontname="helv", color=(0, 0, 0))
|
||||
out = os.path.join("/tmp", f"filled_{form_id}_{uuid.uuid4().hex[:8]}.pdf")
|
||||
doc.save(out)
|
||||
doc.close()
|
||||
return out
|
||||
|
||||
|
||||
def available_forms():
|
||||
return list(FORM_NAMES.keys())
|
||||
Reference in New Issue
Block a user