diff --git a/README.md b/README.md index e69de29..754c02c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,46 @@ +# Gishen Dispatch Bot + +Telegram bot for **drivers / riders**. Replaces a dedicated rider mobile app for v1. + +When Ops assigns a trip on the Admin Dispatch Board, this bot pushes the trip manifest to the driver’s Telegram chat, collects status updates via inline buttons, and captures proof of delivery (photo + recipient name). Status writes back to the shared backend so the Dispatch Board and customer order tracking stay in sync. + +## Docs + +| Doc | Purpose | +| --- | --- | +| [docs/FEATURE_BRIEF.md](docs/FEATURE_BRIEF.md) | What this bot does (acceptance inventory) | +| [docs/BACKEND_SPEC.md](docs/BACKEND_SPEC.md) | **Living** backend needs: events, endpoints, payloads, auth | + +## Standing rule — backend spec + +Whenever we **add or finalize** anything in this repo (feature, flow change, scaffold, payload tweak), update [`docs/BACKEND_SPEC.md`](docs/BACKEND_SPEC.md) in the same change set and append a changelog entry. If there is no new backend need, record `no backend delta` for that change so the sheet stays the single source of truth. + +## Stack (intended) + +| Layer | Choice | +| --- | --- | +| Runtime | Node.js | +| Bot framework | [grammY](https://grammy.dev) | +| Transport | Telegram Bot API, webhook mode | +| Shared API | One platform backend (not implemented in this repo) | + +Scaffolding comes after the backend spec sheet stabilizes with the other client repos. + +## In scope + +- Trip / manifest push on assign +- Inline status: picked up → en route → arrived → delivered / failed +- Proof of delivery (photo + recipient name) +- Real-time status write-back (via shared backend) +- Driver identity = Telegram account, enrolled by Ops (no separate login) + +## Out of scope + +- Admin Dispatch Board UI (Admin Console) +- Customer Telegram notifications / Mini App (Ecom) +- Health/medical modules, community, AI Phase 2+ (platform exclusions) +- Continuous live GPS heartbeat in v1 (poor fit for chat UX; see open items in the backend spec) + +## Remote + +`https://gitea.yaltopia.com/Gishen/Gishen-Dispatch-Bot.git` diff --git a/docs/BACKEND_SPEC.md b/docs/BACKEND_SPEC.md new file mode 100644 index 0000000..8110240 --- /dev/null +++ b/docs/BACKEND_SPEC.md @@ -0,0 +1,282 @@ +# Gishen Dispatch Bot — Backend Spec Sheet + +**Living document.** Update this file whenever this repo adds or finalizes a feature, flow, scaffold, or payload change. Append a [Changelog](#changelog) entry in the same change set. If the change needs nothing new from the backend, log `no backend delta`. + +**Consumers:** shared platform backend team (consolidation with other client repos’ spec sheets). +**Client:** `Gishen-Dispatch-Bot` (Telegram rider bot). +**Does not implement backend** — describes what the bot needs. + +**Supersedes for v1:** Flutter rider-app assumptions in `Gishen-Ecom/docs/backend.md` §10 (JWT rider app + limited status enum). Auth and status machine below replace that sketch for the rider surface. + +--- + +## 1. Auth & rider enrollment + +### Model + +| Concept | Description | +| --- | --- | +| Rider | Platform staff/driver record (`role: rider` or equivalent) | +| Telegram link | `telegram_user_id` (required), optional `telegram_username` | +| Enrollment | Ops admin links Telegram identity → rider; no password / OTP for drivers | + +### Bot → backend auth + +Recommended: **service credentials** for the bot process (API key / mTLS / signed service JWT), plus every rider-facing call includes `telegram_user_id` so the backend resolves `rider_id`. + +Alternative acceptable: short-lived rider token issued after the backend verifies a Telegram login deep-link — still must bind to enrolled `telegram_user_id`. + +Unenrolled `telegram_user_id` → `403` with stable error code `rider_not_enrolled`. + +### Admin APIs needed (Ops / Admin Console will call these; bot may only read link status) + +| Method | Path (proposed) | Purpose | +| --- | --- | --- | +| `POST` | `/admin/riders` | Create rider profile (name, phone, branch/zone, active) | +| `POST` | `/admin/riders/:riderId/telegram` | Link `{ telegram_user_id, telegram_username? }` | +| `DELETE` | `/admin/riders/:riderId/telegram` | Unlink Telegram identity | +| `GET` | `/admin/riders/:riderId` | Include telegram link status | +| `GET` | `/dispatch/riders/by-telegram/:telegramUserId` | Resolve rider for bot bootstrap (or bot-only equivalent under `/bot/...`) | + +--- + +## 2. Domain events — backend → bot + +Bot must be notified when Ops mutates assignment. Prefer a **push** to the bot service (HTTP webhook the bot exposes, or a queue the bot consumes). Polling alone is insufficient for “prompt” assign UX. + +| Event | When | Payload (minimum) | +| --- | --- | --- | +| `trip.assigned` | Rider assigned to trip | `trip` manifest (see §4), `rider_id`, `telegram_user_id`, `assigned_at` | +| `trip.reassigned` | Rider changed | Previous + new `telegram_user_id`, full `trip` for new rider, `reason?` | +| `trip.cancelled` | Trip cancelled | `trip_id`, `telegram_user_id`, `cancelled_at`, `reason?` | +| `trip.updated` | Manifest material change (address, COD, stops) while in progress | `trip` (full or patch), `telegram_user_id` | + +### Webhook contract (proposed) + +`POST {DISPATCH_BOT_WEBHOOK_URL}/hooks/dispatch` + +```json +{ + "event": "trip.assigned", + "occurred_at": "2026-08-07T00:00:00Z", + "idempotency_key": "trip.assigned:trip_123:rider_9", + "data": { } +} +``` + +Bot responds `2xx` after durable accept. Backend retries on non-2xx with backoff. Bot treats duplicate `idempotency_key` as no-op success. + +--- + +## 3. Trip status machine + +### Statuses (v1) + +| Status | Meaning | +| --- | --- | +| `assigned` | On rider; not yet picked up | +| `picked_up` | Parcel(s) collected from branch / pickup | +| `en_route` | Heading to customer | +| `arrived` | At delivery location | +| `delivered` | Completed successfully (POD required) | +| `failed` | Could not complete (exception reason required) | + +**Delta vs Ecom sketch:** add `en_route` and `arrived`; keep `picked_up` / `delivered` / `failed`. + +### Allowed transitions + +``` +assigned → picked_up → en_route → arrived → delivered + arrived → failed + en_route → failed + picked_up → failed +``` + +Backend is source of truth. Bot only offers buttons for transitions the API allows. + +### Bot → backend + +| Method | Path (proposed) | Body | +| --- | --- | --- | +| `POST` | `/rider/trips/:tripId/status` | See below | + +```json +{ + "telegram_user_id": 123456789, + "status": "en_route", + "occurred_at": "2026-08-07T00:05:00Z", + "idempotency_key": "trip_123:en_route:2026-08-07T00:05:00Z", + "exception": null, + "stop_id": null +} +``` + +For `failed`: + +```json +{ + "telegram_user_id": 123456789, + "status": "failed", + "occurred_at": "2026-08-07T00:10:00Z", + "idempotency_key": "trip_123:failed:…", + "exception": { + "code": "unreachable_customer", + "note": "Phone off after 3 attempts" + }, + "stop_id": "stop_1" +} +``` + +**Exception codes (v1):** `unreachable_customer` | `wrong_address` | `refused` | `partial_delivery` | `other` + +**Responses** + +| Code | Meaning | +| --- | --- | +| `200` | Applied; return current trip snapshot | +| `409` | Illegal transition or trip no longer assigned to this rider | +| `403` | Not enrolled / not assignee | +| `404` | Unknown trip | + +Backend fan-out: update Dispatch Board + customer order tracking after successful transition. + +--- + +## 4. Trip manifest shape + +Returned on assign events and on `GET /rider/trips/me` (or `GET /rider/trips/:tripId`). + +```json +{ + "trip_id": "trip_123", + "status": "assigned", + "branch": { + "id": "br_1", + "name": "Gishen Bole", + "address": "…" + }, + "zone": "bole-east", + "cod_total_etb": 450.0, + "currency": "ETB", + "notes": "Call on arrival", + "assigned_at": "2026-08-07T00:00:00Z", + "stops": [ + { + "stop_id": "stop_1", + "sequence": 1, + "customer": { + "display_name": "A. Bekele", + "phone": "+2519…" + }, + "address": { + "line1": "…", + "line2": null, + "lat": 9.01, + "lng": 38.79, + "maps_url": "https://maps.google.com/…" + }, + "cod_etb": 450.0, + "items": [ + { + "sku": "MED-001", + "name": "Paracetamol 500mg 20s", + "qty": 1 + } + ], + "order_id": "ord_99", + "fulfilment_id": "ful_55" + } + ] +} +``` + +### List active trips + +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/rider/trips/me?telegram_user_id=` | Active + recently completed (window TBD) | + +--- + +## 5. Proof of delivery (POD) + +Required before or with transition to `delivered` (backend enforces). + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/rider/trips/:tripId/pod` | Upload POD for a stop (or whole trip if single-stop) | + +**Multipart or two-step:** + +1. `POST /rider/trips/:tripId/pod/media` — binary photo (`image/jpeg` \| `image/png`), returns `media_id` +2. `POST /rider/trips/:tripId/pod` — JSON metadata + +```json +{ + "telegram_user_id": 123456789, + "stop_id": "stop_1", + "recipient_name": "Almaz Bekele", + "media_id": "media_abc", + "captured_at": "2026-08-07T00:12:00Z", + "idempotency_key": "trip_123:stop_1:pod" +} +``` + +Bot obtains the photo from Telegram (`getFile`) and uploads bytes to the backend (backend should not depend on Telegram file URLs long-term). + +**Delivered transition** may be combined: + +`POST /rider/trips/:tripId/status` with `status: "delivered"` and embedded `pod: { … }` — acceptable if backend prefers one round-trip; document one approach as canonical. **Canonical for this sheet:** separate POD create, then status `delivered` (clearer retries). + +--- + +## 6. What this bot does **not** need from backend (v1) + +| Capability | Note | +| --- | --- | +| `POST /rider/location` continuous heartbeat | Deferred — Telegram chat is a poor fit; revisit if Dispatch Board needs live map pins from riders | +| Customer notification send | Owned by notification worker / Ecom channels | +| Catalog, cart, loyalty, B2B entitlements | Other repos | +| Pharmacist / stock / finance admin APIs | Admin Console | + +--- + +## 7. Idempotency & errors + +- All mutating bot → backend calls carry `idempotency_key` (stable per user action). +- Backend → bot webhooks carry `idempotency_key` per event emission. +- Error body shape (proposed): `{ "error": { "code": "rider_not_enrolled", "message": "…" } }` +- Bot maps known codes to Amharic/English driver-facing copy (copy can live in bot; codes must be stable). + +--- + +## 8. Real-time fan-out (backend responsibility) + +After successful status or POD write, backend must update: + +1. Admin Dispatch Board subscribers +2. Customer order tracking (timeline; map when rider location exists — N/A until location is in scope) + +Exact mechanism (SSE, websocket, poll) is backend-owned; this bot only requires durable API success. + +--- + +## 9. Open items + +| # | Item | Proposal | +| --- | --- | --- | +| 1 | Driver channel | Telegram (confirmed default) | +| 2 | Spec ownership | This repo’s sheet stays independent; Admin board has its own | +| 3 | Customer vs driver bots | Separate Telegram bots | +| 4 | Live GPS | Out of v1 | +| 5 | Multi-stop POD | POD per `stop_id`; trip `delivered` only when all stops terminal | +| 6 | Partial delivery | Use `failed` + `partial_delivery` or a future `partially_delivered` status — **prefer exception on `failed` for v1** until Ops defines partial completion rules | +| 7 | Path prefix | `/rider/...` vs `/bot/dispatch/...` — backend may rename; semantics above matter more than literal paths | + +--- + +## Changelog + +| Date | Change | +| --- | --- | +| 2026-08-07 | Initial backend spec: Telegram rider auth/enrollment, assign/reassign/cancel events, status machine (`en_route`, `arrived`), manifest shape, POD upload, idempotency; defer live GPS; supersede Ecom Flutter-rider §10 for v1 rider surface. | diff --git a/docs/FEATURE_BRIEF.md b/docs/FEATURE_BRIEF.md new file mode 100644 index 0000000..b2a1815 --- /dev/null +++ b/docs/FEATURE_BRIEF.md @@ -0,0 +1,106 @@ +# Gishen Dispatch Bot — Feature Brief + +**Source:** Yaltopia Tech Proposal (Rev. 2.0) + Gishen E-Commerce PRD — platform feature inventory, Admin § Dispatch Bot +**Repo:** `Gishen-Dispatch-Bot` +**Channel assumption:** Telegram (same family as customer notification bots; separate bot instance for drivers) + +This document is the build brief for **this repo only**. Backend contracts live in [BACKEND_SPEC.md](./BACKEND_SPEC.md). + +--- + +## Product role + +Replace a dedicated rider / driver mobile app for v1. Drivers receive trip work and report progress entirely inside Telegram. Ops enrolls drivers by linking their Telegram account; there is no separate driver login or app install beyond Telegram. + +The Admin **Dispatch Board** (desk console) remains in the Admin Console repo. This bot is the rider-facing surface only. + +--- + +## Features + +### 1. Trip assignment push + +**When:** Ops assigns (or reassigns) a trip on the Dispatch Board. + +**Then:** The bot delivers a trip/order manifest to the assigned driver’s Telegram chat, including: + +- Trip id and branch / zone context +- Ordered stops with addresses +- Line items per stop (customer-safe names/qty; no clinical prescription narrative beyond what Ops already exposes to riders) +- COD amount if applicable +- Special delivery notes if present + +**Acceptance** + +- [ ] Assigned driver receives the manifest promptly after assign +- [ ] Reassign removes or supersedes the previous driver’s active trip message / buttons +- [ ] Cancelled trip notifies the driver and disables further status actions + +### 2. Inline status updates + +Driver advances the trip with inline action buttons: + +`picked_up` → `en_route` → `arrived` → `delivered` / `failed` + +**Acceptance** + +- [ ] Only legal next transitions are shown (no skipping without backend allowance) +- [ ] Each successful press updates the shared backend; Dispatch Board and customer tracking reflect the new status in near real time +- [ ] Failed path requires or allows an exception reason (unreachable, wrong address, refused, partial delivery, other) +- [ ] Stale / already-advanced buttons are rejected safely (idempotent or clear error) + +### 3. Proof of delivery (POD) + +On `delivered` (and optionally on partial success paths if Ops requires): + +- Driver sends a **photo** in the bot conversation +- Driver types **recipient name** in chat + +**Acceptance** + +- [ ] POD photo + recipient name are attached to the trip/stop record via the backend +- [ ] Delivery cannot be finalized without required POD fields (unless Ops configures an exception path later) +- [ ] Driver gets clear confirmation when POD is accepted + +### 4. Driver identity & enrollment + +- No separate driver credentials +- Identity = Telegram `user_id` (and username if available), linked to a platform `rider` record by an Operations admin +- Unlinked Telegram users cannot receive trips or mutate status + +**Acceptance** + +- [ ] Unenrolled user gets a clear “not enrolled — contact Ops” message +- [ ] Enrolled driver can list / see their active trip(s) +- [ ] Ops can unlink a driver; subsequent bot actions from that Telegram account are rejected + +### 5. Real-time write-back + +Status and POD updates from the bot must flow to: + +- Admin Dispatch Board +- Customer order tracking (web / mobile / Mini App) + +Transport is the shared backend (webhooks/events/websockets as the API team chooses). This bot does not talk to customer clients directly. + +--- + +## Non-goals (this repo / phase) + +| Item | Owner / note | +| --- | --- | +| Dispatch Board UI, batching, route map, drag-reassign | Admin Console | +| Customer Telegram order alerts / Mini App commerce | Ecom Web | +| Continuous live GPS heartbeat | Deferred for v1 (see backend open items) | +| Health trackers, community, AI Phase 2+ | Platform exclusions | +| Flutter dedicated rider app | Superseded by this bot for v1 | + +--- + +## Open items to flag upward + +1. **Telegram confirmed** as the driver channel (default yes). Flag if Ops wants WhatsApp or a native app instead. +2. This repo owns its **own** backend spec sheet — not folded into Admin’s. +3. Customer notification bot vs dispatch bot: **separate bots**, shared worker/patterns recommended. +4. Live location: out of v1 unless Dispatch Board requires continuous map pins from riders. +5. Platform open items (checkout entitlement-split, Procurement vs Stock transfers, Mini App spec ownership) are **N/A** to this repo.