Repository navigation
fix: fill radio button groups with the chosen option - #736
Conversation
The buttons of a radio group share one field name. write_pdf treated each button like a checkbox, so an answer of "Yes" gave every button its own on state and the last one won, and any other answer left the group empty. On OSHA Form 301, "Was employee treated in an emergency room?" answered "Yes" came out as "No". The schema also gave the model a free-form string, so it never saw the options. A radio group is now offered its buttons' states as an enum, plus "Off", the way checkboxes get "Yes" and "Off". write_pdf gives the whole group the chosen state, and pypdf turns on the button that has it. Groups that geometry reads as table columns are unchanged.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
|
Hi @Anandb71, Thanks for the PR, but we cannot accept this change. Your points do not hold up regarding how our repository and pipeline actually handle form fields. In our experience and throughout all our real-world use cases, the current implementation works completely fine and as expected. Please make sure to properly familiarize yourself with the codebase, how our schemas are structured, and test with actual real-world production cases before asserting issues that do not exist in practice. Don't use AI for creating false problems. Closing this PR for now. Marc |
|
Thanks for taking a look, and fair enough if it doesn't fit your pipeline. For context, I hit this on OSHA's public Form 301: answering "Yes" to the ER question gets filled in as "No", because the radio group's export values don't match the option that was picked. Happy to share the test PDF and the steps to reproduce it if that would help. Otherwise, no worries. |
|
Hi @Anandb71, thanks for bringing it up, could you show us this edge example case in which it didn't work out. Thanks, |
|
Sure, here it is. It only uses FireForm's own PDF: OSHA recordkeeping forms package, https://www.osha.gov/sites/default/files/OSHA-RK-Forms-Package.pdf (page 10 is Form 301). Field Steps (from the root of a FireForm checkout, after
import os, sys, urllib.request
sys.path.insert(0, os.getcwd())
from pypdf import PdfReader
from app.services.form_filler.filler import write_pdf
URL = "https://www.osha.gov/sites/default/files/OSHA-RK-Forms-Package.pdf"
req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120 Safari/537.36"})
open("osha.pdf", "wb").write(urllib.request.urlopen(req).read())
answer = {"301 ER": "Yes", "301 Gender": "Female"} # what the model returns
write_pdf("osha.pdf", answer, "filled.pdf")
for a in PdfReader("filled.pdf").pages[9]["/Annots"]:
a = a.get_object(); p = a.get("/Parent")
if p is not None and p.get_object().get("/T") in answer:
on = [s for s in a["/AP"]["/N"] if s != "/Off"][0]
print(p.get_object()["/T"], "V =", p.get_object().get("/V"), "|", on, "button AS =", a.get("/AS"))Observed on Expected (and what the PR branch gives): Rendered with PDFium (left: Why it happens: both buttons share the name I should also correct how I put it earlier: the problem isn't that the export values don't match. Each button gets its own on-state, so the last button in the group wins. Happy to reopen #736, or to cut it down to just the |
|
Hi @Anandb71, since you seem new in this community, I'm happy to welcome you and to invite you to hop on our discord channel so that you can follow our weekly meetings and discussions. I've just reopen your request and merged it. Thanks for your contribution, |


Description
Radio button groups are never filled with the right option. The buttons of a group share one field name, and
write_pdftreated each button like a checkbox. For an answer of"Yes", every button got its own on state and the last one won. Any other answer turned the whole group off. The schema also sent each group to the model as a free-form{"type": "string"}, so the model never saw the options.On OSHA's fillable recordkeeping package (https://www.osha.gov/sites/default/files/OSHA-RK-Forms-Package.pdf, page 10 is the Form 301 injury and illness incident report), with the model's answer patched in:
developmentFemaleYesNoPMI checked this by reading each button's
/ASback, and by rendering the page with PDFium, the engine behind the preview iframe.Radio buttons were asked for in #142 and #493, both closed. The current filler sends them down the checkbox path, which cannot pick one option of a group. None of the benchmark PDFs has a radio group, which is probably why it didn't show up.
This change:
template.create_template()reads each radio group's options, the state each of its buttons turns on (/Male,/Female). It gives them to the model as an enum plus"Off", the same way checkboxes get"Yes"/"Off". The new helpers areread_radio_options()andadd_radio_options(), next to the tooltip ones.filler.write_pdf()gives every button of a group the chosen option's state. pypdf then turns on the button that has that state and turns the others off."Off",""or a missing answer leaves the group empty. Checkboxes are unchanged.Forms without radio groups get exactly the same schema as before. I compared
create_template()output (schema, tables and groups) ondevelopmentand on this branch for the 11 benchmark PDFs,data/inputs/, and 9 more FEMA ICS forms (210, 211, 214, 215, 215A, 218, 220, 221, 225). All 21 are identical, so the benchmark is not affected.Not covered:
geometry.row_lists()still reads some rows of 3 or more radio buttons as a one-column table, so those groups keep a string column. On the OSHA 300 log, that is the classification and injury-type buttons of every case row after the first (18 groups). That needs a change in the geometry, so I left it out of this PR.Related: #142, #493
Type of change
How Has This Been Tested?
Repro on
development(5bab407) with the form the new tests write: a text field and two radio groups,Gender(Male/Female) andER(Yes/No), laid out the way Acrobat writes them. The model's answer{"Gender": "Female", "ER": "Yes"}gives:With the change:
Genderhas Female on, andERhas Yes on.test_radio_groups_offer_their_options: the schema givesGenderthe enum["Male", "Female", "Off"]andERthe enum["Yes", "No", "Off"]. The text field stays a string. Fails ondevelopmentwithKeyError: 'enum'.test_fill_turns_on_the_chosen_radio_button: runsfill()with the model call patched. The schema sent to the model has the options, and the filled PDF has Female and Yes on, with the other buttons off. Fails ondevelopmentwithKeyError: 'enum'. With only the schema half of the fix, it fails on the PDF instead:{'/Female': '/Off'} != {'/Female': '/Female'}.test_radio_group_answered_off_stays_unselected:"Off"and""leave both groups empty. This passes before and after the change, and guards the new branch inwrite_pdf.python -m pytest tests/→ 316 passed.ruff check app/→ All checks passed.Test Configuration:
Checklist: