ifgr003 commited on
Commit
c7a86ba
Β·
verified Β·
1 Parent(s): d5970d3

Update spacyNER/spacyNER/autofill.py

Browse files
Files changed (1) hide show
  1. spacyNER/spacyNER/autofill.py +295 -138
spacyNER/spacyNER/autofill.py CHANGED
@@ -1,138 +1,295 @@
1
- # =============================================================
2
- # spacyNER/autofill.py
3
- # =============================================================
4
- # Maps extracted NER dicts β†’ populated Form dataclasses.
5
- #
6
- # Certifications page:
7
- # fill_form_1a(text) β†’ Form1A (from Form 102 OCR)
8
- # fill_form_2a(text) β†’ Form2A (from Form 103 OCR)
9
- # fill_form_3a(text) β†’ Form3A (from Form 97 OCR)
10
- #
11
- # Marriage License page:
12
- # fill_form_90(groom_ocr, bride_ocr) β†’ Form90
13
- # Internally calls:
14
- # extract_form_90_groom() β†’ uses F90_GROOM_* labels
15
- # extract_form_90_bride() β†’ uses F90_BRIDE_* labels
16
- # MNB classify_sex() has already routed each cert before this.
17
- # =============================================================
18
-
19
- from spacyNER.extractor import CivilRegistryNER
20
- from spacyNER.models import (
21
- Form1A, Form2A, Form3A, Form90,
22
- SpouseOutput, ApplicantOutput,
23
- )
24
- from spacyNER.labels import (
25
- OUTPUT_MAP_1A,
26
- OUTPUT_MAP_2A,
27
- OUTPUT_MAP_3A_HUSBAND,
28
- OUTPUT_MAP_3A_WIFE,
29
- OUTPUT_MAP_3A_EVENT,
30
- OUTPUT_MAP_90_GROOM,
31
- OUTPUT_MAP_90_BRIDE,
32
- )
33
-
34
-
35
- class AutoFillEngine:
36
-
37
- def __init__(self, extractor: CivilRegistryNER):
38
- self.extractor = extractor
39
-
40
- # ── Form 1A ───────────────────────────────────────────────
41
- def fill_form_1a(self, ocr_text: str) -> Form1A:
42
- """OCR text from Form 102 β†’ Form1A object."""
43
- data = self.extractor.extract_form_102(ocr_text)
44
- form = Form1A()
45
- for label, field_name in OUTPUT_MAP_1A.items():
46
- v = data.get(label)
47
- if v: setattr(form, field_name, v)
48
- if data.get("name_of_child"): form.name_of_child = data["name_of_child"]
49
- if data.get("name_of_mother"): form.name_of_mother = data["name_of_mother"]
50
- if data.get("name_of_father"): form.name_of_father = data["name_of_father"]
51
- return form
52
-
53
- # ── Form 2A ───────────────────────────────────────────────
54
- def fill_form_2a(self, ocr_text: str) -> Form2A:
55
- """OCR text from Form 103 β†’ Form2A object."""
56
- data = self.extractor.extract_form_103(ocr_text)
57
- form = Form2A()
58
- for label, field_name in OUTPUT_MAP_2A.items():
59
- v = data.get(label)
60
- if v: setattr(form, field_name, v)
61
- if data.get("name_of_deceased"):
62
- form.name_of_deceased = data["name_of_deceased"]
63
- return form
64
-
65
- # ── Form 3A ───────────────────────────────────────────────
66
- def fill_form_3a(self, ocr_text: str) -> Form3A:
67
- """OCR text from Form 97 β†’ Form3A object."""
68
- data = self.extractor.extract_form_97(ocr_text)
69
- husband = SpouseOutput()
70
- wife = SpouseOutput()
71
- form = Form3A(husband=husband, wife=wife)
72
-
73
- for label, field_name in OUTPUT_MAP_3A_HUSBAND.items():
74
- v = data.get(label)
75
- if v: setattr(husband, field_name, v)
76
- for label, field_name in OUTPUT_MAP_3A_WIFE.items():
77
- v = data.get(label)
78
- if v: setattr(wife, field_name, v)
79
- for label, field_name in OUTPUT_MAP_3A_EVENT.items():
80
- v = data.get(label)
81
- if v: setattr(form, field_name, v)
82
-
83
- if data.get("husband_name"): husband.name = data["husband_name"]
84
- if data.get("husband_name_of_father"): husband.name_of_father = data["husband_name_of_father"]
85
- if data.get("husband_name_of_mother"): husband.name_of_mother = data["husband_name_of_mother"]
86
- if data.get("wife_name"): wife.name = data["wife_name"]
87
- if data.get("wife_name_of_father"): wife.name_of_father = data["wife_name_of_father"]
88
- if data.get("wife_name_of_mother"): wife.name_of_mother = data["wife_name_of_mother"]
89
- return form
90
-
91
- # ── Form 90 ───────────────────────────────────────────────
92
- def fill_form_90(self, groom_ocr: str, bride_ocr: str) -> Form90:
93
- """
94
- Marriage License page:
95
- groom_ocr = OCR text from Male PSA/NSO birth cert β†’ F90_GROOM_* labels
96
- bride_ocr = OCR text from Female PSA/NSO birth cert β†’ F90_BRIDE_* labels
97
-
98
- MNB classify_sex() should have already confirmed which is which
99
- before calling this method.
100
- """
101
- groom_data = self.extractor.extract_form_90_groom(groom_ocr)
102
- bride_data = self.extractor.extract_form_90_bride(bride_ocr)
103
- return Form90(
104
- groom=self._fill_applicant(groom_data, role="groom"),
105
- bride=self._fill_applicant(bride_data, role="bride"),
106
- )
107
-
108
- def _fill_applicant(self, data: dict, role: str = "groom") -> ApplicantOutput:
109
- """
110
- Map extracted groom/bride data to ApplicantOutput.
111
- role = "groom" β†’ uses OUTPUT_MAP_90_GROOM
112
- role = "bride" β†’ uses OUTPUT_MAP_90_BRIDE
113
- """
114
- output_map = OUTPUT_MAP_90_GROOM if role == "groom" else OUTPUT_MAP_90_BRIDE
115
- a = ApplicantOutput()
116
- for label, field_name in output_map.items():
117
- v = data.get(label)
118
- if v: setattr(a, field_name, v)
119
- if data.get("name_of_applicant"): a.name_of_applicant = data["name_of_applicant"]
120
- if data.get("name_of_father"): a.name_of_father = data["name_of_father"]
121
- if data.get("maiden_name_of_mother"): a.maiden_name_of_mother = data["maiden_name_of_mother"]
122
- return a
123
-
124
- # ── Utility ───────────────────────────────────────────────
125
- def to_dict(self, form_obj) -> dict:
126
- """Flatten any Form object to a non-None, non-nested dict."""
127
- if form_obj is None or not hasattr(form_obj, "to_dict"):
128
- return {}
129
- raw = form_obj.to_dict()
130
- flat = {}
131
- for k, v in raw.items():
132
- if isinstance(v, dict):
133
- for sk, sv in v.items():
134
- if sv is not None:
135
- flat[f"{k}_{sk}"] = sv
136
- elif v is not None:
137
- flat[k] = v
138
- return flat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =============================================================
2
+ # spacyNER/autofill.py
3
+ # =============================================================
4
+ # Maps extracted NER dicts β†’ populated Form dataclasses.
5
+ #
6
+ # Certifications page:
7
+ # fill_form_1a(text) β†’ Form1A (from Form 102 OCR)
8
+ # fill_form_2a(text) β†’ Form2A (from Form 103 OCR)
9
+ # fill_form_3a(text) β†’ Form3A (from Form 97 OCR)
10
+ #
11
+ # Marriage License page:
12
+ # fill_form_90(groom_ocr, bride_ocr) β†’ Form90
13
+ # MNB classify_sex() has already routed each cert before this.
14
+ # =============================================================
15
+
16
+ from spacyNER.extractor import CivilRegistryNER
17
+ from spacyNER.models import (
18
+ Form1A, Form2A, Form3A, Form90,
19
+ SpouseOutput, ApplicantOutput,
20
+ )
21
+ from spacyNER.labels import (
22
+ # Form 102 β€” Birth Certificate
23
+ F102_CHILD_FIRST, F102_CHILD_MIDDLE, F102_CHILD_LAST,
24
+ F102_SEX, F102_DATE_OF_BIRTH, F102_PLACE_OF_BIRTH,
25
+ F102_TYPE_OF_BIRTH, F102_BIRTH_ORDER,
26
+ F102_MOTHER_FIRST, F102_MOTHER_MIDDLE, F102_MOTHER_LAST,
27
+ F102_MOTHER_CITIZENSHIP, F102_MOTHER_RELIGION, F102_MOTHER_RESIDENCE,
28
+ F102_FATHER_FIRST, F102_FATHER_MIDDLE, F102_FATHER_LAST,
29
+ F102_FATHER_CITIZENSHIP, F102_FATHER_RELIGION, F102_FATHER_RESIDENCE,
30
+ F102_MARRIAGE_DATE, F102_MARRIAGE_PLACE,
31
+ F102_REGISTRY_NO, F102_DATE_OF_REGISTRATION,
32
+ # Form 103 β€” Death Certificate
33
+ F103_DECEASED_FIRST, F103_DECEASED_MIDDLE, F103_DECEASED_LAST,
34
+ F103_SEX, F103_RELIGION, F103_AGE,
35
+ F103_PLACE_OF_DEATH, F103_DATE_OF_DEATH,
36
+ F103_CITIZENSHIP, F103_RESIDENCE, F103_CIVIL_STATUS, F103_OCCUPATION,
37
+ F103_CAUSE_IMMEDIATE, F103_CAUSE_ANTECEDENT, F103_CAUSE_UNDERLYING,
38
+ F103_REGISTRY_NO, F103_DATE_OF_REGISTRATION,
39
+ # Form 97 β€” Marriage Certificate
40
+ F97_HUSBAND_FIRST, F97_HUSBAND_MIDDLE, F97_HUSBAND_LAST,
41
+ F97_HUSBAND_DOB, F97_HUSBAND_AGE,
42
+ F97_HUSBAND_PLACE_BIRTH, F97_HUSBAND_SEX, F97_HUSBAND_CITIZENSHIP,
43
+ F97_HUSBAND_RESIDENCE, F97_HUSBAND_RELIGION, F97_HUSBAND_CIVIL_STATUS,
44
+ F97_HUSBAND_FATHER_FIRST, F97_HUSBAND_FATHER_MIDDLE, F97_HUSBAND_FATHER_LAST,
45
+ F97_HUSBAND_FATHER_CITIZENSHIP,
46
+ F97_HUSBAND_MOTHER_FIRST, F97_HUSBAND_MOTHER_MIDDLE, F97_HUSBAND_MOTHER_LAST,
47
+ F97_HUSBAND_MOTHER_CITIZENSHIP,
48
+ F97_WIFE_FIRST, F97_WIFE_MIDDLE, F97_WIFE_LAST,
49
+ F97_WIFE_DOB, F97_WIFE_AGE,
50
+ F97_WIFE_PLACE_BIRTH, F97_WIFE_SEX, F97_WIFE_CITIZENSHIP,
51
+ F97_WIFE_RESIDENCE, F97_WIFE_RELIGION, F97_WIFE_CIVIL_STATUS,
52
+ F97_WIFE_FATHER_FIRST, F97_WIFE_FATHER_MIDDLE, F97_WIFE_FATHER_LAST,
53
+ F97_WIFE_FATHER_CITIZENSHIP,
54
+ F97_WIFE_MOTHER_FIRST, F97_WIFE_MOTHER_MIDDLE, F97_WIFE_MOTHER_LAST,
55
+ F97_WIFE_MOTHER_CITIZENSHIP,
56
+ F97_PLACE_OF_MARRIAGE, F97_DATE_OF_MARRIAGE,
57
+ F97_REGISTRY_NO, F97_DATE_OF_REGISTRATION,
58
+ # Form 90 β€” Marriage License (shared)
59
+ F90_REGISTRY_NO, F90_DATE_OF_REGISTRATION,
60
+ # Form 90 β€” Groom (Male birth cert)
61
+ F90_GROOM_FIRST, F90_GROOM_MIDDLE, F90_GROOM_LAST,
62
+ F90_GROOM_DATE_OF_BIRTH, F90_GROOM_AGE, F90_GROOM_PLACE_OF_BIRTH,
63
+ F90_GROOM_SEX, F90_GROOM_CITIZENSHIP, F90_GROOM_RESIDENCE, F90_GROOM_RELIGION,
64
+ F90_GROOM_FATHER_FIRST, F90_GROOM_FATHER_MIDDLE, F90_GROOM_FATHER_LAST,
65
+ F90_GROOM_FATHER_CITIZENSHIP,
66
+ F90_GROOM_MOTHER_FIRST, F90_GROOM_MOTHER_MIDDLE, F90_GROOM_MOTHER_LAST,
67
+ F90_GROOM_MOTHER_CITIZENSHIP, F90_GROOM_MOTHER_RESIDENCE,
68
+ # Form 90 β€” Bride (Female birth cert)
69
+ F90_BRIDE_FIRST, F90_BRIDE_MIDDLE, F90_BRIDE_LAST,
70
+ F90_BRIDE_DATE_OF_BIRTH, F90_BRIDE_AGE, F90_BRIDE_PLACE_OF_BIRTH,
71
+ F90_BRIDE_SEX, F90_BRIDE_CITIZENSHIP, F90_BRIDE_RESIDENCE, F90_BRIDE_RELIGION,
72
+ F90_BRIDE_FATHER_FIRST, F90_BRIDE_FATHER_MIDDLE, F90_BRIDE_FATHER_LAST,
73
+ F90_BRIDE_FATHER_CITIZENSHIP,
74
+ F90_BRIDE_MOTHER_FIRST, F90_BRIDE_MOTHER_MIDDLE, F90_BRIDE_MOTHER_LAST,
75
+ F90_BRIDE_MOTHER_CITIZENSHIP, F90_BRIDE_MOTHER_RESIDENCE,
76
+ )
77
+
78
+
79
+ def _set(obj, field: str, data: dict, label: str) -> None:
80
+ """Set obj.field = data[label] if the label exists and is not None."""
81
+ v = data.get(label)
82
+ if v is not None:
83
+ setattr(obj, field, v)
84
+
85
+
86
+ class AutoFillEngine:
87
+
88
+ def __init__(self, extractor: CivilRegistryNER):
89
+ self.extractor = extractor
90
+
91
+ # ── Form 1A (from Form 102 β€” Certificate of Live Birth) ───
92
+ def fill_form_1a(self, ocr_text: str) -> Form1A:
93
+ data = self.extractor.extract_form_102(ocr_text)
94
+ form = Form1A()
95
+
96
+ # Child
97
+ _set(form, "child_first", data, F102_CHILD_FIRST)
98
+ _set(form, "child_middle", data, F102_CHILD_MIDDLE)
99
+ _set(form, "child_last", data, F102_CHILD_LAST)
100
+ _set(form, "sex", data, F102_SEX)
101
+ _set(form, "date_of_birth", data, F102_DATE_OF_BIRTH)
102
+ _set(form, "place_of_birth", data, F102_PLACE_OF_BIRTH)
103
+ _set(form, "type_of_birth", data, F102_TYPE_OF_BIRTH)
104
+ _set(form, "birth_order", data, F102_BIRTH_ORDER)
105
+
106
+ # Mother
107
+ _set(form, "mother_first", data, F102_MOTHER_FIRST)
108
+ _set(form, "mother_middle", data, F102_MOTHER_MIDDLE)
109
+ _set(form, "mother_last", data, F102_MOTHER_LAST)
110
+ _set(form, "mother_citizenship", data, F102_MOTHER_CITIZENSHIP)
111
+ _set(form, "mother_religion", data, F102_MOTHER_RELIGION)
112
+ _set(form, "mother_residence", data, F102_MOTHER_RESIDENCE)
113
+
114
+ # Father
115
+ _set(form, "father_first", data, F102_FATHER_FIRST)
116
+ _set(form, "father_middle", data, F102_FATHER_MIDDLE)
117
+ _set(form, "father_last", data, F102_FATHER_LAST)
118
+ _set(form, "father_citizenship", data, F102_FATHER_CITIZENSHIP)
119
+ _set(form, "father_religion", data, F102_FATHER_RELIGION)
120
+ _set(form, "father_residence", data, F102_FATHER_RESIDENCE)
121
+
122
+ # Parents' marriage
123
+ _set(form, "marriage_date", data, F102_MARRIAGE_DATE)
124
+ _set(form, "marriage_place", data, F102_MARRIAGE_PLACE)
125
+
126
+ # Registry
127
+ _set(form, "registry_no", data, F102_REGISTRY_NO)
128
+ _set(form, "date_of_registration", data, F102_DATE_OF_REGISTRATION)
129
+
130
+ return form
131
+
132
+ # ── Form 2A (from Form 103 β€” Certificate of Death) ────────
133
+ def fill_form_2a(self, ocr_text: str) -> Form2A:
134
+ data = self.extractor.extract_form_103(ocr_text)
135
+ form = Form2A()
136
+
137
+ # Deceased
138
+ _set(form, "deceased_first", data, F103_DECEASED_FIRST)
139
+ _set(form, "deceased_middle", data, F103_DECEASED_MIDDLE)
140
+ _set(form, "deceased_last", data, F103_DECEASED_LAST)
141
+ _set(form, "sex", data, F103_SEX)
142
+ _set(form, "religion", data, F103_RELIGION)
143
+ _set(form, "age", data, F103_AGE)
144
+ _set(form, "place_of_death", data, F103_PLACE_OF_DEATH)
145
+ _set(form, "date_of_death", data, F103_DATE_OF_DEATH)
146
+ _set(form, "citizenship", data, F103_CITIZENSHIP)
147
+ _set(form, "residence", data, F103_RESIDENCE)
148
+ _set(form, "civil_status", data, F103_CIVIL_STATUS)
149
+ _set(form, "occupation", data, F103_OCCUPATION)
150
+
151
+ # Causes
152
+ _set(form, "cause_immediate", data, F103_CAUSE_IMMEDIATE)
153
+ _set(form, "cause_antecedent", data, F103_CAUSE_ANTECEDENT)
154
+ _set(form, "cause_underlying", data, F103_CAUSE_UNDERLYING)
155
+
156
+ # Registry
157
+ _set(form, "registry_no", data, F103_REGISTRY_NO)
158
+ _set(form, "date_of_registration", data, F103_DATE_OF_REGISTRATION)
159
+
160
+ return form
161
+
162
+ # ── Form 3A (from Form 97 β€” Certificate of Marriage) ──────
163
+ def fill_form_3a(self, ocr_text: str) -> Form3A:
164
+ data = self.extractor.extract_form_97(ocr_text)
165
+ husband = SpouseOutput()
166
+ wife = SpouseOutput()
167
+ form = Form3A(husband=husband, wife=wife)
168
+
169
+ # Husband
170
+ _set(husband, "first", data, F97_HUSBAND_FIRST)
171
+ _set(husband, "middle", data, F97_HUSBAND_MIDDLE)
172
+ _set(husband, "last", data, F97_HUSBAND_LAST)
173
+ _set(husband, "date_of_birth", data, F97_HUSBAND_DOB)
174
+ _set(husband, "age", data, F97_HUSBAND_AGE)
175
+ _set(husband, "place_of_birth", data, F97_HUSBAND_PLACE_BIRTH)
176
+ _set(husband, "sex", data, F97_HUSBAND_SEX)
177
+ _set(husband, "citizenship", data, F97_HUSBAND_CITIZENSHIP)
178
+ _set(husband, "residence", data, F97_HUSBAND_RESIDENCE)
179
+ _set(husband, "religion", data, F97_HUSBAND_RELIGION)
180
+ _set(husband, "civil_status", data, F97_HUSBAND_CIVIL_STATUS)
181
+ _set(husband, "father_first", data, F97_HUSBAND_FATHER_FIRST)
182
+ _set(husband, "father_middle", data, F97_HUSBAND_FATHER_MIDDLE)
183
+ _set(husband, "father_last", data, F97_HUSBAND_FATHER_LAST)
184
+ _set(husband, "father_citizenship", data, F97_HUSBAND_FATHER_CITIZENSHIP)
185
+ _set(husband, "mother_first", data, F97_HUSBAND_MOTHER_FIRST)
186
+ _set(husband, "mother_middle", data, F97_HUSBAND_MOTHER_MIDDLE)
187
+ _set(husband, "mother_last", data, F97_HUSBAND_MOTHER_LAST)
188
+ _set(husband, "mother_citizenship", data, F97_HUSBAND_MOTHER_CITIZENSHIP)
189
+
190
+ # Wife
191
+ _set(wife, "first", data, F97_WIFE_FIRST)
192
+ _set(wife, "middle", data, F97_WIFE_MIDDLE)
193
+ _set(wife, "last", data, F97_WIFE_LAST)
194
+ _set(wife, "date_of_birth", data, F97_WIFE_DOB)
195
+ _set(wife, "age", data, F97_WIFE_AGE)
196
+ _set(wife, "place_of_birth", data, F97_WIFE_PLACE_BIRTH)
197
+ _set(wife, "sex", data, F97_WIFE_SEX)
198
+ _set(wife, "citizenship", data, F97_WIFE_CITIZENSHIP)
199
+ _set(wife, "residence", data, F97_WIFE_RESIDENCE)
200
+ _set(wife, "religion", data, F97_WIFE_RELIGION)
201
+ _set(wife, "civil_status", data, F97_WIFE_CIVIL_STATUS)
202
+ _set(wife, "father_first", data, F97_WIFE_FATHER_FIRST)
203
+ _set(wife, "father_middle", data, F97_WIFE_FATHER_MIDDLE)
204
+ _set(wife, "father_last", data, F97_WIFE_FATHER_LAST)
205
+ _set(wife, "father_citizenship", data, F97_WIFE_FATHER_CITIZENSHIP)
206
+ _set(wife, "mother_first", data, F97_WIFE_MOTHER_FIRST)
207
+ _set(wife, "mother_middle", data, F97_WIFE_MOTHER_MIDDLE)
208
+ _set(wife, "mother_last", data, F97_WIFE_MOTHER_LAST)
209
+ _set(wife, "mother_citizenship", data, F97_WIFE_MOTHER_CITIZENSHIP)
210
+
211
+ # Shared event + registry
212
+ _set(form, "place_of_marriage", data, F97_PLACE_OF_MARRIAGE)
213
+ _set(form, "date_of_marriage", data, F97_DATE_OF_MARRIAGE)
214
+ _set(form, "registry_no", data, F97_REGISTRY_NO)
215
+ _set(form, "date_of_registration", data, F97_DATE_OF_REGISTRATION)
216
+
217
+ return form
218
+
219
+ # ── Form 90 (Marriage License) ─────────────────────────────
220
+ def fill_form_90(self, groom_ocr: str, bride_ocr: str) -> Form90:
221
+ """
222
+ groom_ocr = OCR from Male birth cert β†’ F90_GROOM_* labels
223
+ bride_ocr = OCR from Female birth cert β†’ F90_BRIDE_* labels
224
+ MNB classify_sex() should have already confirmed which is which.
225
+ """
226
+ groom_data = self.extractor.extract_form_90_groom(groom_ocr)
227
+ bride_data = self.extractor.extract_form_90_bride(bride_ocr)
228
+
229
+ groom = ApplicantOutput()
230
+ bride = ApplicantOutput()
231
+ form = Form90(groom=groom, bride=bride)
232
+
233
+ # Shared registry (taken from groom cert)
234
+ _set(form, "registry_no", groom_data, F90_REGISTRY_NO)
235
+ _set(form, "date_of_registration", groom_data, F90_DATE_OF_REGISTRATION)
236
+
237
+ # Groom
238
+ _set(groom, "first", groom_data, F90_GROOM_FIRST)
239
+ _set(groom, "middle", groom_data, F90_GROOM_MIDDLE)
240
+ _set(groom, "last", groom_data, F90_GROOM_LAST)
241
+ _set(groom, "date_of_birth", groom_data, F90_GROOM_DATE_OF_BIRTH)
242
+ _set(groom, "age", groom_data, F90_GROOM_AGE)
243
+ _set(groom, "place_of_birth", groom_data, F90_GROOM_PLACE_OF_BIRTH)
244
+ _set(groom, "sex", groom_data, F90_GROOM_SEX)
245
+ _set(groom, "citizenship", groom_data, F90_GROOM_CITIZENSHIP)
246
+ _set(groom, "residence", groom_data, F90_GROOM_RESIDENCE)
247
+ _set(groom, "religion", groom_data, F90_GROOM_RELIGION)
248
+ _set(groom, "father_first", groom_data, F90_GROOM_FATHER_FIRST)
249
+ _set(groom, "father_middle", groom_data, F90_GROOM_FATHER_MIDDLE)
250
+ _set(groom, "father_last", groom_data, F90_GROOM_FATHER_LAST)
251
+ _set(groom, "father_citizenship", groom_data, F90_GROOM_FATHER_CITIZENSHIP)
252
+ _set(groom, "mother_first", groom_data, F90_GROOM_MOTHER_FIRST)
253
+ _set(groom, "mother_middle", groom_data, F90_GROOM_MOTHER_MIDDLE)
254
+ _set(groom, "mother_last", groom_data, F90_GROOM_MOTHER_LAST)
255
+ _set(groom, "mother_citizenship", groom_data, F90_GROOM_MOTHER_CITIZENSHIP)
256
+ _set(groom, "mother_residence", groom_data, F90_GROOM_MOTHER_RESIDENCE)
257
+
258
+ # Bride
259
+ _set(bride, "first", bride_data, F90_BRIDE_FIRST)
260
+ _set(bride, "middle", bride_data, F90_BRIDE_MIDDLE)
261
+ _set(bride, "last", bride_data, F90_BRIDE_LAST)
262
+ _set(bride, "date_of_birth", bride_data, F90_BRIDE_DATE_OF_BIRTH)
263
+ _set(bride, "age", bride_data, F90_BRIDE_AGE)
264
+ _set(bride, "place_of_birth", bride_data, F90_BRIDE_PLACE_OF_BIRTH)
265
+ _set(bride, "sex", bride_data, F90_BRIDE_SEX)
266
+ _set(bride, "citizenship", bride_data, F90_BRIDE_CITIZENSHIP)
267
+ _set(bride, "residence", bride_data, F90_BRIDE_RESIDENCE)
268
+ _set(bride, "religion", bride_data, F90_BRIDE_RELIGION)
269
+ _set(bride, "father_first", bride_data, F90_BRIDE_FATHER_FIRST)
270
+ _set(bride, "father_middle", bride_data, F90_BRIDE_FATHER_MIDDLE)
271
+ _set(bride, "father_last", bride_data, F90_BRIDE_FATHER_LAST)
272
+ _set(bride, "father_citizenship", bride_data, F90_BRIDE_FATHER_CITIZENSHIP)
273
+ _set(bride, "mother_first", bride_data, F90_BRIDE_MOTHER_FIRST)
274
+ _set(bride, "mother_middle", bride_data, F90_BRIDE_MOTHER_MIDDLE)
275
+ _set(bride, "mother_last", bride_data, F90_BRIDE_MOTHER_LAST)
276
+ _set(bride, "mother_citizenship", bride_data, F90_BRIDE_MOTHER_CITIZENSHIP)
277
+ _set(bride, "mother_residence", bride_data, F90_BRIDE_MOTHER_RESIDENCE)
278
+
279
+ return form
280
+
281
+ # ── Utility ────────────────────────────────────────────────
282
+ def to_dict(self, form_obj) -> dict:
283
+ """Flatten any Form object to a non-None, non-nested dict."""
284
+ if form_obj is None or not hasattr(form_obj, "to_dict"):
285
+ return {}
286
+ raw = form_obj.to_dict()
287
+ flat = {}
288
+ for k, v in raw.items():
289
+ if isinstance(v, dict):
290
+ for sk, sv in v.items():
291
+ if sv is not None:
292
+ flat[f"{k}_{sk}"] = sv
293
+ elif v is not None:
294
+ flat[k] = v
295
+ return flat