Files
astraea/forms.py

99 lines
4.0 KiB
Python

# -*- 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())