Skip to content

Latest commit

 

History

History
89 lines (72 loc) · 4.23 KB

File metadata and controls

89 lines (72 loc) · 4.23 KB

Signal — internal-comms personalization prototype

A working prototype for the ADM AI Challenge: one pool of internal-comms posts, routed and re-ranked per person by a model that learns from what they click. The point isn't a prettier intranet — it's AI as a personalization + routing layer on top of comms you already send, with its reasoning shown on screen so the room can argue about it.

What's here

backend/    FastAPI service + SQLite event log + the model
frontend/   single-file React app wired to the backend

Run it (two terminals)

1. Backend

cd backend
pip install -r requirements.txt
uvicorn main:app --reload --port 8000

2. Frontend — just open frontend/index.html in a browser. (If your browser blocks file:// requests, serve it: python -m http.server 5500 from the frontend/ folder, then visit http://localhost:5500.)

The page talks to the API at http://localhost:8000. Change API at the top of the <script> in index.html if you run the backend elsewhere.

The demo path (what to click in front of the ADMs)

  1. Switch personas (top-right). The same 15 posts re-rank instantly — a program analyst sees grants and governance up top; the data/AI analyst sees the AI pilots. That's cold-start from a role prior, no clicks yet.
  2. Interact. Read a couple of cards, hit an action button, thumbs-down something irrelevant, dismiss the cafeteria post. Watch the "What the model knows" panel fill in and the For you feed reorder.
  3. Point at the override. The network outage and mandatory training stay pinned in Pushed to you the whole time — the model never gets to hide those.
  4. Retrain. After a dozen interactions, hit Retrain on the event log to flip from the live content model to a trained logistic model.

How it works (the two models)

The most important design decision is not which model — it's the value score in recommender.py. A raw click is a bad training target because it rewards clickbait. Instead every event collapses to one graded label: an impression scrolled past is a weak negative, an open is mild, a full read is strong, a completed action is stronger, an explicit "useful" is the ceiling, a dismiss is negative. Both models learn on that.

  • Tier A — content-based profile (default, always on). Each item is a TF-IDF vector over its category + tags + text. A user's profile is a time-decayed, value-weighted sum of the items they've engaged with (Rocchio relevance feedback). Score = cosine similarity. Updates instantly per click, explainable (we can name the tags that drove each card), handles cold start via role priors.
  • Tier B — trained engagement model (opt-in). A logistic regression over item features + role, trained on the whole event log to predict P(engage). The "real trained model" story. Needs data: with only a handful of events it underfits — which is itself the honest lesson about when each tier wins.

Endpoints

method path purpose
GET /personas role options (cold-start priors)
POST /user set a user's role
GET /feed?user_id=&model= ranked feed, split into push / recommended / discovery
POST /event log an interaction (the model's training data)
GET /profile/{user_id} the learned interest profile
POST /retrain train the Tier-B model on the event log
POST /reset/{user_id} clear a user's history

The four things built in on purpose (your talking points)

  • Mandatory override. Personalization governs the discretionary feed only. Urgent and mandatory items bypass the model entirely and always reach the user.
  • Exploration. A Discover slot injects an off-profile item so the feed can't collapse into a filter bubble — a real governance risk for internal comms.
  • No clickbait. The value score deliberately rewards reading and acting over raw clicks.
  • Transparency. The right-hand panel shows the model's current interest profile and every card shows why it surfaced. This is what makes an AI-decides-what-you-see system defensible — and it's the conversation the challenge is meant to spark.

All content is illustrative and HC/PHAC-flavoured. None of it is real.