Insight Player — plain-language explainer
Insight Player is the one shape every People Analytics finding takes on its way to a human, and the one engine that decides which finding that human sees next.
A People Analytics Toolbox cross-cutting primitive (PAT-34) — it lives in src/lib/, not in any one spoke, because every spoke is a candidate emitter and every consumer app is a candidate renderer. Built to the portfolio Explainer Standard v1.0. Every claim below is grounded in the real code (src/lib/insight-player/, src/lib/player/, src/lib/signals/, surface src/surfaces/analytics-player/) and the contract versions in the registry (insight-player.contract 0.1.0, player engine 0.1.0, signals 0.1.0); anything not yet built is marked (TBD).
This is a flagship surface — a thing a buyer and a developer both meet directly — so it earns the fuller, multi-movement treatment rather than a single eight-question page. The eight-question spine (what · why-different · how · enables · fits · worked example · packaging · status · vision) is woven through the movements below.
Lead — the job it does
Give a person the right finding, in a shape they can read in seconds, in an order that learns from how they react — and let any app in the portfolio do that with the same contract and the same ranking math.
Two audiences meet it. A buyer / executive meets the rendered Insight Player — a paced, gestural card feed where one analytical finding fills the screen at a time, swipeable, with a tap-to-reveal justification and a one-tap reaction. A developer meets the primitive — a small pair of vendored files (contract.ts + sequence.ts) plus a converged player engine, that lets their app emit findings as Insight Cards and sequence them the same way every other portfolio app does.
Manifesto — why it exists
The pain it removes is the dashboard. A dashboard hands a person every number at once and makes them do the triage: scan twenty tiles, guess which movement matters, remember it next week. Most People Analytics tooling stops there — it computes well and presents badly, so the analysis that cost real money to produce dies on a tab nobody opens.
The worldview behind Insight Player is the opposite: one finding at a time, sequenced, and the sequence learns. A finding is an atom (the Insight Card). The order those atoms arrive in is a decision (the sequencing engine). And how a person reacts to each one — rate it, dwell on it, expand it, skip it, save it — is a signal that should make the next sequence better (the feedback loop). That is the same loop a recommender runs on a content feed, applied to analytical findings instead of videos.
The differentiation beat, stated as a shift:
- FROM a dashboard of twenty tiles where the human does the triage, the same layout for everyone, and no memory of what they cared about last time.
- TO a sequenced feed of single findings, ranked by relevance + freshness + diversity, that records how each person reacts and re-ranks the next session toward what works for them — while a deliberate exploration budget keeps surfacing the unfamiliar so the feed never collapses onto one narrow cluster.
How it differs from the obvious substitutes:
- vs. generic BI (Tableau / Looker / Power BI): those render charts you assemble and navigate. Insight Player renders findings — a card carries a headline claim, the context, the visual, the justification on reveal, and a request for one rating — and it orders them. BI has no notion of "show this person the most relevant unseen finding next."
- vs. each app rolling its own feed: if Performix, the toolbox's own
/feed, and rankandfile each invented their own card shape and ranking, two apps would show different cards in a different order from the same source data. The contract + the one canonical scoring formula exist precisely so they can't drift. The toolbox owns the math; consumers vendor a copy. - vs. doing it by hand: a human curating "the three things you should look at this week" doesn't scale across spokes, doesn't learn, and can't run a reproducible, seedable ranking that two surfaces will agree on.
Visual — Tier B (FROM→TO typographic block). The shift above is the visual. dashboard of 20 tiles · human triages · no memory → sequenced feed of single findings · engine ranks · loop learns per person.
Walkthrough — how it actually works
There are two journeys through this primitive: the developer's path through the contract + engine, and the person's path through the rendered feed. They meet at the same atoms.
The atom — an Insight Card
Every finding, from every spoke, takes one shape (src/lib/insight-player/contract.ts, InsightCardSchema). The load-bearing fields:
id,source_app— globally unique id and the emitting spoke (e.g."calculus","anycomp").audience_tags— who should see it (the renderer's visibility filter).topic_tags— open strings (["engagement", "survey", "trend"]) that drive focus-match and diversity scoring.freshness—{ computedAt, ttlSeconds? }; past TTL, recency collapses to zero.card_type— a spoke-coined string ("calculus.metric-snapshot") used for redundancy bookkeeping, not a closed taxonomy.headline+body— the one-line claim and its context.visual—{ kind, spec }wherekindis one oftext · metric · trend · chart · comparison · calloutandspecis renderer-specific.reveal?—{ summary, detail? }, surfaced on tap/long-press (the justification).signals_requested?— what rating the card wants back ({ signalId, prompt }).
A spoke coins its own card_type and topic_tags; the contract doesn't enumerate them. That openness is deliberate — it lets new spokes emit without a contract bump.
The engine — buildPlayerQueue
The ordering decision is a single pure function (src/lib/insight-player/sequence.ts, buildPlayerQueue) — no database, no async, no imports beyond the contract, so it is trivially testable and two consumers running it on the same input get the same queue. The canonical scoring formula (weights named in WEIGHTS):
relevance = 0.40 · focusMatch + 0.30 · freshness— focus-match is the fraction of a card'stopic_tagsthat intersect the chosen focus's topic set (a small per-focus map:engagement,performance,compensation,decision-support,default); freshness decays linearly over 7 days, clamped to 0 pastttlSeconds.- per-pick:
+ 0.20 · diversityBonus − 0.15 · redundancyPenalty— diversity is1 − max(Jaccard overlap)against the already-selected queue; redundancy counts prior cards of the samecard_type, soft-capped so a heavy-emitting spoke can't get blacklisted. - exploration budget:
exploratoryRatio(default 0.25) of the slots are drawn — via a seedable xorshift32 PRNG — from the lower half of the ranking. This is lifted verbatim from Vela's reincarnation "25% exploratory" rule; without it the feed collapses onto a narrow high-confidence cluster and the corpus never gets exposed.
The learning — affinity from signals
buildPlayerQueue takes an optional non-wire hook: opts.affinity(card) → [−1, 1], added to base relevance via WEIGHTS.affinity = 0.5. That number comes from the actor's own history. src/lib/signals/personalize.ts reads an actor's prior signals, builds an affinity profile over topics / sources / card-types (buildAffinityProfile), and scores a new card by feature overlap (affinityScore) — boosting what has worked for this person and damping what hasn't. The exploration budget runs alongside so the feed still surfaces the unknown.
The person's journey through the feed
The rendered surface is analytics-player at the public route /feed (src/app/(surfaces)/feed/page.tsx). On render it:
- resolves an actor id (a cookie); anonymous → no affinity → unpersonalized ranking;
- if the actor has history, loads up to 500 prior signals and builds
affinity(getSignalStore().historyFor(actorId, { limit: 500 })→buildAffinity(history)); - fetches cards by calling every live spoke's
<spoke>.emit-insight-cardstool through the in-process MCP gateway (src/surfaces/analytics-player/lib/fetch-cards.ts) — never importing a spoke's internals, exactly like an external consumer; - runs the concatenated deck through
buildPlayerQueuewith the actor'saffinity; - renders one card at a time. Swipe / drag / arrow-keys advance; tap-to-reveal expands
card.reveal; a one-tap reaction posts aratingsignal back through a server action (recordSignalAction/recordPlayerSignalsAction), which persists to Postgres (PostgresSignalStorewhenDATABASE_URLis set, in-memory otherwise).
Optional ?focus=engagement|performance|compensation|decision-support|default reranks the deck. When zero live emitters return cards, the surface falls back to a hand-authored SEED_DECK (flagged isSeed: true) so the page never renders empty.
Visual — Tier B (step flow). spoke emits Insight Card → fetch-cards collects via MCP → buildPlayerQueue ranks (relevance + freshness + diversity − redundancy + learned affinity, 25% exploratory) → /feed renders one card → person reacts → Signal persists → next session's affinity shifts. A rendered capture of the live /feed surface is a follow-up (FU-A — needs the design-system polish pass, currently deferred per the surface README).
What it enables
Concrete uses a practitioner or a builder would recognize:
- An executive "what should I look at this week" feed — the toolbox
/feedsequences findings across every live analytics spoke into one paced, swipeable surface, with a justification one tap away. - A shared Insight Player across apps — Performix is the reference renderer; it and the toolbox feed share the same card shape and ranking, so the same source data yields the same cards in the same order. (Performix is the vendored authority for the light/exec renderer —
src/lib/insight-player/renderer/is vendored from it.) - A learning content product (rankandfile.com) — the north star: generate role-targeted content as cards, learn from each person's reactions, surface more of what works. The Library view (
components/Library.tsx) browses the full card set with filters (source · type · topic · freshness) while the player sequences a slice — the explore-vs-play split rankandfile needs. - A per-card rating loop — each card can request one rating (
signals_requested); the reaction becomes a portfolioSignalthat re-ranks future sessions. - A post-session reflection — after a sequence completes,
src/lib/signals/sequence-reflection.tsaggregates the session's signals into "what the loop learned" (rated / resonated / passed counts, dwell, top topics + sources) plus two short LLM-authored lines (Haiku viagetModel, with deterministic English fallback). Read atGET /api/player/reflection. - A converged engine for cards and immersive media — the generalized player engine (
src/lib/player/) sequences a content-agnosticPlayerUnitwhoserenderFamilyiscard | media | text; an Insight Card maps in 1:1 (fromInsightCard), and Vela's immersive media items are the same atom under amediarenderer.
Visual — Tier B (typographic). Consumers that meet it today: toolbox /feed (renderer) · Performix /insights (reference renderer + renderer authority) · rankandfile (planned product) · vela (media-family sibling) — all over the one contract.
How it fits in the toolbox
It is a cross-cutting primitive in src/lib/, not a spoke — it owns no Postgres schema and emits no MetricEnvelope; it is the carrier and sequencer of findings other spokes produce.
What it composes (HTTP + contracts only, never another spoke's internals):
- Emitters (the producers). Every analytics spoke is a candidate emitter via a
<spoke>.emit-insight-cardsMCP tool; the surface fetcher calls them through the in-process MCP gateway. Today the fetcher callsreincarnation,data-anonymizer,segmentation-studiounconditionally andpreference-modeler/anycomp/forecastingonly when asurveyId/modelId/decisionModelIdis supplied (calculusneeds pre-computed envelopes, so it's skipped from the URL alone). The breadth of which spokes have a live emitter tool is the active edge — see status. - The signals loop (
src/lib/signals/). The contract (SignalSchema, kinds: explicitrating · thumb · save · dismiss · act · reviewed, implicitview · dwell · expand · complete · share · skip), the durable store (PostgresSignalStoreon thesignalsschema, in-memory fallback), the personalizer (buildAffinity), and the cross-property ingest front door (POST /api/signals/ingest, service-key gated). Signals are also vendored cross-property: any property POSTsSignal[]asserting a stableactorId, so the same human shares affinity across apps. - The converged player engine (
src/lib/player/). Contract0.1.0(PlayerUnit/PlayerSequence, render familiescard | media | text) + the pure session state machine (session.ts— advance / retreat / rate / reveal, each emitting the rightSignal). One engine, two divergent presentation wrappers (card feed vs. immersive media chrome). - The renderer (
src/lib/insight-player/renderer/). The light/executive card renderer, vendored verbatim from Performix (the renderer authority) —InsightCardRenderer+ thePAPER_INK/PERFORMIX_CAMSthemes + the toolbox-localfromInsightCardmapper. (Per portfolio convention, the exec Insight Player renders light, never dark.)
Contracts in play and how to consume them:
- In-process — toolbox surfaces import
@/lib/insight-player/contract+sequencedirectly. - Vendored — Performix / vela / external apps copy
contract.ts+sequence.ts(andsignals/contract.ts) with a// vendored from … @ <sha>header. - Discovery — AI agents call the registered MCP tool
toolbox.insight-player.contract(src/lib/mcp/discovery-tools.ts) to introspect version + schema names + enum sets without parsing source.
Bump-via-toolbox is the only path to evolve the contract — a consumer that wants a new focus or visual kind files against the toolbox; it never patches its vendored copy. That is what keeps two renderers aligned.
Visual — Tier B (typographic data-flow). { spokes emit cards } → insight-player contract + sequence → { /feed · Performix · rankandfile · vela } → signals contract → store + personalizer → back into sequence. The loop closes.
One load-bearing worked example
A real, end-to-end pass through the engine, grounded in the in-repo SEED_DECK (src/surfaces/analytics-player/lib/fetch-cards.ts) and the canonical scoring formula. This is the deck the surface serves when no live emitter responds — real cards, real ranking, no invented numbers.
The seed deck (five cards, abbreviated to the load-bearing fields):
seed-eng-pulse-2026q1—preference-modeler, topicsengagement · sentiment · survey, "Engagement up 4 pts QoQ (Engineering)".seed-comp-band-l4-2026q1—anycomp, topicscompensation · band · labor-market, "L4 software-eng band centered at $172k".seed-perf-rating-distribution-2025h2—calculus, topicsperformance · rating · calibration, "Rating distribution skews high (Sales)".seed-decision-attrition-voi-2026q1—forecasting, topicsdecision · voi · attrition-risk, "Attrition-prediction model: EVPI = $84k".seed-text-anonymizer-coverage—data-anonymizer, topicsprivacy, "PII coverage at 98.7% on Q1 import".
Pass 1 — focus=engagement, no actor history. focusMatch rewards cards whose topic_tags intersect the engagement set (engagement · sentiment · survey · …). The pulse card matches on all three of its topics (focus-match ≈ 1.0); the comp / performance / decision cards match none (focus-match 0); all five carry near-max freshness (computedAt is now). So the engineering pulse card ranks first by the relevance term alone, then diversity + the 25% exploration budget mix in an off-topic card from the lower half so the session isn't single-note. The result is deterministic for a given seed (the surface pins seed: 1).
Pass 2 — same focus, with an actor who has down-rated compensation cards before. The actor's history yields an affinity profile where the compensation / band / labor-market topics and the anycomp source score negative. buildAffinity(history) returns a function that scores seed-comp-band-l4-2026q1 toward −1; via WEIGHTS.affinity = 0.5 that subtracts up to ~0.5 from its base relevance, pushing it down the queue — while the engagement card (positive or neutral affinity) holds the top. The same source data now ranks differently for this person — which is the whole point.
What the person does with it: the engineering-pulse card fills the screen, tap-to-reveal shows the justification (reveal.summary: "n = 142 responses; anonymity threshold met (min N = 12)"), they give it a one-tap rating, that rating Signal persists, and Pass-2-style affinity nudges the next session.
(All values above are the literal contents of the in-repo seed deck and the named weights in sequence.ts. The two-pass ranking is the documented behavior of buildPlayerQueue + buildAffinity, not a measured benchmark — the relative ordering is what the formula guarantees; exact composite scores depend on the live deck and are not asserted here.)
Visual — Tier B (the example IS the visual). A rendered before/after queue-order capture from the live /feed is (TBD — FU-A capture once the design polish pass lands).
Commercialization / packaging
Insight Player is design-system spine, not a line item — it's the surface a buyer meets everywhere an executive output renders, across the portfolio, rather than a product sold on its own. Its commercial role is to make every analytics spoke's output legible and to be the shared rendering + ranking layer the portfolio's apps (and rankandfile, the standalone content product) are built on.
- Vendor posture: the toolbox is canonical; consumers vendor
contract.ts+sequence.ts(+signals/contract.ts) and bump only via the toolbox. That's the licensing-internal boundary that keeps Performix, the toolbox feed, and future apps aligned. - Data-license posture: the primitive itself carries no third-party data — it carries whatever cards the emitting spokes produce, and each card's data-license constraints travel with its source spoke (e.g. a wage card's BLS provenance, a survey card's tenant data).
- Product-tier placement, pricing, and any packaged "Insight Player" offering are (TBD) — not earned yet, so not stated.
Visual — (TBD — product-family placement diagram showing Insight Player as the shared render/rank layer beneath the consuming apps).
Current status
Grounded in the real code state (contracts: insight-player.contract 0.1.0, player engine 0.1.0, signals 0.1.0):
Shipped:
- The canonical Insight Card contract + the pure sequencing core (
buildPlayerQueue) with the full relevance + freshness + diversity + redundancy + exploration-budget formula (PAT-34). - The personalization brain (
buildAffinityProfile/affinityScore/buildAffinity) and its wiring into/feed— the rating→sequencing loop is closed:/feedresolves the actor, loads up to 500 prior signals, builds affinity, and feeds it to the ranker. - The portfolio Signals contract + durable Postgres store + cross-property ingest (
POST /api/signals/ingest) + the in-app server-action capture path. - The
/feedsurface (swipe / drag / arrow-key advance, tap-to-reveal, one-tap rating, focus query param, seed-deck fallback) and the Library browse/filter view. - The vendored Performix exec renderer in
src/lib/insight-player/renderer/, with a design page at/design/insight-player. - The converged player engine (
src/lib/player/— content-agnostic units,card|media|textrender families, pure session state machine, signal bridge) and the post-session reflection (sequence-reflection.ts,GET /api/player/reflection). - The MCP discovery tool
toolbox.insight-player.contract.
In flight / planned:
- Breadth of live
<spoke>.emit-insight-cardsemitters — the fetcher is wired for several spokes, but full per-spoke emitter coverage (every analytics spoke producing real, non-seed cards) is the active edge(PAT-36 family). - Visual-kind renderers beyond
metric/text—trend/chart/comparison/calloutcurrently passthrough on the surface(TBD). - Routing a card's signal back to its source spoke for the spoke's own learning (today signals feed only the cross-card affinity loop)
(TBD). - Cross-property identity linking (merging a pre-login anonymous actor key into a post-login identity)
(TBD). - The immersive media wrapper (Ken Burns / audio / gestures) over the converged engine — the
mediarender family exists in contract; the chrome is a later step(TBD). - Claude Design polish pass on
/feedand the Tier-A rendered captures that depend on it(TBD — FU-A).
Visual — Tier B (typographic shipped/edge split). Shipped: contract · sequencer · affinity loop (closed) · signals store · /feed · Library · exec renderer · converged engine · reflection · MCP discovery. Edge: emitter breadth · non-metric visual kinds · signal→spoke routing · cross-property identity · media wrapper · design polish.
The vision
One contract for every finding the portfolio produces, one engine that learns which finding each person wants next, and one loop — fed by every reaction, not just a rating — that makes the content better for that person over time, across every property.
The arc is the universal content feedback loop: many signal kinds (rating is one of twelve — thumbs, save, dismiss, act, view, dwell, expand, complete, share, skip, reviewed) flow into a toolbox-owned loop that re-ranks content per user, content-agnostic and cross-property. rankandfile.com is the first standalone product built on it; Performix and vela are the first cross-portfolio renderers. The two divergent wrappers — the lean-forward analytics card feed and the lean-back immersive media player — converge on this single engine, so a finding and a photograph are the same atom under different chrome, both rated, both feeding the same loop.
The load-bearing next move is breadth and routing: more spokes emitting real cards, and each reaction routed back to the spoke that produced the card so the producer learns too — closing the loop not just for the reader's feed, but for the analysis itself.