Add dispatch bot feature brief and living backend spec.

Documents rider Telegram flows and the API/events this client needs so the shared backend can consolidate later.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
kirukib 2026-08-07 01:25:46 +03:00
parent bb81b99244
commit b4293887a8
3 changed files with 434 additions and 0 deletions

View File

@ -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`

282
docs/BACKEND_SPEC.md Normal file
View File

@ -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. |

106
docs/FEATURE_BRIEF.md Normal file
View File

@ -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.