yohannesdesta commited on
Commit
aa557dc
·
1 Parent(s): 1272d50

ui, null handle

Browse files
Files changed (3) hide show
  1. app.py +23 -9
  2. requirements.txt +1 -0
  3. templates/index.html +251 -31
app.py CHANGED
@@ -4,6 +4,8 @@ from fastapi.templating import Jinja2Templates
4
  from fastapi.requests import Request
5
  import pickle
6
  import pandas as pd
 
 
7
 
8
  app = FastAPI()
9
  templates = Jinja2Templates(directory="templates")
@@ -12,6 +14,16 @@ model = pickle.load(open("hiring_model.pkl", "rb"))
12
 
13
  def predict_job(job_data):
14
  df = pd.DataFrame([job_data])
 
 
 
 
 
 
 
 
 
 
15
  probability = model.predict_proba(df)[0][1]
16
  prediction = "HIRED" if probability > 0.5 else "NOT_HIRED"
17
 
@@ -21,22 +33,24 @@ def predict_job(job_data):
21
  "confidence": "High" if probability > 0.7 or probability < 0.3 else "Medium"
22
  }
23
 
 
24
  @app.get("/", response_class=HTMLResponse)
25
  def home(request: Request):
26
  return templates.TemplateResponse("index.html", {"request": request})
27
 
 
28
  @app.post("/predict", response_class=HTMLResponse)
29
  def predict(
30
  request: Request,
31
- client_hire_rate: float = Form(...),
32
- client_age_years: float = Form(...),
33
- client_active_hires: int = Form(...),
34
- client_avg_hourly_rate: float = Form(...),
35
- client_total_reviews: int = Form(...),
36
- client_total_hires: int = Form(...),
37
- client_total_hours: int = Form(...),
38
- client_total_spent: float = Form(...),
39
- client_rating: float = Form(...)
40
  ):
41
  job_data = {
42
  "client_hire_rate": client_hire_rate,
 
4
  from fastapi.requests import Request
5
  import pickle
6
  import pandas as pd
7
+ from typing import Optional
8
+ import numpy as np
9
 
10
  app = FastAPI()
11
  templates = Jinja2Templates(directory="templates")
 
14
 
15
  def predict_job(job_data):
16
  df = pd.DataFrame([job_data])
17
+
18
+ # Convert None → NaN
19
+ df = df.replace({None: np.nan})
20
+
21
+ # OPTION A (recommended): fill with 0
22
+ df = df.fillna(0)
23
+
24
+ # OPTION B (better if you have training means):
25
+ # df = df.fillna(TRAINING_MEANS_DICT)
26
+
27
  probability = model.predict_proba(df)[0][1]
28
  prediction = "HIRED" if probability > 0.5 else "NOT_HIRED"
29
 
 
33
  "confidence": "High" if probability > 0.7 or probability < 0.3 else "Medium"
34
  }
35
 
36
+
37
  @app.get("/", response_class=HTMLResponse)
38
  def home(request: Request):
39
  return templates.TemplateResponse("index.html", {"request": request})
40
 
41
+
42
  @app.post("/predict", response_class=HTMLResponse)
43
  def predict(
44
  request: Request,
45
+ client_hire_rate: Optional[float] = Form(None),
46
+ client_age_years: Optional[float] = Form(None),
47
+ client_active_hires: Optional[int] = Form(None),
48
+ client_avg_hourly_rate: Optional[float] = Form(None),
49
+ client_total_reviews: Optional[int] = Form(None),
50
+ client_total_hires: Optional[int] = Form(None),
51
+ client_total_hours: Optional[int] = Form(None),
52
+ client_total_spent: Optional[float] = Form(None),
53
+ client_rating: Optional[float] = Form(None),
54
  ):
