2fb86a435e
ASP.NET Core 10 Razor Pages + PostgreSQL/EF Core. RTL Persian, Jalali dates, self-hosted Vazirmatn, teal/coral brand. Features: - Shift listings: browse/filter (city, district, role, type, pay), weekly Jalali calendar, detail + interest handoff, near-me distance sort - Hiring (استخدام) listings with employment type + salary range - Pattern-engine recommendations + anonymous interest tracking (visitor cookie) - Heuristic Persian listing-parser + admin queue (raw channel post → shift/job) - Phone-OTP cookie auth + visitor-history linking + profile Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
7.3 KiB
Markdown
146 lines
7.3 KiB
Markdown
# JobsMedical — Medical Shift Marketplace (Iran)
|
|
|
|
> A niche platform connecting **GP doctors** with **hospitals & clinics** that have open shifts.
|
|
> Replaces the chaotic hunt across Bale / Telegram channels / Divar with one structured,
|
|
> filterable view organized by **hospital, location, and weekly shift calendar**.
|
|
|
|
Date: 2026-06-02 · Market: Iran (Persian/Farsi, RTL) · Revenue: TBD
|
|
|
|
---
|
|
|
|
## 1. The problem & the bet
|
|
|
|
**Today:** A GP doctor wanting locum/extra shifts scrolls dozens of Telegram/Bale channels and
|
|
Divar posts. Listings are unstructured text, no filters, no calendar, no trust signals, expire
|
|
silently. Hospitals broadcast to the same noisy channels and get random DMs.
|
|
|
|
**Our bet:** The value isn't *more* listings — it's **structure + filtering + trust**.
|
|
"Show me open GP shifts, in Tehran, this week, paid X" should be one screen, not 40 channels.
|
|
|
|
**Why a marketplace is hard (and our answer):**
|
|
- *Cold-start problem* — empty marketplace helps no one. We solve supply first via the **mixed
|
|
strategy**: (1) admin posts manually + (2) auto-aggregate from channels → so the site looks
|
|
full from day one, then (3) convert hospitals to self-serve posting over time.
|
|
|
|
---
|
|
|
|
## 2. The two sides
|
|
|
|
| | Doctors (supply of labor) | Hospitals / Clinics (demand) |
|
|
|---|---|---|
|
|
| Who | GPs (پزشک عمومی) seeking shifts | Hospitals, clinics, درمانگاهها |
|
|
| Want | Find shifts fast: by city, date, pay | Fill open shifts with vetted doctors |
|
|
| Onboard via | Phone OTP signup, build profile | Phase 2 self-serve; phase 1 we post for them |
|
|
| Trust signal | نظام پزشکی (medical license) number | Verified facility badge |
|
|
|
|
---
|
|
|
|
## 3. MVP scope (Phase 1 — "the structured board")
|
|
|
|
Goal: a doctor can **find and express interest in a relevant shift in under 60 seconds**.
|
|
|
|
**Must have:**
|
|
1. **Shift browse + filter** — by city, hospital, date range, specialty, shift type (day/night), pay.
|
|
2. **Weekly calendar view** per hospital — the signature feature. Jalali (Shamsi) week grid showing
|
|
open shifts per day.
|
|
3. **Shift detail page** — hospital, location (map pin), date, hours, pay, requirements, how to apply.
|
|
4. **Doctor signup/login** — phone OTP (standard in Iran), basic profile (name, license, city, specialty).
|
|
5. **Express interest / apply** — doctor taps "interested" → handoff to facility contact (call/Bale/
|
|
in-app message). Track applications.
|
|
6. **Admin panel** — we add/edit shifts manually, and review/normalize aggregated listings before
|
|
they go live.
|
|
7. **Aggregation ingest (basic)** — pull text from selected Telegram/Bale channels into a "raw
|
|
listings" queue for admin to normalize. (Start semi-manual; full automation later.)
|
|
|
|
**Explicitly NOT in MVP** (Phase 2+):
|
|
- Hospital self-serve posting & dashboards
|
|
- Payments / monetization
|
|
- Ratings & reviews
|
|
- Automated matching/recommendations, push alerts
|
|
- Full unattended scraping pipeline
|
|
|
|
---
|
|
|
|
## 4. Data model (core entities)
|
|
|
|
```
|
|
User id, phone, full_name, role(doctor|facility_admin|admin), created_at
|
|
DoctorProfile user_id, license_no(نظام پزشکی), specialty, city, years_exp, bio, verified
|
|
Facility id, name, type(hospital|clinic|درمانگاه), city, address, lat,lng,
|
|
phone, bale_id, verified, owner_user_id(nullable, phase 2)
|
|
Shift id, facility_id, date(jalali stored as ISO), start_time, end_time,
|
|
specialty_required, shift_type(day|night|on_call), pay_amount, pay_type,
|
|
description, status(open|filled|expired|cancelled),
|
|
source(direct|admin|aggregated), source_url, created_at
|
|
Application id, shift_id, doctor_id, status(interested|accepted|rejected|withdrawn),
|
|
message, created_at
|
|
RawListing id, source_channel, raw_text, parsed_json, status(new|normalized|discarded),
|
|
linked_shift_id, fetched_at ← aggregation staging area
|
|
City id, name, province ← canonical city list for filters
|
|
```
|
|
|
|
Key decisions:
|
|
- **Dates stored as ISO (UTC) in DB, displayed as Jalali** in the UI. Never store Jalali strings.
|
|
- A `Shift` can originate from a `RawListing` (aggregation) or be created directly (admin/facility).
|
|
- Phone is the unique identity; OTP login, no passwords.
|
|
|
|
---
|
|
|
|
## 5. Tech architecture
|
|
|
|
Constraints that drive choices: **Iran hosting** (many global SaaS block Iranian IPs/payments →
|
|
prefer self-hostable, open-source), **SEO matters** (doctors will find shifts via Google → need
|
|
server-rendered pages), **RTL + Jalali**, **phone OTP** via Iranian SMS providers.
|
|
|
|
**Recommended stack:**
|
|
- **Frontend + backend:** Next.js (App Router) + TypeScript — SSR for SEO, one codebase, great RTL support.
|
|
- **UI:** Tailwind CSS (RTL mode) + a Persian font (Vazirmatn). Jalali via `dayjs-jalali` or `jalaali-js`.
|
|
- **DB:** PostgreSQL + Prisma ORM.
|
|
- **Auth:** Phone OTP. SMS via **Kavenegar** or **SMS.ir** (Iranian providers). Sessions via JWT/cookies.
|
|
- **Maps:** Neshan or Balad map tiles (Iranian, work without VPN) instead of Google Maps.
|
|
- **Aggregation:** A small worker (Node) using Telegram Bot API / Telethon-style reader for Bale,
|
|
writing into `RawListing`. Runs as a separate scheduled job.
|
|
- **Hosting / CI:** Self-hosted on an Iranian VPS, deployed via your **Gitea + Nexus** pipeline
|
|
(soroush ci/cd method). Docker Compose: web + postgres + worker.
|
|
|
|
> If you'd rather build in **.NET** (you have the Nexus/.NET pipeline set up), the equivalent is
|
|
> ASP.NET Core + Blazor/Razor + EF Core + Postgres. Same architecture, different language. ← confirm.
|
|
|
|
**Why not Google Maps / Vercel / Firebase / Stripe:** all routinely blocked or unusable from Iran.
|
|
Everything above is self-hostable or has an Iranian equivalent.
|
|
|
|
---
|
|
|
|
## 6. Key screens
|
|
|
|
1. **Home** — hero + search (city, specialty, date) + "shifts this week" + featured hospitals. SEO landing.
|
|
2. **Shift listing** — filter sidebar + result cards + map toggle.
|
|
3. **Weekly calendar** (per hospital or per city) — Shamsi week grid, open shifts as chips.
|
|
4. **Shift detail** — all info + map + "I'm interested" CTA.
|
|
5. **Doctor onboarding** — phone OTP → profile form.
|
|
6. **Doctor dashboard** — my applications, saved shifts.
|
|
7. **Admin** — shift CRUD, raw-listing normalization queue, facility management.
|
|
|
|
---
|
|
|
|
## 7. Phased roadmap
|
|
|
|
- **Phase 0 — Foundation:** repo, Docker, Postgres+Prisma schema, RTL+Jalali shell, OTP auth.
|
|
- **Phase 1 — MVP board:** browse/filter, calendar, shift detail, doctor signup, apply, admin CRUD,
|
|
basic raw-listing ingest. → *launch to a few hospitals + a doctor Telegram group.*
|
|
- **Phase 2 — Self-serve + trust:** hospital posting dashboards, facility/doctor verification,
|
|
in-app messaging, saved searches + alerts (via Bale bot).
|
|
- **Phase 3 — Monetization:** pick model (likely **hospitals pay to post** or **commission per
|
|
filled shift** — decide after we see what converts), Iranian payment gateway (Zarinpal/IDPay).
|
|
- **Phase 4 — Scale:** full automated aggregation, recommendations, expand beyond GPs to specialists.
|
|
|
|
---
|
|
|
|
## 8. Open questions to revisit
|
|
|
|
- Monetization model (deferred — validate demand first).
|
|
- Which specific Telegram/Bale channels to aggregate first?
|
|
- Start single-city (Tehran?) to concentrate liquidity, or national from day one?
|
|
- Build language: **TypeScript/Next.js** (recommended) vs **.NET** (your existing pipeline)?
|
|
```
|