22from base64 import b64encode
33from collections import defaultdict
44from collections .abc import Iterable , Generator
5- from typing import Any , Mapping , cast
5+ from typing import Any , Mapping , cast , NotRequired
66
77import openpyxl
88from PIL import Image
2828
2929
3030class Config (BatchNameConfig , FailIfAlienConfig ):
31- household_pk_col : str
32- master_column_label : str
33- detail_column_label : str
31+ master_detail : bool
32+ household_pk_col : NotRequired [str ]
33+ master_column_label : NotRequired [str ]
34+ detail_column_label : NotRequired [str ]
35+ people_column_prefix : NotRequired [str ]
36+ first_line : int
3437
3538
3639class ColumnConfigurationError (Exception ):
@@ -129,6 +132,30 @@ def process_individuals(
129132 return processed
130133
131134
135+ def process_people_only (sheet : Sheet , job : AsyncJob , batch : Batch , config : Config ) -> int :
136+ processed = 0
137+
138+ for i , row in enumerate (sheet , 1 ):
139+ cleaned_row = {k .removeprefix (config ["people_column_prefix" ]): v for k , v in row .items ()}
140+ name_column = full_name_column (cleaned_row )
141+ name = get_value (cleaned_row , name_column ) if name_column else None
142+ flex_fields = clean_field_names (cleaned_row )
143+
144+ try :
145+ job .program .individuals .create (
146+ batch = batch ,
147+ name = name ,
148+ household_id = None , # No household association in People scenario
149+ flex_fields = flex_fields ,
150+ )
151+ except Exception as e :
152+ raise SheetProcessingError (INDIVIDUAL , i ) from e
153+
154+ processed += 1
155+
156+ return processed
157+
158+
132159def image_location (image : RDIImage ) -> tuple [int , int ]:
133160 return image .anchor ._from .row , image .anchor ._from .col
134161
@@ -166,13 +193,15 @@ def read_sheets(config: Config, filepath: str, *sheet_indices: int) -> Generator
166193 sheet_images = extract_images (filepath , * sheet_indices )
167194 for (_ , sheet ), images in zip (sheets , sheet_images , strict = False ):
168195 sheet_with_images = merge_images (sheet , images )
169- yield filter_rows_with_household_pk (config , sheet_with_images )
196+ if config ["master_detail" ]:
197+ yield filter_rows_with_household_pk (config , sheet_with_images )
198+ else :
199+ yield sheet_with_images
170200
171201
172202def import_from_rdi (job : AsyncJob ) -> dict [str , int ]:
173203 with atomic ():
174- config : Config = job .config
175- rdi = job .file
204+ config = job .config
176205 batch = Batch .objects .create (
177206 name = config ["batch_name" ],
178207 program = job .program ,
@@ -181,14 +210,12 @@ def import_from_rdi(job: AsyncJob) -> dict[str, int]:
181210 source = Batch .BatchSource .RDI ,
182211 )
183212
184- household_sheet , individual_sheet = read_sheets ( config , rdi , 0 , 1 )
185-
186- household_mapping = process_households (household_sheet , job , batch , config )
187- individuals_number = process_individuals (individual_sheet , household_mapping , job , batch , config )
188-
189- validate_beneficiaries ( config , household_mapping )
213+ if config [ "master_detail" ]:
214+ household_sheet , individual_sheet = read_sheets ( config , job . file , 0 , 1 )
215+ household_mapping = process_households (household_sheet , job , batch , config )
216+ individuals_count = process_individuals (individual_sheet , household_mapping , job , batch , config )
217+ validate_beneficiaries ( config , household_mapping )
218+ return { "household" : len ( household_mapping ), "individual" : individuals_count }
190219
191- return {
192- "household" : len (household_mapping ),
193- "individual" : individuals_number ,
194- }
220+ (people_sheet ,) = read_sheets (config , job .file , 0 )
221+ return {"people" : process_people_only (people_sheet , job , batch , config )}
0 commit comments