ANL2001 commited on
Commit
058ca07
Β·
1 Parent(s): 094b972

feat: add Streamlit web app

Browse files

Interactive UI with sliders and dropdowns for all house features.
Loads model artifacts directly β€” no API dependency needed.
Deployable for free on Streamlit Community Cloud.

Files changed (2) hide show
  1. requirements.txt +1 -0
  2. streamlit_app.py +163 -0
requirements.txt CHANGED
@@ -1,3 +1,4 @@
 
1
  fastapi>=0.111.0
2
  uvicorn[standard]>=0.29.0
3
  sqlalchemy>=2.0.0
 
1
+ streamlit>=1.35.0
2
  fastapi>=0.111.0
3
  uvicorn[standard]>=0.29.0
4
  sqlalchemy>=2.0.0
streamlit_app.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ import joblib
5
+ import streamlit as st
6
+
7
+ ARTIFACTS_DIR = os.path.join(os.path.dirname(__file__), "ml", "artifacts")
8
+
9
+ @st.cache_resource
10
+ def load_model():
11
+ processor = joblib.load(os.path.join(ARTIFACTS_DIR, "manual_processor.joblib"))
12
+ stack = joblib.load(os.path.join(ARTIFACTS_DIR, "stack.joblib"))
13
+ features = joblib.load(os.path.join(ARTIFACTS_DIR, "core_features.joblib"))
14
+ return processor, stack, features
15
+
16
+
17
+ def predict(processor, stack, core_features, inputs: dict) -> float:
18
+ df = pd.DataFrame([inputs])
19
+ X = df[core_features].copy()
20
+ X_processed = processor.transform(X)
21
+ log_pred = stack.predict(X_processed)
22
+ return float(np.expm1(log_pred)[0])
23
+
24
+
25
+ # ── Page config ───────────────────────────────────────────────────────────────
26
+ st.set_page_config(
27
+ page_title="House Price Predictor",
28
+ page_icon="🏠",
29
+ layout="wide",
30
+ )
31
+
32
+ st.title("🏠 House Price Predictor")
33
+ st.caption("Stacking ensemble: GradientBoosting + CatBoost + KernelRidge Β· Top 16 / 4,000+ on Kaggle")
34
+ st.divider()
35
+
36
+ # ── Load model ────────────────────────────────────────────────────────────────
37
+ try:
38
+ processor, stack, core_features = load_model()
39
+ except Exception as e:
40
+ st.error(f"Failed to load model artifacts: {e}")
41
+ st.info("Run `python -m ml.export_model` first to generate the artifacts.")
42
+ st.stop()
43
+
44
+ # ── Input form ────────────────────────────────────────────────────────────────
45
+ st.subheader("House Features")
46
+
47
+ col1, col2, col3 = st.columns(3)
48
+
49
+ with col1:
50
+ st.markdown("**Size**")
51
+ GrLivArea = st.number_input("Above-grade living area (sq ft)", 300, 6000, 1500, step=50)
52
+ TotalBsmtSF = st.number_input("Total basement area (sq ft)", 0, 3000, 800, step=50)
53
+ GarageArea = st.number_input("Garage area (sq ft)", 0, 1500, 400, step=50)
54
+ LotArea = st.number_input("Lot area (sq ft)", 1000, 100000, 8000, step=500)
55
+ LotFrontage = st.number_input("Lot frontage (ft)", 0, 200, 65, step=5)
56
+
57
+ st.markdown("**Floors & Extras**")
58
+ floor_1st = st.number_input("1st floor area (sq ft)", 0, 4000, 856, step=50)
59
+ floor_2nd = st.number_input("2nd floor area (sq ft)", 0, 2000, 0, step=50)
60
+ WoodDeckSF = st.number_input("Wood deck (sq ft)", 0, 800, 0, step=10)
61
+ OpenPorchSF = st.number_input("Open porch (sq ft)", 0, 500, 0, step=10)
62
+
63
+ with col2:
64
+ st.markdown("**Quality & Condition**")
65
+ OverallQual = st.slider("Overall quality (1–10)", 1, 10, 6)
66
+ OverallCond = st.slider("Overall condition (1–9)", 1, 9, 5)
67
+ KitchenQual = st.selectbox("Kitchen quality", ["Ex", "Gd", "TA", "Fa", "Po"], index=2)
68
+ ExterQual = st.selectbox("Exterior quality", ["Ex", "Gd", "TA", "Fa", "Po"], index=2)
69
+ BsmtQual = st.selectbox("Basement quality", ["Ex", "Gd", "TA", "Fa", "Po", None], index=2)
70
+ HeatingQC = st.selectbox("Heating quality", ["Ex", "Gd", "TA", "Fa", "Po"], index=2)
71
+ FireplaceQu = st.selectbox("Fireplace quality", [None, "Ex", "Gd", "TA", "Fa", "Po"], index=0)
72
+
73
+ st.markdown("**Bathrooms & Rooms**")
74
+ FullBath = st.number_input("Full baths (above grade)", 0, 4, 2)
75
+ HalfBath = st.number_input("Half baths (above grade)", 0, 2, 0)
76
+ BsmtFullBath = st.number_input("Basement full baths", 0, 2, 0)
77
+ TotRmsAbvGrd = st.number_input("Total rooms above grade", 2, 14, 7)
78
+ Fireplaces = st.number_input("Fireplaces", 0, 4, 0)
79
+
80
+ with col3:
81
+ st.markdown("**Year & Location**")
82
+ YearBuilt = st.number_input("Year built", 1870, 2024, 2000)
83
+ YearRemodAdd = st.number_input("Year remodelled", 1950, 2024, 2000)
84
+ YrSold = st.number_input("Year sold", 2006, 2010, 2010)
85
+ Neighborhood = st.selectbox("Neighborhood", [
86
+ "NAmes", "CollgCr", "OldTown", "Edwards", "Somerst", "NridgHt",
87
+ "Gilbert", "Sawyer", "NWAmes", "SawyerW", "BrkSide", "Crawfor",
88
+ "Mitchel", "NoRidge", "Timber", "IDOTRR", "ClearCr", "StoneBr",
89
+ "SWISU", "MeadowV", "Blmngtn", "BrDale", "Veenker", "NPkVill", "Blueste",
90
+ ])
91
+ MSZoning = st.selectbox("Zoning", ["RL", "RM", "FV", "RH", "C (all)"], index=0)
92
+ BldgType = st.selectbox("Building type", ["1Fam", "2fmCon", "Duplex", "TwnhsE", "Twnhs"], index=0)
93
+ Foundation = st.selectbox("Foundation", ["PConc", "CBlock", "BrkTil", "Wood", "Slab", "Stone"], index=0)
94
+
95
+ st.markdown("**Garage & Utilities**")
96
+ GarageQual = st.selectbox("Garage quality", ["TA", "Gd", "Fa", "Ex", "Po"], index=0)
97
+ GarageCond = st.selectbox("Garage condition", ["TA", "Gd", "Fa", "Ex", "Po"], index=0)
98
+ Electrical = st.selectbox("Electrical system", ["SBrkr", "FuseA", "FuseF", "FuseP", "Mix"], index=0)
99
+
100
+ # ── Predict ───────────────────────────────────────────────────────────────────
101
+ st.divider()
102
+
103
+ if st.button("Predict Price", type="primary", use_container_width=True):
104
+ inputs = {
105
+ "GrLivArea": float(GrLivArea),
106
+ "TotalBsmtSF": float(TotalBsmtSF),
107
+ "LotArea": float(LotArea),
108
+ "GarageArea": float(GarageArea),
109
+ "PoolArea": 0.0,
110
+ "LotFrontage": float(LotFrontage) if LotFrontage else None,
111
+ "2ndFlrSF": float(floor_2nd),
112
+ "LowQualFinSF": 0.0,
113
+ "BsmtUnfSF": 0.0,
114
+ "1stFlrSF": float(floor_1st),
115
+ "WoodDeckSF": float(WoodDeckSF),
116
+ "OpenPorchSF": float(OpenPorchSF),
117
+ "EnclosedPorch": 0.0,
118
+ "3SsnPorch": 0.0,
119
+ "ScreenPorch": 0.0,
120
+ "FullBath": int(FullBath),
121
+ "HalfBath": int(HalfBath),
122
+ "BsmtFullBath": int(BsmtFullBath),
123
+ "BsmtHalfBath": 0,
124
+ "TotRmsAbvGrd": int(TotRmsAbvGrd),
125
+ "Fireplaces": int(Fireplaces),
126
+ "YearBuilt": int(YearBuilt),
127
+ "YrSold": int(YrSold),
128
+ "YearRemodAdd": int(YearRemodAdd),
129
+ "OverallQual": int(OverallQual),
130
+ "OverallCond": int(OverallCond),
131
+ "HeatingQC": HeatingQC,
132
+ "BsmtQual": BsmtQual,
133
+ "PoolQC": None,
134
+ "ExterQual": ExterQual,
135
+ "KitchenQual": KitchenQual,
136
+ "Functional": "Typ",
137
+ "FireplaceQu": FireplaceQu,
138
+ "BsmtCond": "TA",
139
+ "ExterCond": "TA",
140
+ "Neighborhood": Neighborhood,
141
+ "MSZoning": MSZoning,
142
+ "MSSubClass": 20,
143
+ "LandSlope": "Gtl",
144
+ "Alley": None,
145
+ "LandContour": "Lvl",
146
+ "BldgType": BldgType,
147
+ "Condition1": "Norm",
148
+ "RoofStyle": "Gable",
149
+ "Foundation": Foundation,
150
+ "SaleCondition": "Normal",
151
+ "Exterior1st": "VinylSd",
152
+ "Utilities": "AllPub",
153
+ "Electrical": Electrical,
154
+ "GarageQual": GarageQual,
155
+ "GarageCond": GarageCond,
156
+ }
157
+
158
+ with st.spinner("Predicting..."):
159
+ try:
160
+ price = predict(processor, stack, core_features, inputs)
161
+ st.success(f"### Estimated Sale Price: **${price:,.0f}**")
162
+ except Exception as e:
163
+ st.error(f"Prediction error: {e}")