AI-powered business intelligence CLI for restaurants. Scrapes public reviews from Google Maps, runs a 5-agent analysis pipeline, and produces a rich terminal report with a 7-day action plan.
pulseiq analyse "Sharma's Biryani House" --location "Chennai" --competitors 3
Runs five agents in sequence:
| Agent | What it does |
|---|---|
| 0 — Scraper | Collects reviews from Google Maps (Playwright) with Yelp fallback |
| 1 — Topic Extractor | LLM extracts topic mentions (food quality, service speed, etc.) per review |
| 2 — Temporal Drift | Detects sentiment drift across time buckets; flags RED/AMBER changes |
| 3 — Competitor Bench | Scrapes N competitors, builds topic score gap table |
| 4 — Diagnostician | Two LLM calls: root-cause hypotheses + concrete 7-day action plan |
Terminal output uses Rich — header, scorecard, drift panel, bar chart, hypotheses, action plan. Export to Markdown or JSON.
Requires Python 3.11+ in a conda environment.
conda create -n pulseiq python=3.11
conda activate pulseiq
pip install -e .
playwright install chromiumCopy .env.example to .env and add your OpenAI API key:
cp .env.example .env
# edit .env — set OPENAI_API_KEYpulseiq analyse "Business Name" --location "City" --competitors 3| Flag | Default | Description |
|---|---|---|
--location |
required | City or area |
--competitors |
3 | Number of competitors to benchmark |
--export |
— | markdown or json — saves to ./pulseiq_reports/ |
--model |
gpt-4.1-mini |
litellm model string |
--fresh |
false | Bypass the 24h review cache |
--use-embeddings |
false | K-means cluster reviews before LLM (reduces API calls) |
--dry-run |
false | Scrape only — print stats, no LLM calls |
pulseiq analyse "Business Name" --location "City" --dry-runpulseiq compare "Business A" "Business B" --location "City"Runs Agents 0+1+3 only — no temporal drift, no action plan. Prints a side-by-side topic gap table.
| Variable | Description |
|---|---|
OPENAI_API_KEY |
Required — your OpenAI API key |
PULSEIQ_RPM |
Rate limit (requests per minute). Default: 60 |
PULSEIQ_DB_PATH |
Override SQLite cache path. Default: ~/.pulseiq/cache.db |
pulseiq/
├── cli.py # Typer CLI — analyse + compare commands
├── models/schemas.py # All Pydantic v2 models and enums
├── pipeline/
│ ├── scraper.py # Agent 0 — Playwright scraping (Google Maps + Yelp)
│ ├── topic_extractor.py # Agent 1 — per-review LLM topic extraction
│ ├── temporal.py # Agent 2 — pandas drift detection + LLM interpretation
│ ├── competitor.py # Agent 3 — competitor discovery + benchmarking
│ └── diagnostician.py # Agent 4 — hypotheses + action plan
├── utils/
│ ├── cache.py # SQLite cache (reviews, topics, reports)
│ ├── llm_client.py # litellm wrapper — structured output, retry, rate limiting
│ ├── renderer.py # Rich terminal renderer
│ └── scraper_utils.py # Playwright helpers, date parsing, UA rotation
└── exports/
├── markdown.py # Markdown export
└── json_export.py # JSON export
tests/
└── synthetic_temporal_test.py # Drift detection test with synthetic data
- Reviews: cached for 24 hours per business/platform. Use
--freshto bypass. - Topic extractions: permanent — same
review_idnever re-runs LLM. - Cache lives at
~/.pulseiq/cache.db(SQLite).
- Google Maps scraping uses Playwright (headless Chromium). Requires
playwright install chromium. - Yelp scraping tries httpx+BeautifulSoup4 first, falls back to Playwright if < 15 reviews returned.
- The
--use-embeddingsflag clusters reviews withall-MiniLM-L6-v2before LLM extraction, reducing API calls from N → k (k = min(20, N//5)). - Action plan validation enforces exactly 7 items with days 1–7 in Python (not Pydantic). Invalid LLM output triggers one re-prompt.