shravanda commited on
Commit
a543cdf
·
verified ·
1 Parent(s): 1e34aa6

Deploy ExtraaLearn Flask backend

Browse files
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Lightweight Python base image
2
+ FROM python:3.12-slim
3
+
4
+ # Hugging Face Docker spaces run the container as a non-root user with UID 1000
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+ WORKDIR /home/user/app
9
+
10
+ # Install dependencies first so Docker can cache this layer
11
+ COPY --chown=user requirements.txt .
12
+ RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy the API code and the serialized model
15
+ COPY --chown=user . .
16
+
17
+ # Serve the Flask app with gunicorn on port 7860 (the port Hugging Face exposes)
18
+ EXPOSE 7860
19
+ CMD ["gunicorn", "--workers", "2", "--bind", "0.0.0.0:7860", "--timeout", "120", "app:app"]
README.md CHANGED
@@ -1,10 +1,15 @@
1
  ---
2
- title: ExtraaLearnLeadBackend
3
- emoji: 📚
4
- colorFrom: pink
5
- colorTo: pink
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: ExtraaLearn Lead Conversion Backend
3
+ emoji: 🎓
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ Flask REST API serving the ExtraaLearn lead-conversion model.
12
+
13
+ * `GET /` - health check
14
+ * `POST /v1/lead` - JSON body with one lead's features, returns prediction + conversion probability
15
+ * `POST /v1/leadbatch` - multipart CSV upload (field `file`), returns one prediction per lead
__pycache__/app.cpython-312.pyc ADDED
Binary file (5.77 kB). View file
 
app.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import pandas as pd
3
+ from flask import Flask, jsonify, request
4
+
5
+ # Initialise the Flask app and load the serialized pipeline (preprocessing + model) once at startup
6
+ app = Flask("ExtraaLearn Lead Conversion Predictor")
7
+ model = joblib.load("extraalearn_lead_model_v1_0.joblib")
8
+
9
+ # Raw input features the pipeline expects, with their allowed values where categorical
10
+ FEATURES = ["age", "current_occupation", "first_interaction", "profile_completed", "website_visits",
11
+ "time_spent_on_website", "page_views_per_visit", "last_activity", "print_media_type1",
12
+ "print_media_type2", "digital_media", "educational_channels", "referral"]
13
+ NUMERIC = {"age": int, "website_visits": int, "time_spent_on_website": int, "page_views_per_visit": float}
14
+ CATEGORIES = {
15
+ "current_occupation": ["Professional", "Unemployed", "Student"],
16
+ "first_interaction": ["Website", "Mobile App"],
17
+ "profile_completed": ["Low", "Medium", "High"],
18
+ "last_activity": ["Email Activity", "Phone Activity", "Website Activity"],
19
+ **{flag: ["Yes", "No"] for flag in ["print_media_type1", "print_media_type2", "digital_media",
20
+ "educational_channels", "referral"]},
21
+ }
22
+
23
+
24
+ def validate(record):
25
+ """Check that a lead record has every feature with a valid value; return a list of errors."""
26
+ errors = []
27
+ for f in FEATURES:
28
+ if f not in record:
29
+ errors.append(f"missing field '{f}'")
30
+ elif f in NUMERIC:
31
+ try:
32
+ if NUMERIC[f](record[f]) < 0:
33
+ errors.append(f"'{f}' must be non-negative")
34
+ except (TypeError, ValueError):
35
+ errors.append(f"'{f}' must be numeric")
36
+ elif record[f] not in CATEGORIES[f]:
37
+ errors.append(f"'{f}' must be one of {CATEGORIES[f]}")
38
+ return errors
39
+
40
+
41
+ def to_frame(records):
42
+ """Build a model-ready DataFrame with the right column order and numeric types."""
43
+ df = pd.DataFrame(records)[FEATURES]
44
+ for f, cast in NUMERIC.items():
45
+ df[f] = df[f].astype(cast)
46
+ return df
47
+
48
+
49
+ @app.get("/")
50
+ def home():
51
+ """Health check."""
52
+ return "Welcome to the ExtraaLearn Lead Conversion Prediction API!"
53
+
54
+
55
+ @app.post("/v1/lead")
56
+ def predict_lead():
57
+ """Predict the conversion of a single lead sent as a JSON object."""
58
+ record = request.get_json(silent=True)
59
+ if not isinstance(record, dict):
60
+ return jsonify({"error": "Request body must be a JSON object with the lead's features"}), 400
61
+ errors = validate(record)
62
+ if errors:
63
+ return jsonify({"error": "Invalid input", "details": errors}), 400
64
+
65
+ df = to_frame([record])
66
+ probability = float(model.predict_proba(df)[0, 1])
67
+ prediction = int(model.predict(df)[0])
68
+ return jsonify({
69
+ "prediction": prediction,
70
+ "prediction_label": "Likely to convert" if prediction == 1 else "Unlikely to convert",
71
+ "conversion_probability": round(probability, 4),
72
+ })
73
+
74
+
75
+ @app.post("/v1/leadbatch")
76
+ def predict_lead_batch():
77
+ """Predict the conversion of every lead in an uploaded CSV file (form field 'file')."""
78
+ if "file" not in request.files:
79
+ return jsonify({"error": "Upload a CSV file in the form field 'file'"}), 400
80
+ batch = pd.read_csv(request.files["file"])
81
+ missing = [f for f in FEATURES if f not in batch.columns]
82
+ if missing:
83
+ return jsonify({"error": f"CSV is missing columns: {missing}"}), 400
84
+
85
+ df = to_frame(batch)
86
+ ids = batch["ID"].astype(str).tolist() if "ID" in batch.columns else [str(i) for i in range(len(batch))]
87
+ results = [
88
+ {"ID": i, "prediction": int(p), "conversion_probability": round(float(pr), 4)}
89
+ for i, p, pr in zip(ids, model.predict(df), model.predict_proba(df)[:, 1])
90
+ ]
91
+ return jsonify(results)
92
+
93
+
94
+ if __name__ == "__main__":
95
+ app.run(host="0.0.0.0", port=7860, debug=False)
extraalearn_lead_model_v1_0.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:928508ec696a1ca14e3204387b3283f134ab8d7f99433c33d256d95100640111
3
+ size 132290
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ pandas==3.0.6
2
+ numpy==2.5.3
3
+ scikit-learn==1.9.1
4
+ xgboost-cpu==3.4.1
5
+ joblib==1.6.0
6
+ flask==3.1.3
7
+ gunicorn==26.2.0