1- from typing import Any
1+ from typing import Any , TypedDict , cast , Final , NotRequired
22
33from django .db .transaction import atomic
44
77from country_workspace .utils .fields import clean_field_name , uppercase_field_value
88
99
10+ class Config (TypedDict ):
11+ batch_name : str
12+ registration_reference_pk : int | None
13+ master_detail : bool
14+ household_column_prefix : NotRequired [str ]
15+ individuals_column_prefix : str
16+ household_label_column : NotRequired [str ]
17+ check_before : bool
18+ fail_if_alien : bool
19+
20+
21+ HEAD_RELATIONSHIP : Final [str ] = "HEAD"
22+
23+
1024def import_from_aurora (job : AsyncJob ) -> dict [str , int ]:
1125 """Import data from the Aurora system into the database within an atomic transaction.
1226
1327 Args:
1428 job (AsyncJob): The job instance containing the configuration and context for data import.
15- Expected keys in `job.config`:
16- - "batch_name" (str): The name for the newly created batch.
17- - "registration_reference_pk" (int): The unique identifier of the registration to import.
18- - "household_column_prefix" (str, optional): The prefix for household-related columns.
19- - "individuals_column_prefix" (str, optional): The prefix for individual-related columns.
20- - "household_label_column" (str, optional): The column name used to determine the household label.
29+ Expected keys in `job.config` correspond to the `Config` TypedDict.
2130
2231 Returns:
23- dict[str, int]: A dictionary with the counts of successfully created records:
24- - "households": The number of households imported.
25- - "individuals": The total number of individuals imported.
32+ dict[str, int]: Counts of imported records:
33+ - "households": Number of households imported (0 if `master_detail` is False or None) .
34+ - "individuals": Total number of individuals imported.
2635
2736 """
28- total_hh = total_ind = 0
37+ total = {"households" : 0 , "individuals" : 0 }
38+ cfg = cast (Config , job .config )
39+
2940 batch = Batch .objects .create (
30- name = job . config ["batch_name" ],
41+ name = cfg ["batch_name" ],
3142 program = job .program ,
3243 country_office = job .program .country_office ,
3344 imported_by = job .owner ,
3445 source = Batch .BatchSource .AURORA ,
3546 )
47+
3648 client = AuroraClient ()
3749 with atomic ():
38- for record in client .get (f"registration/{ job .config ['registration_reference_pk' ]} /records/" ):
39- inds_data = _collect_by_prefix (record ["flatten" ], job .config .get ("individuals_column_prefix" ))
40- if inds_data :
41- hh = create_household (batch , record ["flatten" ], job .config .get ("household_column_prefix" ))
42- total_hh += 1
43- total_ind += len (
44- create_individuals (
45- household = hh ,
46- data = inds_data ,
47- household_label_column = job .config .get ("household_label_column" ),
48- )
49- )
50- return {"households" : total_hh , "individuals" : total_ind }
50+ for record in client .get (f"registration/{ cfg ['registration_reference_pk' ]} /records/" ):
51+ individuals = create_individuals (
52+ batch = batch ,
53+ data = record ["flatten" ],
54+ cfg = cfg ,
55+ )
56+ if cfg .get ("master_detail" ) and individuals and individuals [0 ].household_id :
57+ total ["households" ] += 1
58+ total ["individuals" ] += len (individuals )
59+
60+ return total
5161
5262
5363def create_household (batch : Batch , data : dict [str , Any ], prefix : str ) -> Household :
54- """
55- Create a Household object from the provided data and associate it with a batch.
64+ """Create a Household object from the provided data and associate it with a batch.
5665
5766 Args:
5867 batch (Batch): The batch to which the household will be linked.
@@ -69,36 +78,47 @@ def create_household(batch: Batch, data: dict[str, Any], prefix: str) -> Househo
6978 flex_fields = _collect_by_prefix (data , prefix )
7079 if len (flex_fields ) > 1 :
7180 raise ValueError ("Multiple households found" )
81+ flex_fields = next (iter (flex_fields .values ()), {})
7282 return batch .program .households .create (batch = batch , flex_fields = flex_fields )
7383
7484
75- def create_individuals (household : Household , data : dict [str , Any ], household_label_column : str ) -> list [Individual ]:
76- """Create and associate Individual objects with a given Household.
85+ def create_individuals (
86+ batch : Batch ,
87+ data : dict [str , Any ],
88+ cfg : Config ,
89+ ) -> list [Individual ]:
90+ """Create and associate Individual objects with an optional Household.
7791
7892 Args:
79- household (Household ): The household to which the individuals will be linked.
80- data (dict[str, Any]): A dictionary mapping indices to individual details .
81- household_label_column (str ): The key in the individual data used to determine the household label .
93+ batch (Batch ): The batch to which individuals will be linked.
94+ data (dict[str, Any]): A dictionary containing related information .
95+ cfg (Config ): Configuration dictionary containing various settings for the import process .
8296
8397 Returns:
8498 list[Individual]: A list of successfully created Individual instances.
8599
86100 """
87- individuals = []
101+ household , individuals = None , []
88102 head_found = False
89103
90- for individual in data .values ():
91- if not head_found :
104+ inds_data = _collect_by_prefix (data , cfg .get ("individuals_column_prefix" ))
105+
106+ if all ((inds_data , cfg ["master_detail" ], cfg .get ("household_column_prefix" ))):
107+ household = create_household (batch , data , cfg .get ("household_column_prefix" ))
108+
109+ for individual in inds_data .values ():
110+ household_label_column = cfg .get ("household_label_column" )
111+ if household and household_label_column and not head_found :
92112 head_found = _update_household_label_from_individual (household , individual , household_label_column )
93113 individuals .append (
94114 Individual (
95- batch = household . batch ,
96- household_id = household .pk ,
115+ batch = batch ,
116+ household_id = household .pk if household else None ,
97117 name = individual .get ("given_name" , "" ),
98118 flex_fields = individual ,
99- ),
119+ )
100120 )
101- return household .program .individuals .bulk_create (individuals )
121+ return batch .program .individuals .bulk_create (individuals , batch_size = 1000 )
102122
103123
104124def _collect_by_prefix (data : dict [str , Any ], prefix : str ) -> dict [str , dict [str , Any ]]:
@@ -123,11 +143,12 @@ def _collect_by_prefix(data: dict[str, Any], prefix: str) -> dict[str, dict[str,
123143
124144 """
125145 result = {}
126- for k , v in data .items ():
127- if (stripped := k .removeprefix (prefix )) != k :
128- index , field = stripped .split ("_" , 1 )
129- field_clean = clean_field_name (field )
130- result .setdefault (index , {})[field_clean ] = uppercase_field_value (field_clean , v )
146+ for key , value in data .items ():
147+ if not key .startswith (prefix ):
148+ continue
149+ index , field = key .removeprefix (prefix ).split ("_" , 1 )
150+ clean_field = clean_field_name (field )
151+ result .setdefault (index , {})[clean_field ] = uppercase_field_value (clean_field , value )
131152 return result
132153
133154
0 commit comments