An analytics dashboard for WhatsApp chat exports.
WhatsApp Analytics parses an uploaded WhatsApp .txt export message by message and returns a single stats payload: KPIs, activity series, timing distributions, content rankings, per-participant metrics, and insight cards. Everything runs on your machine — there is no database, and the export lives in process memory only. An optional AI assistant answers questions about the result, and it sees bounded aggregate statistics rather than raw message text.
Built with Flask, a pure-Python statistics engine, vanilla browser UI assets, Chart.js, and either the Groq API (Cloud) or a loopback LM Studio / Ollama model (Local).
- Browser Upload Flow: Drop in a WhatsApp
.txtexport and get the whole dashboard back in one request. - KPI Row: Total messages, active days, messages per active day, and peak hour, each with a monthly trend sparkline.
- Activity Timeline: Message volume over time at daily, weekly, or monthly grain, plus date-range and per-participant filters that re-analyze the stored export exactly.
- Robust Export Parsing: iOS and Android formats, 12- and 24-hour clocks, English and Turkish exports; D/M/Y vs M/D/Y is decided once per file from unambiguous dates, not per line.
- Timing Metrics: Weekday averages, hour-of-day distribution, and a 7x24 weekday/hour heatmap.
- Content Metrics: Top-10 full-sequence emoji counts, top-10 Turkish-aware keyword ranking, and a media placeholder breakdown.
- Participant Metrics: Message balance with exact-to-100% shares and median reply speed per person.
- Insight Cards: Most active 4-hour window, busiest weekday, longest daily streak, conversation starter, and night owl.
- Statistics-Only Assistant: The model works from bounded aggregates and declines unsupported claims about exact messages, moments, or topics. Local mode can see a bounded excerpt, but only after you enable it.
- Bring Your Own Key: The Groq key is entered in the UI, held in your session on the server, never written to disk, never returned to the browser. A rolling 60-second burst limit guards against a runaway client.
- Single-Process Store: Stats and the entered key live in process memory, so a restart clears every session, and an abandoned one keeps its memory until
POST /api/reset. - Export-Only Input: Only WhatsApp
.txtexports, up to 25 MB; media files are counted as placeholders, not read. - Two-Language Stopwords: Keyword filtering is tuned for English and Turkish exports.
- No Automatic Fallback: Cloud and Local are chosen explicitly; neither silently takes over when the other fails. With no key at all the dashboard is fully usable and only Cloud mode is unavailable.
- Single-User, Loopback Only: The server binds
127.0.0.1and runs the single-threaded Werkzeug development server. Do not expose it on a network.
- Backend: Flask, python-dotenv, Requests
- Frontend: Vanilla HTML, CSS, and JavaScript modules, Chart.js (vendored, so the app works offline)
- AI: Groq API, or LM Studio / Ollama on loopback
- Testing: pytest
Requires Python 3.8+. A Groq key (free at console.groq.com) is optional and only needed for the Cloud assistant.
1. Install.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # optional; the app runs with no .env at all2. Assistant (optional). Open Settings -> AI Provider and paste a Groq key; it is verified with Groq and held in your session. GROQ_API_KEY in .env works as a fallback, and the UI key wins over it. For Local mode instead, start LM Studio's Developer -> Local Server (keep Serve on Local Network off) or run ollama serve, load a model, and switch the assistant to Local — no key, no internet.
3. Export a chat. In WhatsApp: Menu -> More -> Export chat -> Without media.
python run.pyIt prints where to open the app and how it is configured:
WhatsApp Analytics
────────────────────────────────────────────
Open http://localhost:8000
Assistant Cloud ready, key from .env
Sessions new key each start
Stop Ctrl+C
Ctrl+C stops it, which also forgets the uploaded chat: it only ever lived in that process.
Every setting is an environment variable read once at startup in app/config.py, and every one is optional. Notable ones: PORT (8000); APP_ENV (production sets the Secure cookie flag, so it expects HTTPS in front); FLASK_SECRET (random per process, so sessions do not survive a restart); ENABLE_DEBUGGER (off — the Werkzeug debugger executes code submitted through the browser); GROQ_MODEL / GROQ_MODELS (a preference and the picker list, with fallback if Groq stops serving a model); AI_RATE_PER_MIN (10); and LOCAL_LLM_PROVIDER / LOCAL_LLM_BASE_URL (auto probes :1234 then :11434; non-loopback URLs are rejected at startup).
Token budgets, timeouts, temperatures, and excerpt caps are documented with their defaults in .env.example. Invalid numeric values fail at startup rather than silently applying an unintended limit.
whatsapp-analytics/
├── app/
│ ├── __init__.py # Flask app factory, blueprint registration
│ ├── config.py # Environment-derived settings
│ ├── analyzer/ # parse → volume, timing, content, people → analyze()
│ ├── routes/ # views (dashboard), api (upload/stats/range/recap/reset/key), chat (Cloud + Local)
│ ├── services/ # per-session store, burst quota, groq_client, local_client
│ ├── static/ # css, js modules, vendored chart.umd.min.js, img
│ └── templates/ # index.html (dashboard) + recap.html (Recap story)
├── tests/ # analyzer, api, chat, edge cases, recap layout
├── .env.example # Every setting with its default, documented
├── run.py # Entrypoint: python run.py, Ctrl+C stops it
└── DISCLAIMER.md # WhatsApp trademark and usage notice
There is no database: everything below lives in process memory and is gone when the server stops.
- Uploaded Chats: Never written to disk, keyed by a session id in a signed cookie. Clear from Settings -> Data & Privacy, or
POST /api/reset. - Cloud AI Payload: A bounded statistics summary, the question, and successful Cloud turns. Raw messages are never sent to Groq.
- Local AI Payload: Only after explicit consent, a bounded message excerpt goes to the LM Studio or Ollama server on the same computer; turning the toggle off revokes that access and clears the local transcript.
- Assistant Transcript: Cloud and Local keep separate, server-owned transcripts. Uploading, filtering, or deleting a chat clears both.
GET /— dashboard UIGET /api/stats— current stats for this sessionPOST /api/upload—multipart/form-data, fieldfile: a.txtexport up to 25 MBPOST /api/range—start/end(inclusiveYYYY-MM-DD, empty means no bound) andsenderto narrow volume stats to one participantGET /api/recap— whole-chat stats for the Recap story, ignoring the active filtersPOST /api/reset— forget this session's chat (the Groq key is deliberately kept)POST /api/chat— Cloud assistant, fieldmessagePOST /api/chat/local— local-only assistant; never reaches GroqPOST /api/chat/reset— clear both transcripts, keeping the chat and the key/api/settings/groq-key(GET,POST,DELETE) — key status (never the key itself), verify and hold, drop/api/chat/cloud/models,/api/chat/local/modelsand the matching.../modelendpoints — what each provider actually serves, and the choicePOST /api/chat/local/enable,/api/chat/local/disable— grant or revoke raw-excerpt access
Upload and range answer with the whole payload; the assistant answers with { "reply": "…" }.
{
"loaded": true,
"stats": {
"meta": {}, "kpis": {}, "messages_over_time": {}, "hours": [], "heatmap": [],
"top_emojis": [], "top_keywords": [], "media": {}, "insights": {}
}
}Errors return {"error": "...", "reason": "..."}, with reasons such as too_long, rate_limit, not_configured, invalid_key, and, for Local mode, local_unavailable, local_no_model, and local_timeout. Failed AI calls do not enter the transcript, and a legacy history field is ignored, so a client cannot forge assistant context.
pytest # full suite
pytest tests/test_analyzer.py tests/test_api.py tests/test_chat.py # analyzer and API contract- Filtered statistics are recomputed from the stored export on every filter change, so filtered views are exact rather than derived from cached aggregates.
- Interaction metrics (reply speed, message balance, conversation starter) stay computed on the whole conversation even under a participant filter.
- The whole-chat analysis behind the Recap story is computed once per upload and reused, so opening the story does not re-parse a large export.
- Statistics, assistant history, and user text are treated as untrusted data in the AI prompt, with an explicit guard against embedded instructions.
- The assistant answers in the language of the latest question, defaulting to Turkish when that is unclear. Dashboard controls and API errors stay English.
- Keywords are frequency signals, not verified topics. The assistant does not infer message content, intent, sentiment, or relationships from aggregate data.
- With no export loaded,
POST /api/chatanswers with an upload instruction rather than calling Groq.
This project is licensed under the MIT License (LICENSE). Chart.js is vendored under app/static/vendor/ and is MIT licensed as well.
WhatsApp is a trademark of Meta Platforms, Inc.; see DISCLAIMER.
Built by heykaan.dev