55
  job_data = {
56
  "client_hire_rate": client_hire_rate,
requirements.txt CHANGED
@@ -5,3 +5,4 @@ python-multipart
5
  xgboost
6
  fastapi
7
  uvicorn[standard]
 
 
5
  xgboost
6
  fastapi
7
  uvicorn[standard]
8
+ numpy
templates/index.html CHANGED
@@ -1,38 +1,258 @@
1
  <!DOCTYPE html>
2
- <html>
3
  <head>
4
- <title>Hiring Prediction</title>
5
- <style>
6
- body { font-family: Arial; max-width: 600px; margin: auto; }
7
- input { width: 100%; margin-bottom: 10px; padding: 8px; }
8
- button { padding: 10px; width: 100%; }
9
- .result { margin-top: 20px; padding: 15px; border: 1px solid #ccc; }
10
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  </head>
 
12
  <body>
13
- <h2>Job Hiring Prediction</h2>
14
-
15
- <form method="post" action="/predict">
16
- <input name="client_hire_rate" placeholder="Client Hire Rate (%)" required>
17
- <input name="client_age_years" placeholder="Client Age (years)" required>
18
- <input name="client_active_hires" placeholder="Active Hires" required>
19
- <input name="client_avg_hourly_rate" placeholder="Avg Hourly Rate" required>
20
- <input name="client_total_reviews" placeholder="Total Reviews" required>
21
- <input name="client_total_hires" placeholder="Total Hires" required>
22
- <input name="client_total_hours" placeholder="Total Hours" required>
23
- <input name="client_total_spent" placeholder="Total Spent ($)" required>
24
- <input name="client_rating" placeholder="Client Rating" required>
25
-
26
- <button type="submit">Predict</button>
27
- </form>
28
-
29
- {% if result %}
30
- <div class="result">
31
- <h3>Result</h3>
32
- <p><strong>Prediction:</strong> {{ result.prediction }}</p>
33
- <p><strong>Probability:</strong> {{ result.hire_probability }}%</p>
34
- <p><strong>Confidence:</strong> {{ result.confidence }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  </div>
36
- {% endif %}
 
 
 
 
37
  </body>
38
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Upwork Hiring Predictor</title>
7
+
8
+ <style>
9
+ :root {
10
+ --bg: #0f172a;
11
+ --card: #111827;
12
+ --input: #1f2933;
13
+ --border: #374151;
14
+ --text: #e5e7eb;
15
+ --muted: #9ca3af;
16
+ --primary: #3b82f6;
17
+ --success: #22c55e;
18
+ --danger: #ef4444;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
28
+ background: linear-gradient(135deg, #020617, #020617);
29
+ color: var(--text);
30
+ min-height: 100vh;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ padding: 24px;
35
+ }
36
+
37
+ .container {
38
+ width: 100%;
39
+ max-width: 720px;
40
+ }
41
+
42
+ .card {
43
+ background: var(--card);
44
+ border: 1px solid var(--border);
45
+ border-radius: 16px;
46
+ padding: 28px;
47
+ box-shadow: 0 20px 40px rgba(0,0,0,.4);
48
+ }
49
+
50
+ h1 {
51
+ margin: 0 0 8px;
52
+ font-size: 1.6rem;
53
+ font-weight: 600;
54
+ text-align: center;
55
+ }
56
+
57
+ .subtitle {
58
+ text-align: center;
59
+ color: var(--muted);
60
+ margin-bottom: 28px;
61
+ font-size: 0.95rem;
62
+ }
63
+
64
+ form {
65
+ display: grid;
66
+ grid-template-columns: 1fr 1fr;
67
+ gap: 16px;
68
+ }
69
+
70
+ .field {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: 6px;
74
+ }
75
+
76
+ label {
77
+ font-size: 0.75rem;
78
+ color: var(--muted);
79
+ }
80
+
81
+ input {
82
+ background: var(--input);
83
+ border: 1px solid var(--border);
84
+ border-radius: 10px;
85
+ padding: 10px 12px;
86
+ color: var(--text);
87
+ font-size: 0.9rem;
88
+ outline: none;
89
+ }
90
+
91
+ input:focus {
92
+ border-color: var(--primary);
93
+ box-shadow: 0 0 0 1px var(--primary);
94
+ }
95
+
96
+ .full {
97
+ grid-column: span 2;
98
+ }
99
+
100
+ button {
101
+ margin-top: 10px;
102
+ padding: 12px;
103
+ border-radius: 12px;
104
+ border: none;
105
+ background: var(--primary);
106
+ color: white;
107
+ font-size: 1rem;
108
+ font-weight: 500;
109
+ cursor: pointer;
110
+ transition: transform .05s ease, opacity .2s ease;
111
+ }
112
+
113
+ button:hover {
114
+ opacity: 0.9;
115
+ }
116
+
117
+ button:active {
118
+ transform: scale(0.98);
119
+ }
120
+
121
+ .result {
122
+ margin-top: 28px;
123
+ padding: 20px;
124
+ border-radius: 14px;
125
+ border: 1px solid var(--border);
126
+ background: #020617;
127
+ text-align: center;
128
+ }
129
+
130
+ .badge {
131
+ display: inline-block;
132
+ padding: 6px 14px;
133
+ border-radius: 999px;
134
+ font-size: 0.75rem;
135
+ margin-bottom: 10px;
136
+ font-weight: 600;
137
+ }
138
+
139
+ .badge.hired {
140
+ background: rgba(34,197,94,.15);
141
+ color: var(--success);
142
+ }
143
+
144
+ .badge.not {
145
+ background: rgba(239,68,68,.15);
146
+ color: var(--danger);
147
+ }
148
+
149
+ .prob {
150
+ font-size: 2rem;
151
+ font-weight: 600;
152
+ margin: 8px 0;
153
+ }
154
+
155
+ .confidence {
156
+ color: var(--muted);
157
+ font-size: 0.9rem;
158
+ }
159
+
160
+ footer {
161
+ margin-top: 18px;
162
+ text-align: center;
163
+ font-size: 0.75rem;
164
+ color: var(--muted);
165
+ }
166
+
167
+ /* 📱 Mobile */
168
+ @media (max-width: 640px) {
169
+ form {
170
+ grid-template-columns: 1fr;
171
+ }
172
+ .full {
173
+ grid-column: span 1;
174
+ }
175
+ }
176
+ </style>
177
  </head>
178
+
179
  <body>
180
+ <div class="container">
181
+ <div class="card">
182
+ <h1>Upwork Hiring Predictor</h1>
183
+ <div class="subtitle">
184
+ Estimate the probability that a job will result in a hire
185
+ </div>
186
+
187
+ <form method="post" action="/predict">
188
+ <div class="field">
189
+ <label>Client Hire Rate (%)</label>
190
+ <input name="client_hire_rate" >
191
+ </div>
192
+
193
+ <div class="field">
194
+ <label>Client Age (years)</label>
195
+ <input name="client_age_years" >
196
+ </div>
197
+
198
+ <div class="field">
199
+ <label>Active Hires</label>
200
+ <input name="client_active_hires" >
201
+ </div>
202
+
203
+ <div class="field">
204
+ <label>Avg Hourly Rate ($)</label>
205
+ <input name="client_avg_hourly_rate" >
206
+ </div>
207
+
208
+ <div class="field">
209
+ <label>Total Reviews</label>
210
+ <input name="client_total_reviews" >
211
+ </div>
212
+
213
+ <div class="field">
214
+ <label>Total Hires</label>
215
+ <input name="client_total_hires" >
216
+ </div>
217
+
218
+ <div class="field">
219
+ <label>Total Hours</label>
220
+ <input name="client_total_hours" >
221
+ </div>
222
+
223
+ <div class="field">
224
+ <label>Total Spent ($)</label>
225
+ <input name="client_total_spent" >
226
+ </div>
227
+
228
+ <div class="field full">
229
+ <label>Client Rating</label>
230
+ <input name="client_rating" >
231
+ </div>
232
+
233
+ <button class="full" type="submit">
234
+ Predict Hiring Outcome
235
+ </button>
236
+ </form>
237
+
238
+ {% if result %}
239
+ <div class="result">
240
+ <div class="badge {{ 'hired' if result.prediction == 'HIRED' else 'not' }}">
241
+ {{ result.prediction }}
242
+ </div>
243
+ <div class="prob">
244
+ {{ result.hire_probability }}%
245
+ </div>
246
+ <div class="confidence">
247
+ Confidence: {{ result.confidence }}
248
+ </div>
249
+ </div>
250
+ {% endif %}
251
  </div>
252
+
253
+ <footer>
254
+ Built with FastAPI · Model-based prediction
255
+ </footer>
256
+ </div>
257
  </body>
258
  </html>