Skip to content

Commit 05afb1a

Browse files
committed
fix(streamlit): auto-download resale data on cloud when csv missing
1 parent 99e3fff commit 05afb1a

1 file changed

Lines changed: 60 additions & 2 deletions

File tree

streamlit_app.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@
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+
8199
def _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+
89148
def _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

Comments
 (0)