@@ -224,16 +224,34 @@ def _add_postcode_column(df: PandasDataFrame, postcodes: str):
224224 if isinstance (postcodes , list ):
225225 postcodes = postcodes [0 ]
226226
227- if postcodes != "full_address_postcode" :
228- df = df .rename (columns = {postcodes : "postcode" })
227+ # Helper: normalise a postcode-like series to strings with blanks for missing
228+ def _norm_pc (s : pd .Series ) -> pd .Series :
229+ return s .fillna ("" ).astype (str ).str .strip ()
230+
231+ if postcodes == "full_address_postcode" :
232+ # Extract postcode from full address text into the same column name, then treat
233+ # it as the candidate postcode series.
234+ df ["full_address_postcode" ] = extract_postcode (df , "full_address_postcode" )[0 ]
235+ candidate = _norm_pc (df ["full_address_postcode" ])
236+ source_col = "full_address_postcode"
229237 else :
230- # print(df["full_address_postcode"])
231- # print(extract_postcode(df,"full_address_postcode"))
232- df ["full_address_postcode" ] = extract_postcode (df , "full_address_postcode" )[
233- 0
234- ] #
235- df = df .rename (columns = {postcodes : "postcode" })
236- # print(df)
238+ if postcodes not in df .columns :
239+ return df
240+ candidate = _norm_pc (df [postcodes ])
241+ source_col = postcodes
242+
243+ # Avoid creating duplicate column names ("postcode" already exists in some inputs).
244+ if "postcode" in df .columns :
245+ existing = _norm_pc (df ["postcode" ])
246+ use_existing = existing .ne ("" )
247+ df ["postcode" ] = existing .where (use_existing , candidate )
248+ # Drop the source column if it's not the canonical postcode column.
249+ if source_col != "postcode" and source_col in df .columns :
250+ df = df .drop (columns = [source_col ])
251+ else :
252+ df ["postcode" ] = candidate
253+ if source_col != "postcode" and source_col in df .columns :
254+ df = df .drop (columns = [source_col ])
237255
238256 return df
239257
@@ -342,6 +360,35 @@ def prepare_ref_address(
342360
343361 ref_df_cleaned ["ref_index" ] = ref_df_cleaned .index
344362
363+ # --- Postcode column normalisation ---
364+ # Prevent duplicate postcode column names from propagating downstream (which can
365+ # make df["Postcode"] return a DataFrame and break `.str` operations).
366+ #
367+ # Canonical reference postcode column name in this codebase is "Postcode".
368+ if (
369+ "Postcode" not in ref_df_cleaned .columns
370+ and "postcode" in ref_df_cleaned .columns
371+ ):
372+ ref_df_cleaned = ref_df_cleaned .rename (columns = {"postcode" : "Postcode" })
373+
374+ # If both exist, prefer non-blank values in "Postcode" and fill from "postcode".
375+ if "Postcode" in ref_df_cleaned .columns and "postcode" in ref_df_cleaned .columns :
376+ _pc_main = ref_df_cleaned ["Postcode" ].fillna ("" ).astype (str ).str .strip ()
377+ _pc_alt = ref_df_cleaned ["postcode" ].fillna ("" ).astype (str ).str .strip ()
378+ ref_df_cleaned ["Postcode" ] = _pc_main .where (_pc_main .ne ("" ), _pc_alt )
379+ ref_df_cleaned = ref_df_cleaned .drop (columns = ["postcode" ])
380+
381+ # If there are duplicated "Postcode" columns (possible after merges), keep the first.
382+ if (
383+ hasattr (ref_df_cleaned .columns , "duplicated" )
384+ and ref_df_cleaned .columns .duplicated ().any ()
385+ ):
386+ dup_names = ref_df_cleaned .columns [ref_df_cleaned .columns .duplicated ()].tolist ()
387+ if "Postcode" in dup_names :
388+ ref_df_cleaned = ref_df_cleaned .loc [
389+ :, ~ ref_df_cleaned .columns .duplicated ()
390+ ].copy ()
391+
345392 # In on-prem LPI db street has been excluded, so put this back in
346393 if ("Street" not in ref_df_cleaned .columns ) & (
347394 "Address_LPI" in ref_df_cleaned .columns
0 commit comments