Delete app.py
Browse files
app.py
DELETED
|
@@ -1,257 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Restaurant Health Grade Predictor
|
| 3 |
-
----------------------------------
|
| 4 |
-
A Gradio app that predicts health inspection grades (A/B/C)
|
| 5 |
-
using a placeholder Random Forest model trained on synthetic data.
|
| 6 |
-
|
| 7 |
-
Requirements:
|
| 8 |
-
pip install gradio scikit-learn matplotlib numpy pandas
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import gradio as gr
|
| 12 |
-
import numpy as np
|
| 13 |
-
import pandas as pd
|
| 14 |
-
import matplotlib.pyplot as plt
|
| 15 |
-
import matplotlib.patches as mpatches
|
| 16 |
-
from sklearn.ensemble import RandomForestClassifier
|
| 17 |
-
from sklearn.preprocessing import LabelEncoder
|
| 18 |
-
import warnings
|
| 19 |
-
|
| 20 |
-
warnings.filterwarnings("ignore")
|
| 21 |
-
|
| 22 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 23 |
-
# 1. Build a placeholder Random Forest model on synthetic data
|
| 24 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 25 |
-
|
| 26 |
-
CUISINE_TYPES = [
|
| 27 |
-
"American", "Chinese", "Italian", "Mexican", "Japanese",
|
| 28 |
-
"Indian", "Thai", "Mediterranean", "French", "Korean",
|
| 29 |
-
]
|
| 30 |
-
|
| 31 |
-
VIOLATION_CODES = [
|
| 32 |
-
"No Violation",
|
| 33 |
-
"02A - No food safety certificate",
|
| 34 |
-
"04L - Evidence of mice or rats",
|
| 35 |
-
"06C - Food not protected",
|
| 36 |
-
"08A - Facility not sanitized",
|
| 37 |
-
"10B - Plumbing not properly installed",
|
| 38 |
-
"15L - Workers not using proper hygiene",
|
| 39 |
-
]
|
| 40 |
-
|
| 41 |
-
GRADE_LABELS = ["A", "B", "C"]
|
| 42 |
-
|
| 43 |
-
# Encode categorical features
|
| 44 |
-
cuisine_enc = LabelEncoder().fit(CUISINE_TYPES)
|
| 45 |
-
violation_enc = LabelEncoder().fit(VIOLATION_CODES)
|
| 46 |
-
|
| 47 |
-
def encode_inputs(cuisine: str, violation: str, score: float) -> np.ndarray:
|
| 48 |
-
c = cuisine_enc.transform([cuisine])[0]
|
| 49 |
-
v = violation_enc.transform([violation])[0]
|
| 50 |
-
return np.array([[c, v, score]])
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
def generate_synthetic_data(n: int = 2000, seed: int = 42) -> tuple:
|
| 54 |
-
rng = np.random.default_rng(seed)
|
| 55 |
-
cuisines = rng.integers(0, len(CUISINE_TYPES), n)
|
| 56 |
-
violations = rng.integers(0, len(VIOLATION_CODES), n)
|
| 57 |
-
scores = rng.uniform(0, 100, n)
|
| 58 |
-
|
| 59 |
-
# Grade logic: score drives grade; violations add noise
|
| 60 |
-
grades = []
|
| 61 |
-
for i in range(n):
|
| 62 |
-
base = scores[i]
|
| 63 |
-
penalty = violations[i] * 3 # higher code → worse grade
|
| 64 |
-
effective = base - penalty
|
| 65 |
-
if effective >= 60:
|
| 66 |
-
grades.append(0) # A
|
| 67 |
-
elif effective >= 40:
|
| 68 |
-
grades.append(1) # B
|
| 69 |
-
else:
|
| 70 |
-
grades.append(2) # C
|
| 71 |
-
|
| 72 |
-
X = np.column_stack([cuisines, violations, scores])
|
| 73 |
-
y = np.array(grades)
|
| 74 |
-
return X, y
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
print("Training placeholder Random Forest model …")
|
| 78 |
-
X_train, y_train = generate_synthetic_data()
|
| 79 |
-
model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
|
| 80 |
-
model.fit(X_train, y_train)
|
| 81 |
-
print("Model ready ✓")
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 85 |
-
# 2. Prediction + chart function
|
| 86 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 87 |
-
|
| 88 |
-
GRADE_COLORS = {
|
| 89 |
-
"A": "#2ECC71", # green
|
| 90 |
-
"B": "#F39C12", # amber
|
| 91 |
-
"C": "#E74C3C", # red
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
def predict_grade(cuisine: str, violation: str, score: float):
|
| 95 |
-
"""Run inference and return a grade label and a probability bar chart."""
|
| 96 |
-
X = encode_inputs(cuisine, violation, score)
|
| 97 |
-
proba = model.predict_proba(X)[0] # shape (3,)
|
| 98 |
-
pred_idx = int(np.argmax(proba))
|
| 99 |
-
grade = GRADE_LABELS[pred_idx]
|
| 100 |
-
confidence = proba[pred_idx] * 100
|
| 101 |
-
|
| 102 |
-
# ── build the bar chart ──────────────────────────────────────────────────
|
| 103 |
-
fig, ax = plt.subplots(figsize=(6, 3.5))
|
| 104 |
-
fig.patch.set_facecolor("#1A1A2E")
|
| 105 |
-
ax.set_facecolor("#16213E")
|
| 106 |
-
|
| 107 |
-
bar_colors = [GRADE_COLORS[g] for g in GRADE_LABELS]
|
| 108 |
-
bars = ax.bar(
|
| 109 |
-
GRADE_LABELS,
|
| 110 |
-
proba * 100,
|
| 111 |
-
color=bar_colors,
|
| 112 |
-
width=0.5,
|
| 113 |
-
edgecolor="none",
|
| 114 |
-
zorder=3,
|
| 115 |
-
)
|
| 116 |
-
|
| 117 |
-
# highlight the predicted grade with a glow border
|
| 118 |
-
pred_bar = bars[pred_idx]
|
| 119 |
-
pred_bar.set_linewidth(2.5)
|
| 120 |
-
pred_bar.set_edgecolor("white")
|
| 121 |
-
|
| 122 |
-
# value labels on bars
|
| 123 |
-
for bar, p in zip(bars, proba * 100):
|
| 124 |
-
ax.text(
|
| 125 |
-
bar.get_x() + bar.get_width() / 2,
|
| 126 |
-
bar.get_height() + 1.5,
|
| 127 |
-
f"{p:.1f}%",
|
| 128 |
-
ha="center", va="bottom",
|
| 129 |
-
color="white", fontsize=11, fontweight="bold",
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
ax.set_ylim(0, 110)
|
| 133 |
-
ax.set_xlabel("Predicted Grade", color="#AAAACC", fontsize=11, labelpad=8)
|
| 134 |
-
ax.set_ylabel("Probability (%)", color="#AAAACC", fontsize=11, labelpad=8)
|
| 135 |
-
ax.set_title(
|
| 136 |
-
f"Model Confidence — Predicted Grade: {grade} ({confidence:.1f}%)",
|
| 137 |
-
color="white", fontsize=13, fontweight="bold", pad=12,
|
| 138 |
-
)
|
| 139 |
-
ax.tick_params(colors="white", labelsize=12)
|
| 140 |
-
for spine in ax.spines.values():
|
| 141 |
-
spine.set_visible(False)
|
| 142 |
-
ax.yaxis.grid(True, color="#2A2A4A", linewidth=0.8, zorder=0)
|
| 143 |
-
ax.set_axisbelow(True)
|
| 144 |
-
|
| 145 |
-
plt.tight_layout()
|
| 146 |
-
|
| 147 |
-
# ── compose the text output ───────────────────────────────────────────────
|
| 148 |
-
emoji = {"A": "🟢", "B": "🟡", "C": "🔴"}[grade]
|
| 149 |
-
summary = (
|
| 150 |
-
f"{emoji} Predicted Health Grade: **{grade}**\n\n"
|
| 151 |
-
f"Confidence: {confidence:.1f}%\n\n"
|
| 152 |
-
f"---\n"
|
| 153 |
-
f"| Input | Value |\n"
|
| 154 |
-
f"|---|---|\n"
|
| 155 |
-
f"| Cuisine | {cuisine} |\n"
|
| 156 |
-
f"| Violation | {violation} |\n"
|
| 157 |
-
f"| Inspection Score | {score:.1f} |\n\n"
|
| 158 |
-
f"*Note: This uses a placeholder Random Forest model trained on "
|
| 159 |
-
f"synthetic data. Replace `generate_synthetic_data()` and re-train "
|
| 160 |
-
f"with real inspection records for production use.*"
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
return summary, fig
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 167 |
-
# 3. Gradio UI
|
| 168 |
-
# ──────────────────────────────────────────────────────────────────────────────
|
| 169 |
-
|
| 170 |
-
DESCRIPTION = """
|
| 171 |
-
## 🍽️ Restaurant Health Grade Predictor
|
| 172 |
-
|
| 173 |
-
Enter inspection details below to get a predicted **A / B / C** health grade
|
| 174 |
-
and a probability breakdown from the Random Forest model.
|
| 175 |
-
"""
|
| 176 |
-
|
| 177 |
-
with gr.Blocks(
|
| 178 |
-
title="Health Grade Predictor",
|
| 179 |
-
theme=gr.themes.Soft(
|
| 180 |
-
primary_hue="violet",
|
| 181 |
-
secondary_hue="slate",
|
| 182 |
-
neutral_hue="slate",
|
| 183 |
-
),
|
| 184 |
-
css="""
|
| 185 |
-
.predict-btn { font-size: 1.1rem !important; padding: 0.7rem !important; }
|
| 186 |
-
#grade-output .prose { font-size: 1.05rem !important; }
|
| 187 |
-
""",
|
| 188 |
-
) as demo:
|
| 189 |
-
|
| 190 |
-
gr.Markdown(DESCRIPTION)
|
| 191 |
-
|
| 192 |
-
with gr.Row():
|
| 193 |
-
with gr.Column(scale=1):
|
| 194 |
-
cuisine_input = gr.Dropdown(
|
| 195 |
-
choices=CUISINE_TYPES,
|
| 196 |
-
value="American",
|
| 197 |
-
label="🍜 Cuisine Type",
|
| 198 |
-
)
|
| 199 |
-
violation_input = gr.Dropdown(
|
| 200 |
-
choices=VIOLATION_CODES,
|
| 201 |
-
value="No Violation",
|
| 202 |
-
label="⚠️ Violation Code",
|
| 203 |
-
)
|
| 204 |
-
score_input = gr.Slider(
|
| 205 |
-
minimum=0,
|
| 206 |
-
maximum=100,
|
| 207 |
-
value=85,
|
| 208 |
-
step=0.5,
|
| 209 |
-
label="📊 Inspection Score (0 = worst, 100 = best)",
|
| 210 |
-
)
|
| 211 |
-
predict_btn = gr.Button(
|
| 212 |
-
"🔍 Predict Grade",
|
| 213 |
-
variant="primary",
|
| 214 |
-
elem_classes="predict-btn",
|
| 215 |
-
)
|
| 216 |
-
|
| 217 |
-
with gr.Column(scale=2):
|
| 218 |
-
grade_output = gr.Markdown(
|
| 219 |
-
value="*Fill in the inputs and click **Predict Grade**.*",
|
| 220 |
-
elem_id="grade-output",
|
| 221 |
-
)
|
| 222 |
-
chart_output = gr.Plot(label="Grade Probability Distribution")
|
| 223 |
-
|
| 224 |
-
predict_btn.click(
|
| 225 |
-
fn=predict_grade,
|
| 226 |
-
inputs=[cuisine_input, violation_input, score_input],
|
| 227 |
-
outputs=[grade_output, chart_output],
|
| 228 |
-
)
|
| 229 |
-
|
| 230 |
-
gr.Examples(
|
| 231 |
-
examples=[
|
| 232 |
-
["Italian", "No Violation", 95],
|
| 233 |
-
["Chinese", "04L - Evidence of mice or rats", 55],
|
| 234 |
-
["Mexican", "08A - Facility not sanitized", 40],
|
| 235 |
-
["Japanese", "02A - No food safety certificate",72],
|
| 236 |
-
["Mediterranean","15L - Workers not using proper hygiene", 30],
|
| 237 |
-
],
|
| 238 |
-
inputs=[cuisine_input, violation_input, score_input],
|
| 239 |
-
outputs=[grade_output, chart_output],
|
| 240 |
-
fn=predict_grade,
|
| 241 |
-
cache_examples=True,
|
| 242 |
-
label="📌 Quick Examples",
|
| 243 |
-
)
|
| 244 |
-
|
| 245 |
-
gr.Markdown(
|
| 246 |
-
"""
|
| 247 |
-
---
|
| 248 |
-
**How grades work (synthetic rules used for training)**
|
| 249 |
-
`Effective Score = Inspection Score − (Violation Code Index × 3)`
|
| 250 |
-
• **A** → Effective ≥ 60 | **B** → 40–59 | **C** → < 40
|
| 251 |
-
|
| 252 |
-
Replace `generate_synthetic_data()` with a real labelled dataset to make this production-ready.
|
| 253 |
-
"""
|
| 254 |
-
)
|
| 255 |
-
|
| 256 |
-
if __name__ == "__main__":
|
| 257 |
-
demo.launch(share=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|