2323from sqlalchemy .dialects .postgresql import insert
2424from sqlalchemy .orm import Session
2525
26- from db import NMA_WeatherPhotos
26+ from db import NMA_WeatherData , NMA_WeatherPhotos
27+ from db .engine import session_ctx
2728from transfers .logger import logger
2829from transfers .transferer import Transferer
2930from transfers .util import replace_nans
@@ -37,19 +38,44 @@ class WeatherPhotosTransferer(Transferer):
3738 def __init__ (self , * args , batch_size : int = 1000 , ** kwargs ):
3839 super ().__init__ (* args , ** kwargs )
3940 self .batch_size = batch_size
41+ self ._weather_id_cache : set [str ] = set ()
42+ self ._build_weather_id_cache ()
43+
44+ def _build_weather_id_cache (self ) -> None :
45+ with session_ctx () as session :
46+ weather_ids = session .query (NMA_WeatherData .weather_id ).all ()
47+ for (weather_id ,) in weather_ids :
48+ if weather_id :
49+ self ._weather_id_cache .add (self ._normalize_weather_id (weather_id ))
50+ logger .info (
51+ "Built WeatherData cache with %s weather ids" ,
52+ len (self ._weather_id_cache ),
53+ )
4054
4155 def _get_dfs (self ) -> tuple [pd .DataFrame , pd .DataFrame ]:
4256 df = self ._read_csv (self .source_table )
4357 cleaned_df = replace_nans (df )
4458 return df , cleaned_df
4559
4660 def _transfer_hook (self , session : Session ) -> None :
47- rows = [self ._row_dict (row ) for row in self .cleaned_df .to_dict ("records" )]
61+ rows : list [dict [str , Any ]] = []
62+ skipped_missing_parent = 0
63+ for raw in self .cleaned_df .to_dict ("records" ):
64+ record = self ._row_dict (raw )
65+ if record is None :
66+ skipped_missing_parent += 1
67+ continue
68+ rows .append (record )
4869 rows = self ._dedupe_rows (rows , key = "GlobalID" )
4970
5071 if not rows :
5172 logger .info ("No WeatherPhotos rows to transfer" )
5273 return
74+ if skipped_missing_parent :
75+ logger .warning (
76+ "Skipped %s WeatherPhotos rows without matching WeatherData" ,
77+ skipped_missing_parent ,
78+ )
5379
5480 insert_stmt = insert (NMA_WeatherPhotos )
5581 excluded = insert_stmt .excluded
@@ -74,9 +100,16 @@ def _transfer_hook(self, session: Session) -> None:
74100 session .execute (stmt )
75101 session .commit ()
76102
77- def _row_dict (self , row : dict [str , Any ]) -> dict [str , Any ]:
103+ def _row_dict (self , row : dict [str , Any ]) -> Optional [dict [str , Any ]]:
104+ weather_id = self ._uuid_val (row .get ("WeatherID" ))
105+ if weather_id is None or not self ._has_weather_id (weather_id ):
106+ logger .warning (
107+ "Skipping WeatherPhotos WeatherID=%s - WeatherData not found" ,
108+ weather_id ,
109+ )
110+ return None
78111 return {
79- "WeatherID" : self . _uuid_val ( row . get ( "WeatherID" )) ,
112+ "WeatherID" : weather_id ,
80113 "PointID" : row .get ("PointID" ),
81114 "OLEPath" : row .get ("OLEPath" ),
82115 "OBJECTID" : row .get ("OBJECTID" ),
@@ -107,6 +140,13 @@ def _uuid_val(self, value: Any) -> Optional[UUID]:
107140 return None
108141 return None
109142
143+ def _has_weather_id (self , weather_id : UUID ) -> bool :
144+ return self ._normalize_weather_id (weather_id ) in self ._weather_id_cache
145+
146+ @staticmethod
147+ def _normalize_weather_id (value : UUID ) -> str :
148+ return str (value ).strip ().lower ()
149+
110150
111151def run (batch_size : int = 1000 ) -> None :
112152 """Entrypoint to execute the transfer."""
0 commit comments