149 lines
10 KiB
Markdown
149 lines
10 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
SeaFare Montana is a maritime logistics AI agent platform. A single-page web app with a Flask API backend, Claude API agent with tool-calling, and PostgreSQL database (SQLite in dev). Deployed on Render.com free tier (Python 3.11) with auto-deploy from `master`.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Run locally
|
|
python seafare_api.py # Starts Flask on port 5050
|
|
|
|
# Production (Render)
|
|
gunicorn seafare_api:app --bind 0.0.0.0:$PORT --workers 1 --threads 2 --timeout 120
|
|
|
|
# Check Python syntax before committing (all core files)
|
|
python -m py_compile seafare_api.py && python -m py_compile seafare_agent.py && python -m py_compile maritime_db.py && python -m py_compile moltbook_bot.py && python -m py_compile telegram_bot.py && python -m py_compile ais_provider.py && python -m py_compile equasis_parser.py && python -m py_compile marinetraffic_parser.py && python -m py_compile maritime_compliance.py
|
|
|
|
# Run tests
|
|
python -m pytest tests/ -v --tb=short
|
|
|
|
# Deploy: push to master triggers Render auto-deploy
|
|
git add <files> && git commit -m "message" && git push
|
|
```
|
|
|
|
Pre-commit hook runs automatically: syntax check → pytest → .env secret guard.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
index.html ─── Single-page frontend (vanilla JS, Leaflet map, no framework)
|
|
↓ fetch /api/v1/*
|
|
seafare_api.py ─── Flask REST API (auth, wallet, chat, admin endpoints)
|
|
↓ generate_response()
|
|
seafare_agent.py ─── Claude API agent with 26 tools (tool-calling loop, max 7 iterations)
|
|
↓ _TOOL_DISPATCH dict → handler functions
|
|
maritime_db.py ─── Database layer (19 tables, PostgreSQL/SQLite dual-mode)
|
|
↑ external data
|
|
config.py ─── Centralized constants (version, rate limits, cache TTLs, fees, env var names)
|
|
marinetraffic_parser.py ─── Ports DB (16,553 ports) + routing engine + cargo classification + premium tool helpers
|
|
ais_provider.py ─── Unified AIS: AISStream WebSocket + AISHub REST + Digitraffic REST + MT fallback
|
|
equasis_parser.py ─── IMO vessel registry (authenticated web scraping)
|
|
maritime_compliance.py ─── Sanctions screening + dark fleet detection (standalone, no external deps)
|
|
moltbook_bot.py ─── Promotional bot for Moltbook platform (background daemon)
|
|
telegram_bot.py ─── Telegram bot: standalone service, group chats, vessel watch alerts
|
|
```
|
|
|
|
### AI Agent (`seafare_agent.py`)
|
|
|
|
Uses Claude `claude-sonnet-4-5-20250929` with tool-calling. The agent loop sends user message → Claude responds with `tool_use` blocks → `_execute_tool_inner()` runs them via `_TOOL_DISPATCH` dict → results sent back → repeats until `stop_reason != "tool_use"` (max 7 iterations via `AGENT_MAX_TOOL_ITERATIONS`). System prompt is action-oriented: agent detects request type and uses tools immediately, no filler text.
|
|
|
|
**Tools (26):** `search_vessel`, `get_vessel_details`, `get_position`, `search_vessels_near_port`, `calculate_route`, `find_vessels_for_cargo`, `calculate_demurrage`, `search_contacts`, `unlock_contacts`, `get_freight_rate`, `screen_sanctions`, `check_port_congestion`, `get_bunker_prices`, `optimize_bunker`, `generate_charter_party`, `vessel_performance`, `generate_bill_of_lading`, `optimize_crew_change`, `calculate_insurance`, `estimate_port_costs`, `weather_routing`, `generate_fixture_recap`, `detect_ais_anomaly`, `detect_dark_fleet`, `search_web`, `get_revenue`
|
|
|
|
`user_context` (profile summary + balance) is injected into the system prompt so the agent knows who it's talking to.
|
|
|
|
### AIS Data Priority Chain (`ais_provider.py`)
|
|
|
|
For single vessel position (`get_vessel_position`):
|
|
1. **DB cache** — fresh position (< 5 min)
|
|
2. **AISStream** — persistent background thread OR sync fallback (8s WebSocket query in request thread)
|
|
3. **AISHub REST** — on-demand query (1 min rate limit)
|
|
4. **Digitraffic** — Finnish/Baltic waters (free, no key)
|
|
5. **MarineTraffic** — scraping fallback
|
|
6. **Stale DB** — old cache as last resort
|
|
|
|
**Important**: Daemon threads die on Render's gunicorn gthread workers. Sync fallback (`query_position_sync`, `query_area_sync`) runs inside Flask request threads and works reliably.
|
|
|
|
### Vessel Search Features
|
|
|
|
Both `search_vessels_near_port` and `find_vessels_for_cargo` use **expanding radius** search:
|
|
- Radius steps: 50 → 200 → 500 NM. First step uses full AIS provider (slow, ~6-8s). Wider steps use DB-only queries (instant).
|
|
- Always returns at least 3 vessels (MIN_VESSELS). Each vessel has `distance_nm` and `map_link`.
|
|
- `map_link` format: `{{SHOWMAP~lat~lon~zoom~name}}` — rendered as clickable buttons in chat by `formatResponse()` step 7 in `index.html`. Uses `~` delimiter (not `|`) to avoid markdown table conflicts.
|
|
|
|
### Auth System (`seafare_api.py`)
|
|
|
|
Hybrid stateless + stateful: primary auth uses `itsdangerous` signed tokens (30-day TTL, survives Render DB wipes). Fallback to legacy UUID DB sessions. `auto_promote_admin()` checks `ADMIN_EMAILS` env var on every auth.
|
|
|
|
### Payment Flow
|
|
|
|
`search_contacts` → free masked preview → user confirms → `unlock_contacts` → `charge_user()` (atomic SQL `WHERE balance >= ?`) → `add_service_charge()` → `save_purchased_contacts()`. Previously purchased contacts are returned free via `has_purchased_contact()`.
|
|
|
|
### i18n (Dual)
|
|
|
|
- **Server-side**: `TEXTS` dict in `seafare_api.py` + `L(lang, key)` helper — for API error messages
|
|
- **Client-side**: `i18n` JS object in `index.html` + `t(key)` helper — for all UI labels. Three languages: EN, RU, ES. Always add keys to all three.
|
|
|
|
### Background Threads (`seafare_api.py`)
|
|
|
|
Two daemon threads auto-start at module level:
|
|
1. **Keep-alive** (`_start_keep_alive`): pings `/health` every 14 min to prevent sleep
|
|
2. **Moltbook bot** (`_start_moltbook_bot`): searches maritime posts on Moltbook every 30 min, generates expert comments via Claude Haiku
|
|
|
|
### Telegram Bot (`telegram_bot.py` — separate systemd service)
|
|
|
|
Runs as standalone process (`seafare-telegram.service`), NOT inside gunicorn (daemon threads die on fork with `--preload`).
|
|
|
|
**Features:** Full AI agent (26 tools), group chat support (@mention/reply/commands), vessel watch alerts (/watch, /unwatch, /watches), auto language detection, per-user rate limiting in groups.
|
|
|
|
**Vessel Watch:** Background checker thread polls DB positions every 10 min. Arrival watches (one-shot) fire when vessel enters port radius. Status watches (continuous) fire on nav status changes. Max 5 watches per chat, auto-expire 7 days. DB table: `vessel_watches`.
|
|
|
|
### Adding New Tools
|
|
|
|
1. Add helper function in `marinetraffic_parser.py` (or `maritime_compliance.py` for compliance tools)
|
|
2. Add tool definition to `TOOLS` list in `seafare_agent.py` (with `input_schema`)
|
|
3. Add handler function `_tool_<name>()` in `seafare_agent.py`
|
|
4. Add entry to `_TOOL_DISPATCH` dict in `seafare_agent.py`
|
|
5. Add trigger rule in `SYSTEM_PROMPT` BEHAVIOR section
|
|
6. Add UI service card in `index.html` + i18n keys in all 3 languages (EN/RU/ES)
|
|
|
|
## Key Conventions
|
|
|
|
- **Versioning**: `APP_VERSION` in `config.py` (semver). Bump with every commit: PATCH for fixes, MINOR for features, MAJOR for breaking changes. Displays in `/health` endpoint and sidebar footer.
|
|
- **Config centralization**: All constants live in `config.py` (rate limits, cache TTLs, fees, env var names). Import from there, don't hardcode.
|
|
- **Data sources are secret**: System prompt instructs agent to never reveal Equasis/MarineTraffic. Present data as "our maritime intelligence network."
|
|
- **Admin**: `is_admin` users get free access to all paid features. Check via `is_admin` flag on user dict.
|
|
- **Atomic balance operations**: Always use `charge_user()` (returns bool) — never manually subtract balance.
|
|
- **DB migrations**: Use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` in `init_db()`. No migration framework — backward-compatible additions only.
|
|
- **.env contains secrets** — never commit. Credentials are set via Render dashboard env vars in production.
|
|
- **Single HTML file**: All CSS, HTML, and JS live in `index.html`. No build step, no bundler.
|
|
- **Port database**: `world_ports.json` — 16,553 ports from UN/LOCODE. Regenerate with `python merge_unlocode.py`.
|
|
- **Cargo classification**: `CARGO_TO_VESSEL` dict in `marinetraffic_parser.py` maps cargo types → vessel categories (bulk, tanker, container, roro, general).
|
|
|
|
## Database (PostgreSQL + SQLite)
|
|
|
|
**Dual-mode**: `maritime_db.py` auto-detects `DATABASE_URL` env var → PostgreSQL in production, SQLite locally. Wrapper classes (`_PgCursorWrapper`, `_PgConnectionWrapper`) translate SQLite-style `?` placeholders to `%s` and auto-add `RETURNING id` to INSERTs. **Always write SQL with `?` placeholders** — the wrapper handles conversion.
|
|
|
|
**Tables (22):** `vessels`, `positions`, `contacts`, `port_calls`, `demurrage`, `users`, `sessions`, `chat_history`, `user_profiles`, `wallets`, `deposits`, `withdrawals`, `service_charges`, `purchased_contacts`, `port_vessel_cache`, `equasis_cache`, `equasis_daily_counter`, `query_log`, `moltbook_comments`, `user_memory`, `conversation_summaries`, `vessel_watches`
|
|
|
|
All user-related tables FK to `users(id)`. Vessel-related tables use `mmsi` as key.
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Purpose |
|
|
|----------|---------|
|
|
| `ANTHROPIC_API_KEY` | Claude API (required for AI agent + Moltbook bot) |
|
|
| `EQUASIS_USER` / `EQUASIS_PASS` | Equasis.org login |
|
|
| `GOOGLE_CLIENT_ID` | Google OAuth |
|
|
| `ADMIN_EMAILS` | Comma-separated admin emails (auto-promoted on login) |
|
|
| `SECRET_KEY` | Token signing (falls back to ANTHROPIC_API_KEY) |
|
|
| `MOLTBOOK_API_KEY` | Moltbook platform API key (bot disabled if not set) |
|
|
| `AISSTREAM_API_KEY` | AISStream.io WebSocket API key (real-time AIS) |
|
|
| `AISHUB_USERNAME` | AISHub.net contributor username (REST AIS queries) |
|
|
| `DIGITRAFFIC_ENABLED` | '1' (default) to enable Finnish Digitraffic AIS, '0' to disable |
|
|
| `TELEGRAM_BOT_TOKEN` | Telegram Bot API token from @BotFather (bot disabled if not set) |
|
|
| `DATABASE_URL` | PostgreSQL connection string (auto-set by Render; if absent → SQLite) |
|