with one click
add-form
Add a new form template from a PDF file and create its field mapping
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Add a new form template from a PDF file and create its field mapping
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | add-form |
| description | Add a new form template from a PDF file and create its field mapping |
This skill takes a PDF file path and creates a new form template with its field mapping in the flightforms system.
The user should provide:
LSGS) for a single airportED) for a country/regiondefault for a catch-all fallback formIf any of these are missing, ask the user before proceeding.
Run a Python script to extract all AcroForm fields from the PDF:
python3 -c "
from pypdf import PdfReader
r = PdfReader('<pdf_path>')
fields = r.get_fields()
if not fields:
print('ERROR: No form fields found in this PDF')
else:
for name, field in sorted(fields.items()):
ft = field.get('/FT', 'unknown')
print(f'{name}: type={ft}')
"
If no fields are found, tell the user the PDF has no fillable AcroForm fields and stop.
Show the user the list of fields and propose a mapping.
Map PDF fields to canonical names using these conventions. Not all fields need to be mapped — only map what makes sense for the form.
| Canonical name | Description |
|---|---|
flight.origin | Origin ICAO code |
flight.destination | Destination ICAO code |
flight.departure_date | Departure date (formatted per date_format) |
flight.arrival_date | Arrival date |
flight.departure_time_utc | Departure time in UTC |
flight.arrival_time_utc | Arrival time in UTC |
flight.remote | The "other" airport (opposite of airport in request) |
flight.nature | Flight nature as text (private, business, etc.) |
flight.contact | Contact info |
flight.observations | Observations/remarks |
aircraft.registration | Aircraft registration |
aircraft.type | Aircraft type |
aircraft.owner | Aircraft owner/operator |
aircraft.owner_address | Owner address |
aircraft.usual_base | Usual base airport |
origin.country | Country of origin airport |
destination.country | Country of destination airport |
remote.country | Country of the remote airport |
passengers.count | Number of passengers (as string) |
passengers.embarking | Number of passengers embarking |
passengers.disembarking | Number of passengers disembarking |
routing.departure_place | Resolved name of origin airport |
routing.arrival_place | Resolved name of destination airport |
airport.name | Resolved name of the form's target airport |
airport.icao | The form's target airport ICAO code |
For forms with separate arrival/departure sections, use these — they only produce values for the matching direction:
| Canonical name | Description |
|---|---|
flight.date | Arrival date if arriving, departure date if departing |
flight.time | Arrival time if arriving, departure time if departing |
arrival.date | Arrival date (only filled when direction is arrival) |
arrival.time | Arrival time (only filled when arriving) |
arrival.registration | Aircraft registration (only filled when arriving) |
arrival.type | Aircraft type (only filled when arriving) |
arrival.owner | Aircraft owner (only filled when arriving) |
arrival.nature | Flight nature (only filled when arriving) |
departure.date | Departure date (only filled when departing) |
departure.time | Departure time (only filled when departing) |
departure.registration | Aircraft registration (only filled when departing) |
departure.type | Aircraft type (only filled when departing) |
departure.owner | Aircraft owner (only filled when departing) |
departure.nature | Flight nature (only filled when departing) |
| Canonical name | Description |
|---|---|
direction.inbound | Checkbox: checked when airport is destination |
direction.outbound | Checkbox: checked when airport is origin |
direction.arrival_mark | Text "X" when arriving, empty when departing |
direction.departure_mark | Text "X" when departing, empty when arriving |
Use flight.nature.<value> where <value> matches the nature string:
flight.nature.privateflight.nature.businessflight.nature.fretflight.nature.other| Canonical name | Description |
|---|---|
aircraft.airplane | Checked if airplane |
aircraft.helicopter | Checked if helicopter |
Use {i} for 0-based index and {n} for 1-based index in the PDF field pattern:
| Canonical pattern | Description |
|---|---|
crew[{i}].full_name | Full name ("LastName FirstName") |
crew[{i}].first_name | First name |
crew[{i}].last_name | Last name |
crew[{i}].function | Role (Pilot, Crew) |
crew[{i}].dob | Date of birth |
crew[{i}].nationality | Nationality |
crew[{i}].id_number | ID/passport number |
crew[{i}].id_type | ID type (Passport, Identity card) |
crew[{i}].id_issuing_country | ID issuing country |
crew[{i}].id_expiry | ID expiry date |
crew[{i}].sex | Sex |
crew[{i}].place_of_birth | Place of birth |
Same patterns apply with passengers[{i}] prefix.
| Canonical name | Description |
|---|---|
extra.<key> | Value from extra_fields in the request |
connecting.origin | Connecting flight origin |
connecting.destination | Connecting flight destination |
connecting.departure_date | Connecting flight departure date |
connecting.departure_time_utc | Connecting flight departure time |
connecting.arrival_date | Connecting flight arrival date |
connecting.arrival_time_utc | Connecting flight arrival time |
pdf_acroform — standard PDF AcroForm filler (most common, used for LSGS, LFQA, ICAO GenDec)pdf_acroform_french — French customs-specific filler with combined crew/pax list and local time conversionxlsx — Excel filler (used for GAR)docx — Word document filler (legacy, not used for new forms)For new PDF templates, use pdf_acroform unless there's a special reason not to.
Present the proposed mapping as a JSON structure and ask the user to confirm or adjust before writing files. Include:
src/flightforms/templates/<form_id>.pdfsrc/flightforms/mappings/<form_id>.jsonThe mapping JSON structure:
{
"icao": "XXXX", // for exact airport match (omit if using prefix or default)
"icao_prefix": "XX", // for country/region match (omit if using icao or default)
"default": true, // for catch-all fallback (omit if using icao or prefix)
"label": "Form Label",
"template": "<form_id>.pdf",
"type": "pdf_acroform",
"version": "1.0",
"time_reference": "utc",
"max_crew": 4,
"max_passengers": 8,
"date_format": "%d/%m/%Y",
"has_connecting_flight": false,
"extra_fields": [],
"required_fields": {
"flight": ["origin", "destination", "departure_date"],
"aircraft": ["registration"],
"crew": ["first_name", "last_name"],
"passengers": []
},
"field_map": {
"canonical.name": "PDF Field Name"
}
}
Only include icao, icao_prefix, OR default — never more than one.
Run a quick Python test to verify the form can be generated:
python3 -c "
from flightforms.registry import MappingRegistry
from flightforms.fillers.pdf_filler import fill_pdf
from flightforms.api.models import GenerateRequest
from pypdf import PdfReader
from io import BytesIO
reg = MappingRegistry('src/flightforms/mappings', 'src/flightforms/templates')
class StubResolver:
def get_country(self, icao): return 'XX'
def get_name(self, icao): return icao
req = GenerateRequest(
airport='<target_icao>',
form='<form_id>',
flight={'origin': 'AAAA', 'destination': '<target_icao>',
'departure_date': '2026-01-15', 'departure_time_utc': '10:00',
'arrival_date': '2026-01-15', 'arrival_time_utc': '11:30',
'nature': 'private'},
aircraft={'registration': 'F-TEST', 'type': 'PA28', 'owner': 'Test Owner'},
crew=[{'first_name': 'John', 'last_name': 'Doe'}],
passengers=[{'first_name': 'Jane', 'last_name': 'Smith'}],
)
mapping = reg.get_form('<target_icao>', '<form_id>')
result = fill_pdf(reg.get_template_path(mapping), mapping, req, StubResolver())
print(f'Generated PDF: {len(result)} bytes')
reader = PdfReader(BytesIO(result))
fields = reader.get_fields()
for name, field in sorted(fields.items()):
val = field.get('/V')
if val:
print(f' {name}: {val}')
"
Show the user which fields were filled and their values.
Run the existing test suite to make sure nothing is broken:
python3 -m pytest tests/ -x -q -k "not flatten"
If any tests fail due to the new default/prefix mapping changing behavior (e.g., previously-unknown airports now returning forms), update the tests accordingly.
Register the new form for preview and snapshot testing:
Add to FORM_AIRPORTS in src/flightforms/preview.py:
FORM_AIRPORTS = {
...
"<form_id>": "<target_icao>",
}
Add to DIRECTION_AWARE_FORMS in src/flightforms/preview.py if the form has direction-dependent fields (arrival/departure checkboxes, direction marks, or arrival.*/departure.* fields):
DIRECTION_AWARE_FORMS = {..., "<form_id>"}
Generate preview files for visual verification:
python -m flightforms.cli preview --form <form_id> --output-dir /tmp/preview
open /tmp/preview/preview_<form_id>*.pdf
Ask the user to visually verify the previews. Each field value is self-describing (e.g., CrewLast1, AcReg, OriginCountry) so they can confirm data lands in the right spot. For direction-aware forms, both arrival and departure variants are generated.
Generate golden snapshots once the user confirms:
python -m pytest tests/unit/test_snapshots.py --snapshot-update -v
Verify snapshots pass:
python -m pytest tests/unit/test_snapshots.py -v
From now on, any mapping or template change that moves a value will fail the snapshot test until explicitly re-approved.
Tell the user:
src/flightforms/templates/<form_id>.pdfsrc/flightforms/mappings/<form_id>.json/deploy when ready to push to production