Upload health_grade_predictor.py

#135
by Danielescp - opened
Files changed (1) hide show
  1. health_grade_predictor.py +257 -0
health_grade_predictor.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 &nbsp;|&nbsp; **B** β†’ 40–59 &nbsp;|&nbsp; **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)