7878_TINY_GEO = _ROOT / "tests" / "fixtures" / "planning_areas_tiny.geojson"
7979
8080
81+ def _is_truthy (v : str | None ) -> bool :
82+ if v is None :
83+ return False
84+ return str (v ).strip ().lower () in {"1" , "true" , "yes" , "on" }
85+
86+
87+ def _on_streamlit_cloud () -> bool :
88+ # Streamlit Cloud exposes this in hosted apps.
89+ return _is_truthy (os .environ .get ("STREAMLIT_SHARING_MODE" ))
90+
91+
92+ def _default_bool_env (name : str , cloud_default : bool , local_default : bool ) -> bool :
93+ raw = os .environ .get (name )
94+ if raw is not None :
95+ return _is_truthy (raw )
96+ return cloud_default if _on_streamlit_cloud () else local_default
97+
98+
8199def _default_geo () -> str :
82100 if _PLANNING_GEO .exists ():
83101 return str (_PLANNING_GEO )
@@ -86,6 +104,47 @@ def _default_geo() -> str:
86104 return ""
87105
88106
107+ def _bootstrap_resale_if_missing (default_path : str ) -> str :
108+ p = Path (default_path )
109+ if p .exists ():
110+ return default_path
111+
112+ auto_fetch = _default_bool_env (
113+ "SINGAPORE_EDA_AUTO_DOWNLOAD_ON_MISSING" ,
114+ cloud_default = True ,
115+ local_default = False ,
116+ )
117+ if auto_fetch :
118+ max_rows_raw = os .environ .get ("SINGAPORE_EDA_BOOTSTRAP_MAX_ROWS" , "20000" )
119+ try :
120+ max_rows = max (1000 , int (str (max_rows_raw ).strip ()))
121+ except ValueError :
122+ max_rows = 20000
123+ try :
124+ from singapore_eda .download_data import download_hdb_resale
125+
126+ p .parent .mkdir (parents = True , exist_ok = True )
127+ n = download_hdb_resale (
128+ p ,
129+ max_rows = max_rows ,
130+ latest_first = True ,
131+ skip_if_fresh_hours = 24.0 ,
132+ )
133+ st .sidebar .success (f"Fetched { n :,} resale rows from data.gov.sg." )
134+ if p .exists ():
135+ return str (p )
136+ except (OSError , ValueError , RuntimeError ) as ex :
137+ st .sidebar .warning (f"Auto-download failed; using fixture fallback. ({ ex } )" )
138+
139+ if _DEFAULT_FIXTURE .exists ():
140+ st .sidebar .info (
141+ "Using fallback fixture (small sample). "
142+ "Set `SINGAPORE_EDA_AUTO_DOWNLOAD_ON_MISSING=1` to auto-fetch data."
143+ )
144+ return str (_DEFAULT_FIXTURE )
145+ return default_path
146+
147+
89148def _ols_coef_table (model : Any , top_n : int = 12 ) -> pd .DataFrame :
90149 ci = model .conf_int ()
91150 rows : list [dict [str , object ]] = []
@@ -346,8 +405,7 @@ def main() -> None:
346405 )
347406
348407 default = os .environ .get ("SINGAPORE_EDA_CSV" , str (DEFAULT_RAW_CSV ))
349- if not Path (default ).exists () and _DEFAULT_FIXTURE .exists ():
350- default = str (_DEFAULT_FIXTURE )
408+ default = _bootstrap_resale_if_missing (default )
351409 use_path = st .sidebar .text_input ("Resale CSV path" , value = default )
352410 rent_path = st .sidebar .text_input (
353411 "Median rent CSV (for yields)" ,
0 commit comments