{"id":50939440,"url":"https://github.com/manziosee/ussd","last_synced_at":"2026-06-17T12:30:53.329Z","repository":{"id":359883496,"uuid":"1239104960","full_name":"manziosee/ussd","owner":"manziosee","description":"SmartAssist is an AI-powered USSD assistant that lets any mobile phone user (feature phone or smartphone, no internet required) access AI by simply dialling a shortcode like *123#. Responses are instant and cost the user nothing extra beyond their normal USSD session.","archived":false,"fork":false,"pushed_at":"2026-05-31T19:25:17.000Z","size":208,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T20:22:26.304Z","etag":null,"topics":["alembic","docker","fastapi","pytest","python3","redis-cache","sqlalchemy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/manziosee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T19:08:34.000Z","updated_at":"2026-05-31T19:25:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/manziosee/ussd","commit_stats":null,"previous_names":["manziosee/ussd"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/manziosee/ussd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manziosee%2Fussd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manziosee%2Fussd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manziosee%2Fussd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manziosee%2Fussd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/manziosee","download_url":"https://codeload.github.com/manziosee/ussd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manziosee%2Fussd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34449277,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alembic","docker","fastapi","pytest","python3","redis-cache","sqlalchemy"],"created_at":"2026-06-17T12:30:52.271Z","updated_at":"2026-06-17T12:30:53.322Z","avatar_url":"https://github.com/manziosee.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SmartAssist USSD\n\n\u003e **AI for everyone — including the 3 billion people with no internet.**\n\nSmartAssist is an AI-powered USSD assistant that lets any mobile phone user\n(feature phone or smartphone, zero internet required) access AI by dialling\na shortcode like `*123#`. Works with any USSD aggregator, any country,\nany mobile operator.\n\n---\n\n## Technology Stack\n\n| | Technology | Role |\n|---|---|---|\n| ![Python](https://img.shields.io/badge/Python_3.12-3776AB?style=flat\u0026logo=python\u0026logoColor=white) | **Python 3.12** | Core language |\n| ![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat\u0026logo=fastapi\u0026logoColor=white) | **FastAPI** | Async web framework (USSD app + SMS gateway) |\n| ![Groq](https://img.shields.io/badge/Groq_Llama-FF6B35?style=flat\u0026logoColor=white) | **Groq — Llama 3.1 8B** | Ultra-fast AI inference (~50 ms) |\n| ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?style=flat\u0026logo=postgresql\u0026logoColor=white) | **PostgreSQL** | User data, interactions, market prices |\n| ![Redis](https://img.shields.io/badge/Redis-DC382D?style=flat\u0026logo=redis\u0026logoColor=white) | **Redis** | Sessions, AI cache, rate limiting |\n| ![Jasmin](https://img.shields.io/badge/Jasmin_SMPP-2C3E50?style=flat\u0026logoColor=white) | **Jasmin SMS Gateway** | Open-source SMPP gateway — own SMS infrastructure, zero per-SMS cost |\n| ![RabbitMQ](https://img.shields.io/badge/RabbitMQ-FF6600?style=flat\u0026logo=rabbitmq\u0026logoColor=white) | **RabbitMQ** | Jasmin internal message queue |\n| ![Docker](https://img.shields.io/badge/Docker-2496ED?style=flat\u0026logo=docker\u0026logoColor=white) | **Docker Compose** | Full-stack deployment |\n| ![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy_2.0-D71F00?style=flat\u0026logoColor=white) | **SQLAlchemy 2.0** | Async ORM |\n| ![Alembic](https://img.shields.io/badge/Alembic-grey?style=flat) | **Alembic** | Database migrations |\n| ![pytest](https://img.shields.io/badge/pytest-0A9EDC?style=flat\u0026logo=pytest\u0026logoColor=white) | **pytest-asyncio** | 45-test async suite |\n\n---\n\n## Architecture\n\n```\nAny mobile phone dials a shortcode  (no internet — any feature phone)\n          │\n          ▼\n┌──────────────────────────────────────┐\n│         USSD Aggregator              │\n│  (your regional USSD network         │\n│   partner — AT, Comviva, Ericsson,   │\n│   NTTDATA, local operator, etc.)     │\n└──────────────┬───────────────────────┘\n               │  POST /ussd  (form-encoded)\n               │  sessionId · phoneNumber · text\n               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│              SmartAssist USSD App  (port 8000)                   │\n│                                                                   │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                USSD State Machine                        │   │\n│  │  Parses *-separated input → routes to correct handler   │   │\n│  └─────────────────────┬────────────────────────────────────┘   │\n│         ┌──────────────┼──────────────┐                         │\n│         ▼              ▼              ▼                         │\n│  ┌────────────┐  ┌──────────┐  ┌───────────────┐              │\n│  │   Redis    │  │  Groq    │  │  PostgreSQL   │              │\n│  │  Sessions  │  │  Llama   │  │  Users        │              │\n│  │  AI cache  │  │  3.1 8B  │  │  Interactions │              │\n│  │  Rate limit│  │  ~50 ms  │  │  Market prices│              │\n│  └────────────┘  └──────────┘  └───────────────┘              │\n│                                                                   │\n│  Long answer → POST /sms/send                                    │\n└──────────────────────┬───────────────────────────────────────────┘\n                       │\n                       ▼\n┌──────────────────────────────────────────────────────────────────┐\n│             SMS Gateway API  (port 8001)                         │\n│                                                                   │\n│  • Validates E.164 phone number                                   │\n│  • Extracts country dialing prefix                                │\n│  • Looks up Jasmin connector (or lets Jasmin route globally)      │\n│  • Detects GSM-7 vs UCS-2 encoding automatically                 │\n│  • Bulk send for daily tip broadcasts                             │\n└──────────────────────┬───────────────────────────────────────────┘\n                       │  POST to Jasmin HTTP API (port 1401)\n                       ▼\n┌──────────────────────────────────────────────────────────────────┐\n│                Jasmin SMPP Gateway  (port 1401/2775)             │\n│                                                                   │\n│  Connectors are named by ISO country code and point to           │\n│  whatever SMPP provider the operator chooses.                    │\n│  Add/remove via:  telnet localhost 8990  (Jasmin CLI)            │\n│                                                                   │\n│  Default mode: empty connector map → Jasmin's MT routing rules   │\n│  decide automatically — works globally with any provider.        │\n└──────────────────────┬───────────────────────────────────────────┘\n                       │  SMPP v3.4\n                       ▼\n           Telecom Operator  (any country, any network)\n                       │\n                       ▼\n                 User's Phone\n```\n\n---\n\n## Why Jasmin Instead of Twilio / Third-Party SMS APIs?\n\n| | Third-party SMS API | Jasmin SMPP |\n|---|---|---|\n| **Cost per SMS** | $0.0075 – $0.05 | ~$0 (direct operator rate) |\n| **Infrastructure** | Vendor cloud | Your own server |\n| **Latency** | 1–5 s (cloud relay) | \u003c 500 ms (direct SMPP) |\n| **Multi-country** | Different API per provider | One gateway, all operators |\n| **Carrier lock-in** | Yes | None — swap providers without code changes |\n| **Data sovereignty** | Vendor servers | Your servers |\n\n---\n\n## Menu Structure\n\n```\nDial shortcode → Onboarding (new users: language + role) → Main Menu\n└── SmartAssist AI\n    ├── 1. Business\n    │   ├── 1–4. Pricing / Bookkeeping / Marketing / Get customers  (AI tip → CON)\n    │   │         └── 1.More tips · 2.More detail · 3.Send SMS · 4.Helpful · 5.Not helpful\n    │   ├── 5. My question  (free AI question — paginated for long answers)\n    │   └── 6. Calculator   (profit check · loan payment — zero AI cost)\n    │\n    ├── 2. Farming\n    │   ├── 1–3. Soil / Pest control / Best crops  (AI tip)\n    │   ├── 4. Market prices  (DB-backed, configurable per deployment)\n    │   ├── 5. My question\n    │   └── 6. Nearby offices  (configurable services directory)\n    │\n    ├── 3. Health\n    │   ├── 1–4. Nutrition / Hygiene / Maternal / Child  (AI tip)\n    │   ├── 5. My question\n    │   ├── 6. Nearby clinics  (configurable services directory)\n    │   └── 7. Emergency numbers  (configurable per country)\n    │\n    ├── 4. Education\n    │   ├── 1–4. Study / Career / Math / English  (AI tip)\n    │   ├── 5. My question\n    │   └── 6. Nearby schools  (configurable services directory)\n    │\n    ├── 5. Ask AI  (free-form question, paginated for long answers)\n    │\n    └── 6. Account\n        ├── 1. My stats  · 2. Set name  · 3. Set profession\n        ├── 4. Language  · 5. SMS alerts · 6. Daily tips\n        └── 0. Main menu\n```\n\n---\n\n## Quick Start\n\n### 1. Clone and configure\n\n```bash\ngit clone https://github.com/manziosee/ussd.git\ncd ussd\n```\n\nEdit `.env` (key settings):\n\n```env\n# AI — get a free key at console.groq.com\nGROQ_API_KEY=gsk_...\nGROQ_MODEL=llama-3.1-8b-instant\n\n# Your USSD shortcode (assigned by your USSD aggregator)\nUSSD_SHORTCODE=*123#\n\n# Database — Neon free tier at neon.tech, or local Docker\nDATABASE_URL=postgresql+asyncpg://user:pass@host/dbname\n\n# SMS gateway (Docker service name; use http://localhost:8001 locally)\nSMS_GATEWAY_URL=http://sms-gateway:8001\n\n# Jasmin — leave empty to use Jasmin's global routing rules (recommended)\nSMS_GW_CONNECTOR_MAP_JSON={}\n\n# USSD webhook security (leave empty for local dev / sandbox)\nAT_API_KEY=\nAT_WEBHOOK_TOKEN=\n```\n\n### 2. Start with Docker Compose\n\n```bash\ndocker-compose up -d\n```\n\n| Service | URL |\n|---|---|\n| USSD app | http://localhost:8000 |\n| API docs | http://localhost:8000/docs |\n| Admin dashboard | http://localhost:8000/admin/dashboard?key=`\u003cADMIN_API_KEY\u003e` |\n| SMS Gateway | http://localhost:8001 |\n| Jasmin CLI | `telnet localhost 8990` (admin / admin) |\n| RabbitMQ UI | http://localhost:15672 (jasmin / jasmin) |\n\n### 3. Connect an SMPP provider in Jasmin\n\nJasmin connects to telecom operators via SMPP. Use the Jasmin CLI to add connectors:\n\n```bash\ntelnet localhost 8990\n# Username: admin   Password: admin\n```\n\n```\n# Add a connector — name it with the ISO country code\nsmppccm -a\n\u003e cid ke\n\u003e host smpp.your-provider.com\n\u003e port 2775\n\u003e username your_smpp_user\n\u003e password your_smpp_pass\n\u003e ok\n\n# Add a catch-all MT route (sends all numbers through this connector)\nmtrouter -a\n\u003e order 100\n\u003e type DefaultRoute\n\u003e connector smppc(ke)\n\u003e ok\n\n# Save and start\npersist\nsmppccm -1 ke\nquit\n```\n\n\u003e **Multiple countries:** add one connector per country (`ke`, `ng`, `gh`, `in`, `pk` …),\n\u003e then add MT route filters so Jasmin routes each prefix to the right connector.\n\u003e The SMS gateway will honour Jasmin's routing automatically.\n\n\u003e **Test SMPP server:** `smpp.ozekisms.com:9500` (free public test endpoint — no real SMS sent).\n\n### 4. Run without Docker (development)\n\n```bash\n# Install main app dependencies\npip install -r requirements.txt\n\n# Start USSD app (auto-uses fakeredis if Redis is unavailable)\nuvicorn app.main:app --reload --port 8000\n\n# In a second terminal — start SMS gateway\ncd sms_gateway\npip install -r requirements.txt\nuvicorn main:app --reload --port 8001\n```\n\n### 5. Test with the simulator\n\n```bash\npython simulator/cli_sim.py\n```\n\n```\n══════════════════════════════════════════\n   SmartAssist USSD Simulator\n══════════════════════════════════════════\n\n┌────────────────────────────────────────┐\n│  Welcome to SmartAssist!               │\n│  Choose language:                      │\n│  1.English                             │\n│  2.Kinyarwanda                         │\n└────────────────────────────────────────┘\n  Your input: _\n```\n\n### 6. Run tests\n\n```bash\npytest\n```\n\nAll 45 tests pass with no external services required.\n\n---\n\n## SMS Gateway API\n\nThe gateway runs as an independent microservice on port 8001.\n\n### Send a single SMS\n\n```bash\ncurl -X POST http://localhost:8001/sms/send \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\": \"+254700000001\", \"message\": \"Hello from SmartAssist!\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message_id\": \"01234-5678-uuid\",\n  \"connector\": null,\n  \"country_code\": \"254\",\n  \"error\": null\n}\n```\n\n`connector: null` means Jasmin's own routing rules dispatched the message — the correct global default.\n\n### Bulk send (daily tips broadcast)\n\n```bash\ncurl -X POST http://localhost:8001/sms/send-bulk \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"recipients\": [\"+254700000001\", \"+234800000001\", \"+91900000001\"],\n    \"message\": \"Daily tip: Price = cost + 30% profit minimum.\",\n    \"sender_id\": \"SmartAssist\"\n  }'\n```\n\n```json\n{\"sent\": 3, \"failed\": 0, \"results\": [...]}\n```\n\n### Gateway health\n\n```bash\ncurl http://localhost:8001/health\n```\n\n```json\n{\"status\": \"ok\", \"jasmin_reachable\": true, \"version\": \"1.0.0\"}\n```\n\n---\n\n## SMS Gateway — Country Routing\n\nThe connector map is **empty by default**. Jasmin handles routing globally through its own MT route rules — you configure those once in the Jasmin CLI, and the gateway never needs to know about carriers.\n\nTo override routing for specific prefixes, set `SMS_GW_CONNECTOR_MAP_JSON`:\n\n```env\n# Route by country code prefix → your named Jasmin connector\nSMS_GW_CONNECTOR_MAP_JSON={\"254\":\"ke\",\"234\":\"ng\",\"91\":\"in\",\"44\":\"uk\"}\n```\n\nConnector names are ISO 3166-1 alpha-2 country codes. They map to SMPP connections you configure in Jasmin — entirely carrier-agnostic.\n\n---\n\n## Admin API\n\nAll admin routes require `X-Admin-Key: \u003cADMIN_API_KEY\u003e`.\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/admin/dashboard` | HTML dashboard with charts |\n| `GET` | `/admin/stats` | Aggregated analytics |\n| `GET` | `/admin/interactions` | Query history (paginated) |\n| `GET` | `/admin/users` | User list |\n| `GET` | `/admin/market-prices` | Crop price list |\n| `PUT` | `/admin/market-prices` | Upsert a single price |\n| `POST` | `/admin/market-prices/bulk` | Bulk upsert prices |\n| `DELETE` | `/admin/market-prices/{id}` | Delete a price |\n| `GET` | `/admin/feedback` | Helpful / not-helpful counts |\n| `POST` | `/ussd` | USSD webhook (form-encoded) |\n| `POST` | `/simulate` | Local USSD simulator (JSON) |\n| `GET` | `/health` | Health check |\n\n---\n\n## Localisation\n\nThe system ships with English and Kinyarwanda menus. To add a language:\n\n1. Add entries to `_STRINGS` in `app/services/menu_service.py`\n2. Add category labels to `_LABELS` in `app/services/broadcast_service.py`\n3. Add the AI language hint in `app/services/ai_service.py`\n\n## Local Services Directory\n\n`app/data/services.py` and `app/data/emergency.py` contain sample data for\none deployment region. Replace these files with your own districts, clinics,\nschools, and emergency numbers. The menu system reads them dynamically —\nno code changes needed, only data.\n\n---\n\n## Project Structure\n\n```\nussd/\n├── app/                          # USSD Application (port 8000)\n│   ├── main.py                   # FastAPI app, lifespan, startup\n│   ├── config.py                 # All settings via .env (incl. USSD_SHORTCODE)\n│   ├── auth.py                   # USSD webhook HMAC + token guard\n│   ├── models/                   # User · Interaction · MarketPrice · Feedback\n│   ├── services/\n│   │   ├── menu_service.py       # USSD state machine\n│   │   ├── ai_service.py         # Groq Llama 3.1 8B + Redis cache + retry\n│   │   ├── session_service.py    # Redis sessions, cache, rate limit, pagination\n│   │   ├── knowledge_service.py  # 15 pre-seeded offline responses\n│   │   ├── sms_service.py        # HTTP client → SMS Gateway\n│   │   └── broadcast_service.py  # Daily tip broadcast (uses USSD_SHORTCODE)\n│   ├── routes/\n│   │   ├── ussd.py               # POST /ussd + POST /simulate\n│   │   ├── admin.py              # /admin/* + HTML dashboard\n│   │   └── cron.py               # POST /cron/daily-tips\n│   └── data/\n│       ├── emergency.py          # Emergency numbers (replace for your country)\n│       └── services.py           # Local services directory (replace for your region)\n│\n├── sms_gateway/                  # SMS Gateway microservice (port 8001)\n│   ├── main.py                   # FastAPI app\n│   ├── config.py                 # SMS_GW_ prefixed settings\n│   ├── schemas.py                # SMSRequest · SMSResponse · BulkSMS*\n│   ├── routes/sms.py             # POST /sms/send · /sms/send-bulk · GET /health\n│   └── services/\n│       ├── jasmin_client.py      # Jasmin HTTP API + GSM-7/UCS-2 auto-detect\n│       └── routing.py            # Dialing prefix → connector (100+ countries)\n│\n├── simulator/cli_sim.py          # Terminal USSD simulator\n├── tests/\n│   ├── conftest.py               # Shared fixtures\n│   ├── test_ussd_menu.py         # 30 menu tests\n│   └── test_admin_routes.py      # 15 admin route tests\n├── alembic/versions/             # 4 DB migrations\n├── docker-compose.yml            # db · redis · rabbitmq · jasmin · sms-gateway · app\n├── Dockerfile\n└── requirements.txt\n```\n\n---\n\n## Cost Model\n\n| Scenario | Cost |\n|---|---|\n| Pre-defined topic (any category) | **$0** — knowledge cache |\n| Same free question within 24 h | **$0** — Redis response cache |\n| New free-form question (Groq) | **\u003c $0.0001** |\n| SMS via Jasmin (direct SMPP) | **~$0** — direct operator rate |\n| **Blended average per interaction** | **\u003c $0.00005** |\n\n---\n\n## License\n\nMIT — free to use, modify, and deploy.\n\n---\n\n*Built to make AI accessible to everyone, everywhere.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanziosee%2Fussd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmanziosee%2Fussd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanziosee%2Fussd/lists"}