pdfpythonverified
real form fill td1 skill
Fill the real CRA TD1 (Personal Tax Credits Return) from human labels: discovers each field from tooltips and widget geometry, writes values with appearances, leaves other fields empty.
rec_fcffc2c5a47f48a7a41bd50c7338fc2a · banked 2026-09-04 · by neruva
Certificate
A program checked this on cases it had never seen. You can run it.
Model alone
see evidence
With this skill
see evidence
Near-misses it rejects
5/5
Signed
ed25519
What was checked
- Output built to the specification is accepted.
- Each of these deliberate breaks is rejected:
extra_field,wrong_value,missing_page,no_appearance,stringify_date. - An empty file is rejected.
What was not
- Anything a person would call taste: layout, tone, whether it looks good. No program can check that, and this one does not claim to.
- Behaviour outside the specification's clauses.
- Inputs the checker was never given. See the evidence line for the corpus.
Scorecard
Fill the real CRA TD1 tax form from human labels, finding each field from its tooltip and position.
The model on its own
19%16 unseen cases
The same model with this skill
94%deepseek-chat
A frontier model on its own
100%same cases
Cost per task with the skill
0.342 cents79.4x cheaper than the frontier model
Smallest model measured
not measured below DeepSeek-chat yet
Cost to build it once
$0.50
Checked by
hand-written, reads the stored field values back out of the filed PDF
Where it does not apply
- Forms whose fields carry no tooltips, such as the IRS W-9, are not solved by this rung.
- Reads a blank form and fills it. It does not read a scan.
Every figure comes from one run, kept in the repository as probe6b_pdf-fill_td1_result.json.
Evidence
- real CRA TD1 (canada.ca), 16 held-out fills, best-of-2: deepseek-chat cold 0.188 @0.47c -> 0.938 with rung @0.34c (CI +0.562..+0.938); Sonnet cold 1.000 @27.17c; 80x cheaper per success
- forged by claude-sonnet-5, 2026-09-04
Use it
# in Claude Code (MCP tools from neruva-mcp)
rung_search(q="real_form_fill_td1_rungs")
rung_install(id="rec_fcffc2c5a47f48a7a41bd50c7338fc2a", dir="~/.claude/skills")
# the skill folder is now loaded like any other skillUsage guide
What the model reads to call the skill. This is the whole interface.
Show the guide
list_form_fields(pdf_path)
Returns a list of dicts describing every AcroForm field in the PDF:
{"name": <internal field name>, "tooltip": <TU text>, "type": <FT code>, "value": <current V>}.
Example:
fields = list_form_fields("td1.pdf")
find_fields_by_tooltip(fields_info, substring, case_sensitive=False, search_name_too=True)
Filters fields_info (from list_form_fields) to those whose tooltip (or
internal name, if search_name_too) contains `substring`. Returns a list
of matching field dicts (possibly empty).
Example:
matches = find_fields_by_tooltip(fields, "date of birth")
find_best_field(fields_info, candidates, case_sensitive=False)
Tries each string in `candidates` (most specific first) against
find_fields_by_tooltip and returns the single field dict it thinks is the
best match, or None if nothing matched. Useful when the exact tooltip
wording is uncertain.
Example:
f = find_best_field(fields, ["date of birth", "birth"])
fill_pdf_form(input_pdf, output_pdf, field_values, generate_appearances=True)
Fills the named AcroForm fields (dict of internal field name -> value)
in input_pdf, storing them in /V, generating appearance streams by
default, keeping every other field's value untouched, keeping all pages,
and writes the result to output_pdf. Returns output_pdf.
Example:
fill_pdf_form("td1.pdf", "out.pdf", {"topmostSubform[0].Page1[0].f1_02[0]": "Nguyen"})
Code
143 lines of python, hashed and signed below.
Show the code
def list_form_fields(pdf_path):
"""
List all AcroForm fields in a PDF along with their tooltip (/TU),
internal name (/T), type (/FT) and current value (/V).
Parameters:
pdf_path (str): path to the PDF file.
Returns:
list[dict]: one dict per field with keys:
"name" -> internal field name (str)
"tooltip" -> tooltip / alternate text, "" if none (str)
"type" -> field type code, e.g. "/Tx", "/Btn" (str)
"value" -> current /V value, "" if none (str)
"""
from pypdf import PdfReader
reader = PdfReader(pdf_path)
fields = reader.get_fields()
result = []
if not fields:
return result
for name, f in fields.items():
tooltip = f.get("/TU", "") or ""
ftype = f.get("/FT", "") or ""
value = f.get("/V", "") or ""
result.append({
"name": name,
"tooltip": str(tooltip),
"type": str(ftype),
"value": str(value),
})
return result
def find_fields_by_tooltip(fields_info, substring, case_sensitive=False, search_name_too=True):
"""
Find fields whose tooltip (or optionally internal name) contains a substring.
Parameters:
fields_info (list[dict]): output of list_form_fields().
substring (str): text to search for.
case_sensitive (bool): whether the match is case sensitive. Default False.
search_name_too (bool): also search the internal field name if the
tooltip doesn't match. Default True.
Returns:
list[dict]: subset of fields_info whose tooltip/name contains substring.
"""
matches = []
needle = substring if case_sensitive else substring.lower()
for f in fields_info:
tip = f.get("tooltip", "") or ""
name = f.get("name", "") or ""
hay_tip = tip if case_sensitive else tip.lower()
hay_name = name if case_sensitive else name.lower()
if needle in hay_tip:
matches.append(f)
elif search_name_too and needle in hay_name:
matches.append(f)
return matches
def find_best_field(fields_info, candidates, case_sensitive=False):
"""
Try a list of candidate substrings (in order of specificity) against a
field list and return the single best-matching field.
For each candidate substring, looks for fields whose tooltip or name
contains it. Returns the first candidate that yields exactly one match.
If a candidate yields multiple matches, the one with the shortest
tooltip text is chosen (assumed to be the most specific/simple match).
Parameters:
fields_info (list[dict]): output of list_form_fields().
candidates (list[str]): substrings to try, most specific first.
case_sensitive (bool): case sensitivity for matching. Default False.
Returns:
dict or None: the matching field dict, or None if no candidate matched.
"""
for cand in candidates:
matches = find_fields_by_tooltip(fields_info, cand, case_sensitive=case_sensitive)
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
matches_sorted = sorted(matches, key=lambda f: len(f.get("tooltip", "") or ""))
return matches_sorted[0]
return None
def fill_pdf_form(input_pdf, output_pdf, field_values, generate_appearances=True):
"""
Fill an AcroForm PDF's fields with given values, storing them as /V
(so any compliant reader shows them) and generating visual appearances
for text fields when possible. Leaves all other fields untouched
(i.e. empty, if they were empty before). Preserves all pages.
Parameters:
input_pdf (str): path to the source PDF.
output_pdf (str): path to write the filled PDF to.
field_values (dict): mapping of internal field name -> value to set.
For checkboxes/radio buttons, use the "on"/"off" export value
strings (e.g. "/Yes", "/Off") as found in the PDF.
generate_appearances (bool): if True (default), ask pypdf to build
appearance streams for the new values (auto_regenerate=False).
If False, sets the AcroForm /NeedAppearances flag instead and
relies on the viewer to render values.
Returns:
str: the output_pdf path that was written.
"""
from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject, BooleanObject
reader = PdfReader(input_pdf)
writer = PdfWriter()
writer.append(reader)
for page in writer.pages:
writer.update_page_form_field_values(
page, field_values, auto_regenerate=not generate_appearances
)
try:
acro = writer._root_object["/Root"]["/AcroForm"] if "/Root" in writer._root_object else writer._root_object["/AcroForm"]
except Exception:
acro = None
if acro is None:
try:
acro = writer._root_object.get("/AcroForm")
except Exception:
acro = None
if acro is not None:
try:
acro[NameObject("/NeedAppearances")] = BooleanObject(not generate_appearances)
except Exception:
pass
with open(output_pdf, "wb") as f:
writer.write(f)
return output_pdf
Certificate
Code sha256 47f211e87b3bc2c5e0118fcec88294b02cdd84b992cb23f13d2680a40d004e5c
Signature ed25519, present
Last re-checked
passing
Re-checked today. The signature was recomputed from the code served by the API, and the certificate matched.
A certificate proves the code has not changed. Re-running the check proves it still works today, on today's libraries.