Backend - Routes moved under /api/, JWT bearer auth via @before_request - DEV_AUTH_BYPASS escape hatch for local dev - In-memory chat history and report state replaced with Postgres tables (preferences, chat_messages, reports, feedback_events) keyed on user - SQLAlchemy 2.x + Alembic migrations run on container start - Graceful Airtable failure handling — bad creds no longer 500 the API - Per-user data isolation via g.user_email from validated token Frontend - React + Vite + TypeScript SPA at /programme-pulse/ - MSAL.js (PKCE, sessionStorage, ID token to backend) - VITE_DEV_AUTH_BYPASS mirrors backend bypass for local dev - Streaming chat via fetch ReadableStream + SSE parsing - Charts via chart.js, markdown via react-markdown + remark-gfm - Full UI parity with the original templates/index.html Deploy (optical-dev split-build pattern) - Dockerfile + docker-compose.yml (name: programme-pulse pinned; app + Postgres; 127.0.0.1 binding only) - deploy/apache-programme-pulse.conf.tmpl with flushpackets=on for SSE - deploy/deploy.sh mirrors OSOP — port auto-pick (5051..5099), apache conf render, frontend build in throwaway node container, rsync to /var/www/html/programme-pulse, /api/health poll Tests - 49 passing; new tests for DB-backed preferences and JWT auth helpers - SQLite-backed test fixture in tests/conftest.py Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
137 lines
3.6 KiB
Markdown
137 lines
3.6 KiB
Markdown
# Programme Pulse
|
|
|
|
A small Flask app for chatting with live Airtable programme data and generating two-tier status reports (Manager Summary + Full Report) as Word documents.
|
|
|
|
Runs locally on port 5051 by default.
|
|
|
|
---
|
|
|
|
## What it does
|
|
|
|
- Loads tasks from one Airtable base (the Master tracker) and resource bookings from a second Airtable base.
|
|
- Lets you chat with the data through Claude. Streaming response, stop button, thumbs up/down feedback.
|
|
- Reads `.docx` meeting transcripts from `docs/Programme Pulse transcripts/` and uses them as supporting context.
|
|
- Generates two reports on demand: a Manager Summary and a Full Report. Both saved as `.docx` in `reports/`.
|
|
- Saves your stated preferences to `data/preferences.md` so future sessions remember them.
|
|
|
|
---
|
|
|
|
## Requirements
|
|
|
|
- Python 3.11 or newer
|
|
- An Anthropic API key
|
|
- Two Airtable bases with personal access tokens
|
|
|
|
---
|
|
|
|
## Setup
|
|
|
|
1. Create and activate a virtual environment.
|
|
|
|
```sh
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
```
|
|
|
|
2. Install dependencies.
|
|
|
|
```sh
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
3. Copy the example env file and fill in your own keys.
|
|
|
|
```sh
|
|
cp .env.example .env
|
|
```
|
|
|
|
Open `.env` and add:
|
|
- `ANTHROPIC_API_KEY`
|
|
- `PULSE_AIRTABLE_API_KEY`, `PULSE_AIRTABLE_BASE_ID`, `PULSE_AIRTABLE_TABLE_ID` — the tasks base
|
|
- `PULSE_RESOURCE_API_KEY`, `PULSE_RESOURCE_BASE_ID`, `PULSE_RESOURCE_TABLE_ID` — the resource booking base
|
|
- `FLASK_SECRET_KEY` — any random string
|
|
|
|
4. Run the app.
|
|
|
|
```sh
|
|
python web_app.py
|
|
```
|
|
|
|
Or with gunicorn (recommended — streaming works better):
|
|
|
|
```sh
|
|
gunicorn web_app:app --bind 0.0.0.0:5051 --worker-class gevent --workers 1 --timeout 300
|
|
```
|
|
|
|
5. Open http://localhost:5051 in your browser.
|
|
|
|
---
|
|
|
|
## Adding meeting transcripts
|
|
|
|
Drop Teams `.docx` transcript exports into `docs/Programme Pulse transcripts/`. The app picks them up on restart and feeds them to the chat and the reports as supporting narrative.
|
|
|
|
Each `.docx` is parsed paragraph by paragraph. One speaker turn per paragraph.
|
|
|
|
---
|
|
|
|
## Folder layout
|
|
|
|
```
|
|
web_app.py Flask entry point
|
|
src/
|
|
airtable_client.py Pulls tasks and bookings from Airtable
|
|
analyzer.py Health signal logic
|
|
claude_client.py Anthropic SDK wrapper, streaming
|
|
preferences.py Saves liked/disliked patterns
|
|
prompts.py System prompts and snapshot builders
|
|
reporter.py Word document generation
|
|
transcripts.py Parses .docx meeting transcripts
|
|
templates/index.html The single-page UI
|
|
design/ Fonts and design tokens
|
|
data/ preferences.md lives here
|
|
docs/Programme Pulse transcripts/ Drop .docx files here
|
|
reports/ Generated Word files land here
|
|
logs/ Runtime logs
|
|
tests/ Pytest suite
|
|
```
|
|
|
|
---
|
|
|
|
## Airtable schema notes
|
|
|
|
The tasks base must include these fields (rename the constants in `src/airtable_client.py` if your column names differ):
|
|
- Title / task name
|
|
- Owner (collaborator)
|
|
- Status / Progress
|
|
- Priority
|
|
- Due date
|
|
- Notes
|
|
- Last modified
|
|
|
|
The resource base needs:
|
|
- Resource name
|
|
- Project
|
|
- Division
|
|
- Start date, end date
|
|
- Hours
|
|
- Status
|
|
- Conflict flag
|
|
|
|
Open `src/airtable_client.py` to map field names to your own base.
|
|
|
|
---
|
|
|
|
## Running tests
|
|
|
|
```sh
|
|
pytest
|
|
```
|
|
|
|
---
|
|
|
|
## Deploying elsewhere
|
|
|
|
The app is stateless apart from `data/preferences.md`, `reports/`, and `logs/`. Mount those as volumes if you containerise it. The `Procfile` is set up for any platform that respects it (Heroku, Render, Fly, etc).
|
|
|
|
Set `PORT` via env var. Everything else is read from `.env` or the platform's secret store.
|