Inspiration

What it does

How we built it

Challenges we ran into## Inspiration

Following a team means watching two games at once: the one on the field, and the one in the conversation around it. The second is where fans actually live — the arguments, the turns, the moment a player stops being a problem and starts being the reason you won. That signal is real, it moves fast, and nothing shows it to you. Box scores tell you what happened. They don't tell you how it landed.

What it does

Fanalyze Pulse turns live fan conversation and AI analysis into a simple view of what is trending across the NFL.

  • Live fan sentiment — track how sentiment around your team or player is moving, and how fast
  • Pulse Spikes — the app detects meaningful shifts: sentiment swings, a player beginning to trend, conversation volume accelerating, a narrative emerging
  • Trending players — who is generating attention right now, and why
  • AI-powered insights — not just that sentiment changed, but the narratives driving it
  • Real-time alerts — delivered the moment something real is detected, plus pregame, halftime and postgame pulses timed to the actual schedule
  • Share the Pulse — turn a moment into a shareable graphic

How we built it

A Python/FastAPI backend on Railway ingests public NFL conversation from the X API, scores sentiment per post, and aggregates it into a Fan Pulse score per entity. Around 2,700 teams and players are recognised; ~110 are actively polled, chosen by a game-aware priority pass and an attention layer that spends reads where people are actually looking.

An anomaly detector watches each entity's score for statistically meaningful moves — 1.5 standard deviations against that entity's own baseline, with at least five historical points required before anything is called unusual. A loud team and a quiet one get different thresholds automatically. When one fires it becomes a Pulse Spike: a push through OneSignal, targeted by device tags so a fan hears about their team and nobody else's, with copy differentiated across free, trial, Pro and lapsed tiers from one real event.

Game context comes from Sportradar, so pregame, halftime and postgame pulses land against real kickoffs rather than fixed hours.

The LLM explains, it never decides. OpenAI's gpt-4o powers the Ask agent, gpt-4o-mini writes insight captions, text-embedding-3-small clusters posts into narratives. Every model and X API call routes through an internal CostGuard metering spend against hard daily caps. Sentiment comes from the data; the model is only ever asked to describe what the pipeline already found.

The iOS app is React Native and Expo. RevenueCat handles subscriptions and the fan_pulse_pro entitlement across monthly, yearly, lifetime and a $1.99 Game Day Pass — verified server-side, not trusted from the client.

Challenges we ran into

A consumable attached to an entitlement grants it forever. Attaching the Game Day Pass to fan_pulse_pro is exactly what the dashboard invites you to do, and RevenueCat grants a consumable's entitlement with no expiry — because for a consumable there isn't one to infer. Observed live in sandbox: a $1.99 day pass granting permanent Pro. The fix isn't to attach it more carefully. It's to not attach it, and treat the purchase timestamp plus 24 hours as the grant, computed independently on client and server.

A perfect entitlement and an empty paywall. Products attach to entitlements and to packages in an offering, and the dashboard makes them look like one job. We had the first and not the second, so getOfferings() returned zero packages behind a flawless-looking configuration, with no error anywhere.

A hardcoded price is right in one country and wrong in 174. The Game Day Pass button read "$1.99" as literal text while every other price correctly read priceString from StoreKit. An Australian user would have seen a 50% understatement in the wrong currency. Found while writing documentation, not by a test.

Cost as an architectural constraint. Fan conversation is unbounded and reads cost money per post. That shaped the design: hard daily spend caps, a CostGuard every external call passes through, and attention-driven polling so a limited budget lands on entities someone is actually looking at.

Accomplishments that we're proud of

A pipeline that runs continuously and honestly. Sentiment, anomaly detection, narrative clustering, game-phase awareness and tier-targeted push all run end-to-end on live data, with the LLM confined to explanation and every paid call metered. Four products doing four different jobs behind one entitlement, and 902 automated tests across 78 files.

What we learned

The credibility of an analytics product is decided by what you refuse to claim. Early marketing copy described features the app didn't have. Each was checked against the actual models and cut. The product we describe is smaller than the first draft — and true.

A RevenueCat key silently scopes every answer to one app. A leftover Test Store key returned zero packages for all three offerings and produced hours of confident, wrong diagnosis that the paywall was broken. It wasn't. The key was.

RevenueCat being configured correctly doesn't mean the store will serve it. Two products sat in App Store Connect's "Missing Metadata" while RevenueCat reported them present — StoreKit drops those client-side, and the paywall would have rendered with no subscriptions at all.

What's next for Fanalyze Pulse

Android, which the codebase already builds for. Then a second sport — the platform is sport-agnostic apart from the entity dictionary and the sports feed, and the NFL season is four months against a twelve-month cost base. Basketball is the natural next step: smaller rosters, and its season opens as the NFL's closes.

Built With

Share this project:

Updates

Submission history