-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
168 lines (133 loc) · 4.84 KB
/
Copy pathmain.py
File metadata and controls
168 lines (133 loc) · 4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
FastAPI service for the internal-comms personalization prototype.
Endpoints
GET /personas -> available role personas (for onboarding/cold start)
POST /user -> create/set a user's role
GET /feed?user_id=&model= -> personalized feed, split into routing lanes
POST /event -> log an interaction (this is what the model learns from)
GET /profile/{user_id} -> the learned interest profile (transparency panel)
POST /retrain -> train the Tier-B logistic model on the event log
POST /reset/{user_id} -> clear a user's history (for clean demos)
Run: uvicorn main:app --reload --port 8000
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Optional
import os
import store
from recommender import Recommender
from seed_data import PERSONAS
app = FastAPI(title="Internal Comms Personalization Prototype")
# The React frontend runs from a file/dev server on a different origin.
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
reco = Recommender()
@app.get("/", include_in_schema=False)
def index():
return FileResponse(os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html"))
@app.get("/analytics", include_in_schema=False)
def analytics_page():
return FileResponse(os.path.join(os.path.dirname(os.path.abspath(__file__)), "analytics.html"))
@app.get("/analytics/data")
def analytics_data():
data = store.get_analytics_data()
data["items"] = [
{
"id": it["id"],
"title": it["title"],
"category": it["category"],
"urgency": it["urgency"],
"mandatory": it["mandatory"],
}
for it in reco.items
]
data["personas"] = [{"id": k, "label": v["label"]} for k, v in PERSONAS.items()]
return data
@app.on_event("startup")
def _startup():
store.init_db()
# ---- request models ----
class UserIn(BaseModel):
user_id: str
role: str
class EventIn(BaseModel):
user_id: str
item_id: str
event_type: str
dwell_ms: Optional[int] = None
feed_position: Optional[int] = None
urgency_tier: Optional[str] = None
# ---- endpoints ----
@app.get("/personas")
def personas():
return [{"id": k, "label": v["label"]} for k, v in PERSONAS.items()]
@app.post("/user")
def set_user(u: UserIn):
if u.role not in PERSONAS:
raise HTTPException(400, f"unknown role '{u.role}'")
store.upsert_user(u.user_id, u.role)
return {"ok": True, "user_id": u.user_id, "role": u.role}
@app.get("/feed")
def feed(user_id: str, model: str = "content"):
user = store.get_user(user_id)
if not user:
raise HTTPException(404, "unknown user -- POST /user first")
events = store.get_events(user_id)
result = reco.rank(user["role"], events, model=model)
def shape(s, lane):
it = s["item"]
return {
"id": it["id"],
"title": it["title"],
"body": it["body"],
"category": it["category"],
"urgency": it["urgency"],
"mandatory": it["mandatory"],
"action": it["action"],
"score": s["score"],
"reasons": s["reasons"],
"lane": lane,
}
return {
"model": model if (model == "content" or reco.trained_model) else "content",
"push": [shape(s, "push") for s in result["push"]],
"recommended": [shape(s, "recommended") for s in result["recommended"]],
"discovery": shape(result["discovery"], "discovery") if result["discovery"] else None,
"profile_terms": result["profile_terms"],
"event_count": len(events),
}
@app.post("/event")
def event(e: EventIn):
if not store.get_user(e.user_id):
raise HTTPException(404, "unknown user -- POST /user first")
store.log_event(e.user_id, e.item_id, e.event_type,
dwell_ms=e.dwell_ms, feed_position=e.feed_position,
urgency_tier=e.urgency_tier)
return {"ok": True}
@app.get("/profile/{user_id}")
def profile(user_id: str):
user = store.get_user(user_id)
if not user:
raise HTTPException(404, "unknown user")
events = store.get_events(user_id)
prof = reco.build_profile(user["role"], events)
return {
"role": user["role"],
"event_count": len(events),
"terms": reco._top_profile_terms(prof, top_k=10),
}
@app.post("/retrain")
def retrain():
stats = reco.retrain(store.get_all_events(),
get_role=lambda uid: (store.get_user(uid) or {}).get("role"))
return stats
@app.post("/reset/{user_id}")
def reset(user_id: str):
store.reset_user(user_id)
return {"ok": True, "user_id": user_id}