Repository navigation
Home
A guided intake in a chat window, a 28-question wellbeing screen, and a matching engine that gets better every time a counselor turns out to be the right choice. All of it running inside one Python process on a Bale messenger bot — no LLM API, no cloud inference, no per-message cost.
This project is a counselor-matching assistant for the "خانواده نیکروان" family counseling center, delivered as a bot on the Bale messenger. A visitor lands in a chat window, answers a short guided intake (name, phone, age, gender, the problem they want help with, therapy history, what they expect from counseling, preferred counselor gender, preferred branch), optionally takes the GHQ-28 wellbeing screening, and receives a ranked shortlist of two counselors with a "why this match" explanation and a direct link to their booking page.
It is for two audiences at once: the person seeking help — who gets a fast, structured, private way to find the right counselor instead of phone tag — and the clinic's staff — who run the whole thing from inside the same bot: statistics, Excel export and import, mass announcements, a scheduled availability crawler, and the feedback loop that trains the matcher.
The unusual part, and the reason the code looks the way it does: the "AI" is not a language model. It is a local, deterministic engine — clinical concept extraction from free text, fuzzy name matching, age-bound parsing, weighted scoring, and a feedback table that re-weights future matches. Nothing a user types is sent to a third-party AI service, and the bot keeps working without an internet-hosted model, an API key, or a per-request bill.
| Page | What you will find there |
|---|---|
| Architecture | The layered design (Bale → conversation → AI engine → storage), the end-to-end data flow of a single session, a component diagram, and the two-pass ranking algorithm. |
| Matching Engine | Deep dive into SpiralMatchEngine: the public API, hard filters, weighted scoring, concept extraction, name normalization, age-bound parsing, and the feedback-learning loop. |
| GHQ-28 Screening | What the 28-question screen measures, the four subscales, how it is asked and scored, how the score is interpreted, how it feeds ranking, and the ethical boundaries. |
| Admin Panel | Every admin command as reference tables: the session model, the Excel export shape, the broadcast confirmation flow, the counselors upload with hot-reload, and the learning commands. |
| Setup and Deployment | Clone → venv → pip install → .env → run. Every environment variable explained, plus a production checklist and the data files to back up. |
| Troubleshooting | Symptom → cause → fix table for the problems that actually happen: bad tokens, missing files, locked databases, crawls that return nothing, names that won't match. |
| Contributing | Dev setup, the code conventions the repo observes, the data pipeline, how to add a synonym or a screening question safely, and what a good PR looks like. |
| Component | What it does |
|---|---|
SpiralMatchEngine |
Two-pass ranking: hard filters (gender, branch, age bounds, legal-specialty guard) followed by weighted scoring over topic fit, clinical-concept match, ability, and live free-appointment count. |
| Concept extraction | Maps free-text topics and expectations to clinical concepts through an expanded synonym dictionary with Persian/Arabic variants (e.g. والد_فرزند, زوج_ازدواج, پزشکی_روانپزشکی). |
| Name normalization | Fuzzy counselor-name matching across spacing, Arabic/Persian letter variants (ي/ی, ك/ک), titles (آقای, دکتر), and one-character typos — so a name typed by an admin or scraped from a booking page still resolves. |
| Clinical triage | GHQ-28 screening (28 questions) scored across four subscales, interpreted into risk bands, and fed into the ranking as a routing signal. |
| Feedback learning | Every recommendation and every admin rating writes to ai_knowledge.db; positive/negative signals re-weight future matches for that (counselor × concept) pair. |
| Availability awareness | A crawler refreshes open-slot counts every 2 hours so the engine can prefer counselors the user can actually book right now. |
| Language | Conversation UI in Persian (RTL); all code, comments, commit messages and documentation in English. |
| License | MIT (© 2026 Mohammad Hasan Kaman). |
| Runtime | Python 3.9+ per the README — in practice 3.10+, because the code uses PEP 604 X | None annotations without from __future__ import annotations. |
| AI service required |
None. No OpenAI/Anthropic/local-LLM dependency; requirements.txt contains no model runtime. |
| Does user data leave the machine? |
No AI vendor sees it. The only outbound traffic is the Bale bot API (https://tapi.bale.ai/bot) and the crawler's read of the center's own public team page (https://nikravan.org/team/). Data lives in local SQLite files. |
| Messaging platform | Bale, driven by python-telegram-bot==22.8 pointed at https://tapi.bale.ai/bot. |
| Entry point |
python main.py — starts polling, the APScheduler crawl job, and the initial background crawl. |
main.py Bot entry point: conversation flows, admin handlers, scheduler
internal_ai_engine.py SpiralMatchEngine — matching, triage, feedback learning
ghq_analyzer.py GHQ-28 questionnaire, scoring and interpretation
user_db.py SQLite storage for users and request history
admin_tools.py Excel export and broadcast helpers
crawler.py Appointment availability scraper (scheduled)
config.py Paths, scheduler settings, env-based credentials
excel_to_json.py Excel roster → consultants_profile_full.json
build_clean_mapping.py Builds the name → profile-URL mapping
extract_links_from_html.py Extracts team profile links from saved HTML
load_clean.py Legacy loader (not runnable as it stands — see Contributing)
mapping.json Counselor name → profile URL map
docs/ hero.png, engine.png, conversation.png — project screenshots
references/ Saved copy of the public team page (crawler input sample)
All 12 conversation states, in order
NAME → PHONE → GENDER → AGE → TOPIC → PREV_THERAPY → PREV_DETAIL → EXPECTATION → PREFERRED_GENDER → BRANCH → ASK_GHQ → GHQ_QUESTION
The first ten are the intake; the last two are the optional screening. PREV_DETAIL is only
entered when the user says they have been in therapy before. Every state accepts the
شروع مجدد button, the word شروع, or /start as a way out.
-
Greeting —
/start(or the "شروع مجدد" button) opens the intake and clears any half-finished session. -
Intake (10 steps) — name → phone → gender → age → topic → previous therapy → (details) → expectation → preferred counselor gender → branch. The phone must be 11 digits starting with
09; if that number is already registered under a different name, the bot refuses to continue, to keep two people's records apart. - Welcome back — after the phone number is verified, a returning visitor (from the second request onward, matched on the phone) gets a "this is my Nth visit" message before the intake continues.
- GHQ-28 offer — the bot explicitly says the screening is optional and asks "بله / خیر". Answering "خیر" skips straight to matching.
- 28 questions — one question per message, with a numbered reply keyboard; the answer is stored as the 0-based option index.
- Scoring — four subscale sums plus a total, interpreted into severity bands and shown to the user with a plain-language message.
-
Matching —
SpiralMatchEngine.match()runs hard filters, then weighted scoring, then returns the top 2 counselors. -
Result card — each recommendation shows the counselor's name, the reason for the match, and a booking link resolved from
mapping.json. -
Write-back — the request is stored in
user_records.db, and a positive learning signal is recorded for each recommended counselor against the clinical concept detected from the topic. -
Learn — admins rate outcomes with
/feedback; the weight for that (counselor × concept) pair moves, and the next match reflects it.
Important
This bot supports — it does not replace — professional care.
- The GHQ-28 output is a screening signal, not a medical diagnosis. A score says "this person reports more distress on these four areas"; it is not a clinical assessment, and no diagnosis should ever be inferred from it.
- Counselor matching is advisory. The engine ranks candidates by documented rules over structured data; the user makes the final choice, and the clinic's own intake process remains authoritative.
- A high GHQ score is used for routing — pushing psychiatry-capable counselors higher in the list — and never for automated clinical decisions.
- The recorded responses are sensitive health-adjacent data stored in local SQLite files. Treat them accordingly: never commit them, and restrict access.
- New to the project? Start with Setup and Deployment — it is a ten-minute path from a fresh clone to a running bot.
- Want to understand the code? Architecture gives the shape, and Matching Engine gives the details.
- Want to run the clinic side of it? Admin Panel.
- Something is broken? Troubleshooting.