الوثيقة التقنية Zezo Smart Transit Platform (ZSTP)

📊 2 مشاهدة

Zezo Smart Transit Platform (ZSTP)

تحليل شامل وتحويل الفكرة إلى منصة Production

أولًا: تحليل المحتوى الكامل بدقة

1. طبيعة الملف

الملف ليس وثيقة تقنية نهائية، بل محادثة تخطيطية تدريجية على منصة “Zezo Ai Clude” تحوّلت من فكرة بسيطة إلى تصور منظومي متكامل. مرّ المحتوى بالمراحل التالية:

المرحلةالمحتوى
انطلاق الفكرةمشكلة انتظار المواطن دون معرفة موعد وصول الحافلة
تصور أوليتطبيق موبايل: خريطة + GPS + ETA + تنبيهات
توسّعمنظومة متكاملة: مواطن + محطة + إدارة + حافلة
سياق جغرافيحافلات جازان (JAZAN BUS) كنموذج
تحديد الفجوةالمواطن لا يعرف الوجهة المحلية ولا موعد الوصول/المغادرة ولا أي حافلة تذهب لمدينة أخرى
الحلثلاث واجهات + ETA + تكامل مع أنظمة قائمة
المنهجيةتجربة محدودة ثم توسع
صياغة رسميةمقترح للإدارة

2. المشكلة الجوهرية (كما كشفها الملف بدقة)

المشكلة ليست “نقل” فقط، بل فجوة معلوماتية في نقطة اتخاذ القرار:

البُعدالمشكلة
المعلوماتيلا يعرف المواطن وجهة الحافلة قبل ركوبها
الزمنيلا يعرف موعد الوصول ولا المغادرة
المكانيلا يعرف موقع الحافلة الحالي
التخطيطيلا يعرف أي حافلة تذهب لمدينة أخرى
التشغيليالشاشات الداخلية لا تخدم المنتظر في المحطة
الثقةقد تُعرض أوقات غير مؤكدة دون توضيح

3. خريطة الفجوات المعلوماتية (المفتاح التحليلي)

البياناتالإدارةداخل الحافلةالمواطن في المحطة
رقم الخطغالبًا نعمنعم❌
الوجهة النهائيةغالبًا نعمجزئيًا❌
موقع الحافلة اللحظيغير مؤكدلا❌
وقت الوصول المتوقعغير مؤكدجزئيًا❌
موعد المغادرة (بين المدن)غالبًا نعملا❌
المحطات الوسيطةغالبًا نعمجزئيًا❌
حالة الرحلةغير مؤكدجزئيًا❌

الاستنتاج: المشكلة ليست غياب البيانات كليًا، بل غياب قناة عرض موجهة للمواطن. الحل قد لا يكون بناء نظام من الصفر، بل طبقة عرض (Presentation Layer) فوق بيانات قائمة.

4. إجابات المستخدم الحاسمة في المحادثة

  • الحافلات لديها شاشات داخلية تعرض المحطة والخط وحالة الوصول.
  • لكن المواطن لا يعرف الوجهات المحلية، موعد الوصول/المغادرة، ولا أي باص يذهب لمدينة أخرى.
  • إجابته عن نظام التتبع: “1 و3 و4” = يشك في وجود تتبع + يعرف بوجود نظام متابعة مركزي لكن لا يصل للمواطن + لا يملك تأكيدًا تقنيًا.
  • قرار حاسم: لا نفترض وجود التتبع أو غيابه — التحقق أول خطوة.

ثانيًا: إعادة كتابة الحل كمنصة Production

سأحوّل التصور إلى معمارية إنتاجية قابلة للتنفيذ بمستوى هندسي حقيقي، مع فصل ما هو مؤكد عمّا هو افتراض يجب التحقق منه.

🏗️ اسم المنصة

Zezo Smart Transit Platform (ZSTP)

1. المبادئ التصميمية الحاكمة (Non-Negotiables)

المبدأالتطبيق الهندسي
الصدق في البياناتلا عرض ETA لحظي دون تتبع فعلي — ثلاث حالات صريحة (🟢🟡🔴)
العدالة الرقميةشاشة محطة + QR لمن لا يملك هاتفًا ذكيًا
عدم افتراض الشراءDiscovery phase قبل أي اقتناء أجهزة
التدرجخط واحد → قياس → توسع
البساطة للمواطنسؤال واحد: “إلى أين؟” — بحث بالوجهة لا برقم الخط

2. المعمارية التقنية (Production Architecture)

text

┌─────────────────────────────────────────────────────────────┐
│                    طبقة العرض (Presentation)                 │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ تطبيق المواطن │  شاشة المحطة  │  QR Web Page │  لوحة الإدارة   │
│ (PWA/Flutter) │ (Kiosk Web)  │ (Responsive) │  (React Admin)  │
└──────┬───────┴──────┬───────┴──────┬───────┴───────┬────────┘
       │              │              │               │
       └──────────────┴──────┬───────┴───────────────┘
                             │  HTTPS / WebSocket
                    ┌────────▼────────┐
                    │   API Gateway   │
                    │  (Rate Limit,   │
                    │   Auth, Cache)  │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
┌───────▼──────┐   ┌─────────▼────────┐  ┌───────▼────────┐
│ Tracking Svc │   │  ETA Engine      │  │  Trip/Route    │
│ (Ingest GPS) │   │  (Prediction)    │  │  Service       │
└───────┬──────┘   └─────────┬────────┘  └───────┬────────┘
        │                    │                    │
        └────────────────────┼────────────────────┘
                             │
                    ┌────────▼────────┐
                    │  Message Bus    │
                    │ (Kafka/Redis)   │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
┌───────▼──────┐   ┌─────────▼────────┐  ┌───────▼────────┐
│ Time-Series  │   │  Relational DB   │  │  Cache Layer   │
│ DB (GPS)     │   │ (Routes/Stops)   │  │  (Redis)       │
│ TimescaleDB  │   │  PostgreSQL      │  │                │
└──────────────┘   └──────────────────┘  └────────────────┘

المكونات الأساسية:

أ. Tracking Ingestion Service

  • يستقبل بيانات GPS كل 5–10 ثوانٍ (MQTT/HTTP).
  • يتحقق من صحة البيانات (GPS drift, timestamp sanity).
  • يخزن في TimescaleDB للتاريخ + Redis للحالة اللحظية.
  • كشف انقطاع الإشارة: إذا لم تصل بيانات خلال 30 ثانية → الحالة تتحول لـ 🔴.

ب. ETA Engine (المحرك الحاسم)

  • المستوى 1 (Basic): المسافة المتبقية ÷ متوسط السرعة على الخط.
  • المستوى 2 (Traffic-aware): تكامل مع بيانات مرور (Google/HERE) عند توفرها.
  • المستوى 3 (Historical): نموذج تعلّم آلي بسيط (XGBoost) على البيانات التاريخية لكل خط/ساعة.
  • مخرجات ثلاثية إلزامية: LIVE / SCHEDULED / UNAVAILABLE مع confidence_score.

ج. Trip/Route Service

  • قاعدة بيانات الخطوط، المحطات، المسارات، المواعيد.
  • يميّز صراحة: LOCAL vs INTERCITY.
  • يحفظ scheduled_departure و scheduled_arrival منفصلين.

د. Notification Service

  • Push (FCM/APNs) + SMS fallback.
  • قواعد: “قرب الحافلة” (500م أو دقيقتان)، “تأخير كبير” (>5 دقائق عن المتوقع)، “تغيير مسار”.

هـ. Admin Control Room

  • خريطة حية لكل الأسطول.
  • تنبيهات: أجهزة منقطعة، رحلات متأخرة، حافلات لم تغادر.
  • تقارير الالتزام بالمواعيد (OTP).

3. نموذج البيانات الأساسي (Schema مبسط)

sql

-- Routes
routes(id, code, name_ar, name_en, type[LOCAL|INTERCITY], 
       origin_city, destination_city, is_active)

-- Stops
stops(id, name_ar, lat, lng, city, is_terminal)

-- Route_Stops (ترتيب المحطات)
route_stops(route_id, stop_id, sequence, avg_travel_min_from_prev)

-- Trips (رحلة فعلية)
trips(id, route_id, vehicle_id, scheduled_departure, 
      scheduled_arrival, actual_departure, actual_arrival, status)

-- Vehicles
vehicles(id, plate, route_id_current, gps_device_id, 
         last_seen_at, status)

-- GPS_Readings (TimescaleDB hypertable)
gps_readings(vehicle_id, trip_id, ts, lat, lng, speed_kmh, heading)

4. واجهة المواطن — تصميم تنفيذي

شاشة واحدة (Single-Page) + تنقل سفلي بثلاث تبويبات:

التبويبالمحتوى
الخريطةحافلات متحركة + عند الضغط: رقم الخط، الوجهة، المحطات القادمة، ETA بحالته (🟢🟡🔴)
رحلاتيخطوط محفوظة + تحديثات فورية + زر “نبّهني”
التنبيهاتتغيير مسار، تأخير، اقتراب الحافلة

تدفق البحث:

text

"إلى أين؟" → بحث بالاسم → قائمة خطوط تخدم الوجهة 
→ اختيار → خريطة + ETA + زر تنبيه

عرض ETA — المنطق الإلزامي:

text

if (data_age < 30s && gps_available):
    show "باقي X دقائق" 🟢 + confidence
elif (scheduled_only):
    show "مجدول HH:MM" 🟡
else:
    show "غير مؤكد — آخر تحديث HH:MM" 🔴

5. خطة التنفيذ — 90 يومًا (Production-Ready Roadmap)

المرحلة 0: Discovery (أسبوعان) — بوابة قرار

المهمةالمخرج
مقابلة الجهة المشغلةفهم البنية الحالية
جرد أجهزة GPSهل موجودة؟ أي بروتوكول؟
فحص API/قاعدة بياناتهل هناك تكامل ممكن؟
توثيق الخطوط والمحطاتمصدر الحقيقة

قرار: إذا وُجد GPS + API → مسار “طبقة عرض”. إذا لا → مسار “تجهيز + بناء”.

المرحلة 1: MVP على خط واحد (4 أسابيع)

  • Tracking Ingestion + ETA Basic.
  • صفحة ويب للركاب (PWA) — خط واحد + 5 محطات.
  • لوحة إدارة بسيطة (خريطة + تنبيهات).
  • مقياس النجاح: دقة ETA ضمن ±2 دقيقة في 80% من الحالات.

المرحلة 2: التوسع الأفقي (4 أسابيع)

  • إضافة خطوط محلية (5–10 خطوط).
  • شاشة محطة + QR.
  • إشعارات Push.
  • ETA المستوى 2 (Traffic-aware).

المرحلة 3: النقل بين المدن + التخطيط (4 أسابيع)

  • فصل INTERCITY في الواجهة.
  • مواعيد المغادرة/الوصول.
  • ميزة “كيف أصل؟” مع التبديلات.
  • ETA المستوى 3 (Historical ML).

إجمالي: 14 أسبوعًا → منصة production قابلة للتشغيل.


6. المخاطر والضوابط

المخاطرةالضابط
لا يوجد GPS فعليDiscovery تكشف مبكرًا → خطة تجهيز بديلة
ETA غير دقيقثلاث حالات صريحة + confidence score
انقطاع اتصال الحافلةكشف >30s → تحويل لـ 🔴
بيانات قديمة تُعرض كلحظيةTTL إلزامي في الـ cache + timestamp مرئي
حماية البياناتموافقة المستخدم + إخفاء هوية السائق + RBAC
مقاومة التغييرتجربة صغيرة + قياس + إثبات قيمة

7. مقترح الإدارة (نص نهائي مصقول)

الموضوع: مبادرة تطوير منظومة المعلومات الذكية لركاب حافلات جازان

المشكلة: عدم معرفة الراكب بوجهة الحافلة وموعد وصولها ومغادرتها قبل الركوب، ما يسبب حيرة وانتظارًا غير ضروري.

الحل: ثلاث واجهات مترابطة (مواطن، محطة، إدارة) + محرك ETA شفاف بثلاث حالات يقين + تكامل مع الأنظمة القائمة بعد التحقق منها.

المنهجية: Discovery → تجربة على خط واحد → قياس الدقة → توسع تدريجي خلال 90 يومًا.

المخرج المتوقع: تقليل الحيرة، رفع الثقة، تحسين تجربة النقل العام، دون افتراض شراء أنظمة قد تكون موجودة.


8. الخلاصة التنفيذية

ملف “مواقعي” يوثّق رحلة تحويل شكوى يومية إلى معمارية حل من ثلاث طبقات:

  • بيانات: GPS + خطوط + محطات + خادم مركزي.
  • ذكاء: ETA + حالات يقين + تنبيهات.
  • عرض: تطبيق + شاشة/QR + لوحة إدارة.

وقيمته الحقيقية أنه لا يبدأ من الصفر، بل من التحقق، ثم تجربة صغيرة قابلة للقياس، ثم توسع — وهذا ما يجعل تحويله إلى منصة production ممكنًا خلال 90 يومًا، بشرط إنجاز مرحلة Discovery أولًا لحسم مسار التنفيذ (طبقة عرض أم تجهيز كامل).

الخطوة التالية المقترحة: إعداد وثيقة Discovery من صفحتين تُقدَّم للجهة المشغلة، تتضمن الأسئلة الأربعة الحاسمة (GPS؟ غرفة عمليات؟ قاعدة بيانات؟ API؟) — لأن الإجابة عليها تحدد المعمارية النهائية بدقة.

توسيع التحليل: من الفكرة إلى منصة Production كاملة

سأكمل بما لم يُغطَّ بعد في التحليل السابق: التفاصيل الهندسية العميقة، العقود البرمجية، خطة البيانات، الأمان، التشغيل، والقياس — أي كل ما يفصل “تصورًا” عن “منصة إنتاجية حقيقية”.


أولًا: نموذج البيانات الكامل (Production-Grade Schema)

ما قدّمته سابقًا كان مبسطًا. هذا هو المخطط الكامل القابل للتنفيذ:

sql

-- ============ المراجع الجغرافية ============
CREATE TABLE cities (
    id UUID PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100),
    region VARCHAR(100)
);

CREATE TABLE stops (
    id UUID PRIMARY KEY,
    city_id UUID REFERENCES cities(id),
    name_ar VARCHAR(200) NOT NULL,
    code VARCHAR(20) UNIQUE,           -- كود المحطة للمسح/QR
    lat DECIMAL(10,7) NOT NULL,
    lng DECIMAL(10,7) NOT NULL,
    is_terminal BOOLEAN DEFAULT FALSE,
    has_display BOOLEAN DEFAULT FALSE, -- هل لديها شاشة؟
    qr_enabled BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- ============ الخطوط والمسارات ============
CREATE TYPE route_type AS ENUM ('LOCAL', 'INTERCITY');

CREATE TABLE routes (
    id UUID PRIMARY KEY,
    code VARCHAR(20) NOT NULL,          -- 07A
    name_ar VARCHAR(200) NOT NULL,
    type route_type NOT NULL,
    origin_city_id UUID REFERENCES cities(id),
    destination_city_id UUID REFERENCES cities(id),
    is_active BOOLEAN DEFAULT TRUE,
    color_hex VARCHAR(7),               -- لون الخط على الخريطة
    UNIQUE(code, type)
);

-- ترتيب المحطات على الخط (المسار المعتمد)
CREATE TABLE route_stops (
    route_id UUID REFERENCES routes(id) ON DELETE CASCADE,
    stop_id UUID REFERENCES stops(id),
    sequence INT NOT NULL,              -- 1,2,3...
    distance_from_prev_m INT,           -- متر
    avg_travel_sec_from_prev INT,       -- متوسط زمني تاريخي
    PRIMARY KEY (route_id, sequence)
);

-- ============ المركبات ============
CREATE TYPE vehicle_status AS ENUM ('ACTIVE','IDLE','MAINTENANCE','OFFLINE');

CREATE TABLE vehicles (
    id UUID PRIMARY KEY,
    plate VARCHAR(20) UNIQUE NOT NULL,
    gps_device_id VARCHAR(50) UNIQUE,
    gps_protocol VARCHAR(20),           -- MQTT/HTTP/GT06...
    capacity INT,
    status vehicle_status DEFAULT 'IDLE',
    last_seen_at TIMESTAMPTZ,
    last_lat DECIMAL(10,7),
    last_lng DECIMAL(10,7),
    last_speed_kmh DECIMAL(5,2),
    last_heading INT
);

-- ============ الرحلات (Trips) ============
CREATE TYPE trip_status AS ENUM (
    'SCHEDULED','BOARDING','IN_TRANSIT','ARRIVED','CANCELLED','DELAYED'
);

CREATE TABLE trips (
    id UUID PRIMARY KEY,
    route_id UUID REFERENCES routes(id),
    vehicle_id UUID REFERENCES vehicles(id),
    driver_id UUID,                     -- مرجع لنظام السائقين
    scheduled_departure TIMESTAMPTZ NOT NULL,
    scheduled_arrival TIMESTAMPTZ NOT NULL,
    actual_departure TIMESTAMPTZ,
    actual_arrival TIMESTAMPTZ,
    status trip_status DEFAULT 'SCHEDULED',
    direction SMALLINT DEFAULT 1,       -- 1=ذهاب، 2=عودة (للمسار الدائري)
    created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_trips_route_time ON trips(route_id, scheduled_departure);

-- ============ قراءات GPS (TimescaleDB Hypertable) ============
CREATE TABLE gps_readings (
    time TIMESTAMPTZ NOT NULL,
    vehicle_id UUID NOT NULL,
    trip_id UUID,
    lat DECIMAL(10,7) NOT NULL,
    lng DECIMAL(10,7) NOT NULL,
    speed_kmh DECIMAL(5,2),
    heading INT,
    accuracy_m DECIMAL(6,2),
    odometer_km DECIMAL(10,2),
    ignition BOOLEAN,
    source VARCHAR(20) DEFAULT 'GPS'
);
SELECT create_hypertable('gps_readings', 'time');
CREATE INDEX idx_gps_vehicle_time ON gps_readings(vehicle_id, time DESC);

-- ============ ETA Snapshots (للتقييم والتعلّم) ============
CREATE TABLE eta_snapshots (
    id BIGSERIAL PRIMARY KEY,
    trip_id UUID,
    stop_id UUID,
    computed_at TIMESTAMPTZ NOT NULL,
    eta_seconds INT,
    confidence DECIMAL(3,2),           -- 0.00 - 1.00
    data_state VARCHAR(12),            -- LIVE / SCHEDULED / UNAVAILABLE
    method VARCHAR(20),                -- BASIC/TRAFFIC/ML
    actual_arrival_at TIMESTAMPTZ,     -- يُملأ لاحقًا للمقارنة
    error_seconds INT                  -- actual - eta
);

-- ============ التنبيهات ============
CREATE TABLE alerts (
    id UUID PRIMARY KEY,
    trip_id UUID,
    type VARCHAR(30),                  -- DELAY/ROUTE_CHANGE/GPS_LOST/ARRIVED
    severity SMALLINT,                 -- 1=info, 2=warn, 3=critical
    message_ar TEXT,
    created_at TIMESTAMPTZ DEFAULT now(),
    resolved_at TIMESTAMPTZ
);

لماذا هذا المخطط؟

  • route_stops.sequence يحل مشكلة “الوجهة قبل الركوب” بترتيب المحطات.
  • trips.direction ضروري للمسارات الدائرية (ذهاب/عودة).
  • eta_snapshots هو كنز القياس: يسمح بحساب دقة ETA تاريخيًا وتحسين النموذج.
  • vehicles.last_seen_at هو أساس كشف 🔴 (انقطاع التتبع).

ثانيًا: العقود البرمجية (API Contracts)

1. واجهة المواطن — GET /api/v1/stops/{stop_id}/arrivals

json

{
  "stop": {
    "id": "uuid",
    "name_ar": "محطة مركز الشامل",
    "lat": 16.889,
    "lng": 42.551,
    "last_updated": "2026-10-20T14:32:10Z"
  },
  "arrivals": [
    {
      "trip_id": "uuid",
      "route_code": "07A",
      "route_type": "LOCAL",
      "destination_ar": "المنطقة الشمالية",
      "vehicle_plate": "أ ب ج 1234",
      "eta": {
        "state": "LIVE",
        "seconds": 420,
        "display_ar": "باقي 7 دقائق",
        "confidence": 0.92,
        "data_age_sec": 8,
        "method": "TRAFFIC"
      },
      "next_stops": [
        {"name_ar": "المحطة 2", "sequence": 4},
        {"name_ar": "المحطة 3", "sequence": 5}
      ]
    },
    {
      "trip_id": "uuid2",
      "route_code": "12B",
      "route_type": "LOCAL",
      "destination_ar": "المنطقة الجنوبية",
      "eta": {
        "state": "SCHEDULED",
        "scheduled_at": "2026-10-20T14:45:00Z",
        "display_ar": "مجدول 2:45 م",
        "confidence": null,
        "data_age_sec": null
      }
    },
    {
      "trip_id": "uuid3",
      "route_code": "204",
      "route_type": "INTERCITY",
      "destination_ar": "أبها",
      "eta": {
        "state": "UNAVAILABLE",
        "display_ar": "غير مؤكد — آخر تحديث 2:28 م",
        "last_known_at": "2026-10-20T14:28:00Z"
      }
    }
  ]
}

القاعدة الذهبية: الحقل eta.state إلزامي في كل استجابة. لا يُسمح بعرض رقم دون حالة. هذا يُجبر الواجهة على الصدق.

2. واجهة الإدارة — GET /api/v1/admin/fleet/live

json

{
  "summary": {
    "total": 42, "active": 35, "idle": 4, "offline": 3,
    "on_time_pct": 88.5, "avg_delay_sec": 95
  },
  "vehicles": [
    {
      "vehicle_id": "uuid",
      "plate": "أ ب ج 1234",
      "trip_id": "uuid",
      "route_code": "07A",
      "status": "IN_TRANSIT",
      "position": {"lat": 16.89, "lng": 42.55, "speed_kmh": 34},
      "gps_age_sec": 6,
      "health": "OK",
      "next_stop": {"name_ar": "المحطة 3", "eta_sec": 120},
      "delay_sec": 45
    }
  ],
  "alerts": [
    {"type": "GPS_LOST", "vehicle_plate": "س ص ع 5678", "since": "2026-10-20T14:20:00Z", "severity": 2},
    {"type": "DELAY", "trip_id": "uuid", "delay_sec": 480, "severity": 2}
  ]
}

3. تدفق GPS — POST /api/v1/ingest/gps (من أجهزة التتبع)

json

{
  "device_id": "GPS-00123",
  "ts": "2026-10-20T14:32:08Z",
  "lat": 16.8891,
  "lng": 42.5512,
  "speed_kmh": 32.5,
  "heading": 145,
  "accuracy_m": 5.0,
  "ignition": true,
  "odometer_km": 12450.3
}

الاستجابة: 202 Accepted + {"next_expected_in_sec": 5} — لإدارة معدل الإرسال.

4. WebSocket للتحديث اللحظي — wss://api/ws/live?stop_id=...

json

{"type": "vehicle_moved", "trip_id": "uuid", "eta_sec": 380, "state": "LIVE"}
{"type": "alert", "trip_id": "uuid", "type": "DELAY", "delta_sec": 120}

ثالثًا: محرك ETA — التفصيل الهندسي الكامل

المعادلة الأساسية (Level 1)

text

remaining_distance = Σ (distance_from_prev) للمحطات المتبقية
                + مسافة من موقع الحافلة لأقرب محطة قادمة
avg_speed = median(speed آخر 5 دقائق)  [مع استبعاد التوقفات < 2 دقيقة]
eta_basic = remaining_distance / max(avg_speed, min_speed_floor)

min_speed_floor = 10 كم/س — لمنع القسمة على صفر عند الازدحام.

المعادلة المتقدمة (Level 2 — Traffic-aware)

text

eta_traffic = eta_basic × traffic_factor
traffic_factor = f(الوقت، الطريق، بيانات المرور الحية)

المعادلة التاريخية (Level 3 — ML)

نموذج XGBoost بميزات:

الميزةالوصف
hour_of_dayالساعة (0–23)
day_of_weekيوم الأسبوع
route_idمعرف الخط (one-hot)
segment_indexالمقطع الحالي على المسار
current_speedالسرعة الحالية
historical_avg_secمتوسط تاريخي للمقطع
is_peakذروة؟

الهدف: تقليل MAE (متوسط الخطأ المطلق) عن 120 ثانية.

حالات البيانات — المنطق الإلزامي

python

def compute_eta_state(vehicle, trip):
    data_age = now() - vehicle.last_seen_at
    
    if data_age > timedelta(seconds=30) or vehicle.gps_device_id is None:
        return {
            "state": "UNAVAILABLE",
            "display_ar": f"غير مؤكد — آخر تحديث {fmt(vehicle.last_seen_at)}",
            "last_known_at": vehicle.last_seen_at
        }
    
    if vehicle.last_speed_kmh is None and trip.status == "SCHEDULED":
        return {
            "state": "SCHEDULED",
            "scheduled_at": trip.scheduled_arrival,
            "display_ar": f"مجدول {fmt(trip.scheduled_arrival)}"
        }
    
    eta = eta_engine.compute(trip, vehicle)
    return {
        "state": "LIVE",
        "seconds": eta.seconds,
        "display_ar": f"باقي {humanize(eta.seconds)}",
        "confidence": eta.confidence,
        "data_age_sec": data_age.total_seconds(),
        "method": eta.method
    }

confidence يُحسب من: عمر البيانات + دقة GPS + تطابق السرعة مع التاريخي. إذا < 0.5 → يُعرض تحذير خفي “تقدير تقريبي”.


رابعًا: الأمان وحماية البيانات (Security Architecture)

1. المصادقة والتفويض (AuthN/AuthZ)

الفئةآلية المصادقةالصلاحيات
المواطنمجهول + JWT خفيف (اختياري)قراءة فقط للبيانات العامة
شاشة المحطةAPI Key ثابت (kiosk)قراءة محطة واحدة
جهاز GPSmTLS + device certificateكتابة قراءات فقط
موظف الإدارةOIDC (SSO) + MFARBAC حسب الدور
مدير النظامOIDC + MFA + IP allowlistكامل

2. RBAC — الأدوار

text

roles:
  - viewer:      قراءة لوحة الإدارة
  - operator:    + حل التنبيهات، إعادة تعيين رحلة
  - supervisor:  + تعديل الرحلات، إدارة الأسطول
  - admin:       + إدارة المستخدمين والإعدادات

3. حماية البيانات الشخصية

  • موقع السائق لا يُعرض للمواطن — فقط موقع المركبة.
  • الاحتفاظ: GPS خام 90 يومًا، مجمّع 2 سنة.
  • إخفاء الهوية: أي تحليل يُجري على vehicle_id لا driver_id.
  • موافقة المستخدم: عند طلب الموقع، موافقة صريحة + إمكانية السحب.
  • GDPR/PDPL: توثيق أساس المعالجة، حق الوصول، حق الحذف.

4. حماية API

  • Rate limiting: 60 طلب/دقيقة للمواطن، 600 للـ kiosk.
  • CORS محدود للنطاقات المعروفة.
  • Input validation صارم (lat ∈ [-90,90], lng ∈ [-180,180], ts ضمن ±5 دقائق).
  • تسجيل كل وصول إداري (audit log).

خامسًا: خطة البيانات (Data Strategy) — من لا شيء إلى نموذج ذكي

المرحلة 0: بيانات أساسية (Discovery)

  • جرد: هل هناك CSV/Excel للخطوط والمحطات؟
  • إن وُجد: استيراد + تنظيف + تحقق من الإحداثيات.
  • إن لم يوجد: جمع ميداني — فريق صغير يزور المحطات ويسجل GPS/الأسماء.

المرحلة 1: بيانات التشغيل (MVP)

  • ربط أجهزة GPS الحالية → تدفق حقيقي.
  • تسجيل actual_arrival لكل رحلة (يدويًا إن لزم) → بناء تاريخ.
  • الحد الأدنى: 2 أسبوع من البيانات لبدء ETA Basic.

المرحلة 2: بناء النموذج (بعد 4–6 أسابيع)

  • حساب error_seconds لكل snapshot.
  • إذا MAE < 180s → تفعيل Level 3 تدريجيًا.
  • بوابة: لا تفعيل ML قبل 5000 snapshot.

سادسًا: التشغيل (Operations & SRE)

1. مؤشرات SLI/SLO

المؤشر (SLI)الهدف (SLO)
توفر API99.5% شهريًا
زمن استجابة p95< 300ms
عمر بيانات GPS p95< 15 ثانية
دقة ETA (MAE)< 120 ثانية
توفر WebSocket99.0%

2. المراقبة (Observability)

  • Metrics: Prometheus — كل خدمة تصدّر /metrics.
  • Logs: structured JSON → Loki.
  • Traces: OpenTelemetry → Tempo/Jaeger.
  • Dashboards: Grafana — لوحة لكل خدمة + لوحة أعمال.
  • Alerting: PagerDuty/Opsgenie — تنبيه عند تجاوز SLO.

3. خطة الاستجابة للحوادث

الحادثالإجراء
انقطاع تدفق GPSتنبيه فوري → حالة 🔴 للمركبات المتأثرة تلقائيًا
تعطل ETA EngineFallback إلى SCHEDULED — لا أرقام كاذبة
تعطل قاعدة البياناتRedis cache يخدم آخر حالة معروفة (stale marker)
هجوم DDoSWAF + rate limit + وضع القراءة فقط

4. النشر (Deployment)

text

CI/CD: GitHub Actions → Build → Test → Stage → Canary → Production
Infra:  Kubernetes (EKS/GKE) أو Docker Compose للبداية
DB:     PostgreSQL (RDS) + TimescaleDB extension
Cache:  Redis (ElastiCache)
Queue:  Redis Streams أو Kafka (حسب الحجم)
CDN:    CloudFront/Cloudflare للـ PWA

البداية: Docker Compose على VPS واحد (خط واحد) → Kubernetes عند التوسع.


سابعًا: القياس والتقييم (Measurement Framework)

1. مؤشرات نجاح المنتج

المؤشركيف يُقاسالهدف بعد 90 يومًا
دقة ETAMAE من eta_snapshots< 120s في 80%
استخدامجلسات فريدة / يوم> 500
رضااستبيان داخل التطبيق (NPS)> 40
تقليل الانتظار المُدركسؤال: “هل عرفت متى تجي الحافلة؟”> 70% نعم
الالتزام بالمواعيد (OTP)رحلات في ±5 دقائق> 85%
تغطية المحطاتمحطات لها شاشة/QR> 50%

2. تجربة A/B (اختياري)

  • مجموعة A: تعرض ETA بحالات ثلاثية.
  • مجموعة B: تعرض رقمًا فقط (الوضع الحالي).
  • المقياس: الثقة (استبيان) + معدل الاستخدام.

3. تقييم ما بعد التجربة (Post-MVP Review)

أسئلة إلزامية قبل التوسع:

  1. هل دقة ETA كافية؟ (نعم/لا + بيانات)
  2. هل التكامل مع الأنظمة القائمة نجح؟
  3. ما التكلفة الفعلية لكل خط؟
  4. هل هناك حواجز تنظيمية؟
  5. هل نوسع أفقيًا (خطوط) أم رأسيًا (ميزات)؟

ثامنًا: الفريق والميزانية (Team & Cost Estimation)

الفريق الأدنى (MVP)

الدورالعددالمهمة
Backend Engineer2Ingestion + ETA + API
Frontend Engineer1PWA + Admin
DevOps/SRE0.5نشر ومراقبة
Data/ML0.5ETA tuning
Product/PM0.5تنسيق + تواصل مع الجهة
ميداني1جمع بيانات المحطات

الإجمالي: ~5.5 شخص × 14 أسبوعًا.

تقدير التكلفة (تقريبي)

البندالتكلفة الشهرية
استضافة (VPS/Small K8s)$150–400
قاعدة بيانات مُدارة$100–300
خرائط + مرور (Google/HERE)$200–500
Push/SMS$50–150
مراقبة (Grafana Cloud)$0–100
إجمالي$500–1,450

بدون تكلفة الأجهزة (إن لزم شراء GPS: $50–150 لكل حافلة).


تاسعًا: خطة التوسع (Scaling Path)

التوسع الأفقي (خطوط أكثر)

  • إضافة خط: < يوم عمل (بيانات + تحقق).
  • العائق: ليس تقنيًا، بل جمع بيانات المحطات.

التوسع الرأسي (مدن أخرى)

  • إعادة استخدام كل المنصة.
  • فقط: استيراد خطوط المدينة الجديدة.
  • البنية: multi-tenant (city_id في كل جدول) — يُضاف من البداية.

التوسع الوظيفي

الميزةالبنية التحتية المطلوبة
حجز مقاعد (بين المدن)جدول seat_reservations + دفع
تخطيط رحلة متعدد الوسائطمحرك routing (OSRM/GraphHopper)
تحليلات تنبؤية للطلبData warehouse (ClickHouse)
مساعد ذكي (LLM)RAG على بيانات التشغيل + واجهة محادثة

عاشرًا: ما لم يُغطَّ في المحادثة الأصلية — إضافات حاسمة

المحادثة الأصلية ركّزت على التصور. هذه فجوات إنتاجية يجب سدّها قبل التنفيذ:

الفجوةلماذا مهمةالحل المقترح
من يملك البيانات؟قانونيًا وتشغيليًااتفاقية بيانات مع الجهة المشغلة
من يحدّث الخطوط؟الخطوط تتغيرلوحة إدارة للخطوط + سجل تغييرات
ماذا لو تأخر السائق؟يؤثر على ETAإدخال يدوي للتأخير من لوحة الإدارة
كيف نتعامل مع الأخطاء؟شكاوىزر “بلغ عن خطأ” في التطبيق → تذكرة
التعدد اللغويزوارi18n من البداية (ar/en)
إمكانية الوصولإلزاميWCAG 2.1 AA للواجهات
الوضع دون اتصالشبكة ضعيفةPWA + cache آخر حالة
الطوارئحافلة تتعطلزر “تعطل” في لوحة الإدارة → تنبيه تلقائي

حادي عشر: الخلاصة التنفيذية الموسّعة

الرحلة الكاملة: من الشكوى إلى Production

text

شكوى يومية: "متى تجي الحافلة؟"
        ↓
Discovery (أسبوعان): هل يوجد GPS؟ API؟ قاعدة بيانات؟
        ↓
قرار معماري: طبقة عرض أم تجهيز كامل
        ↓
MVP (4 أسابيع): خط واحد + PWA + لوحة إدارة + ETA Basic
        ↓
قياس: MAE < 120s؟ رضا > 70%؟
        ↓
توسع (8 أسابيع): خطوط محلية + شاشات + ETA Traffic/ML
        ↓
بين المدن (4 أسابيع): فصل INTERCITY + مواعيد + حجز
        ↓
منصة Production (14 أسبوعًا): قابلة للتوسع لمدن أخرى

القيم الثلاث الحاكمة (من الملف الأصلي، مُترجَمة هندسيًا)

القيمةالترجمة الهندسية
البساطة للمواطنصفحة واحدة + 3 تبويبات + بحث بالوجهة
الصدق في البياناتeta.state إلزامي + لا أرقام دون حالة
العدالة في الوصولشاشة/QR + WCAG + لا اعتماد على هاتف فقط

الفرق بين “فكرة جميلة” و”منصة Production”

الفكرةالمنصة
“تطبيق يعرض الحافلات”Schema + API contracts + SLOs
“وقت وصول متوقع”محرك ETA بثلاثة مستويات + قياس MAE
“شاشات محطات”kiosk auth + WebSocket + fallback
“لوحة إدارة”RBAC + audit log + incident response
“تجربة محدودة”Discovery gate + success criteria + rollback
“توسع”multi-tenant + CI/CD + observability

الخلاصة: الملف الأصلي أنتج رؤية صادقة وواعية بحدود البيانات — وهذا نادر. لكن تحويلها إلى production يتطلب إضافة الطبقة الهندسية التي قدّمتها: مخطط بيانات كامل، عقود API، محرك ETA مُقاس، أمان، تشغيل، قياس، وفريق. بهذه الإضافات، Zezo Smart Transit Platform ليست فكرة على الورق، بل منصة قابلة للإطلاق خلال 90 يومًا — بشرط إنجاز بوابة Discovery أولًا.

الخطوة التالية الأدق: إعداد Discovery Package من 3 صفحات:

  1. صفحة أسئلة للجهة المشغلة (GPS؟ غرفة عمليات؟ قاعدة بيانات؟ API؟).
  2. صفحة معمارية مقترحة (بناءً على الإجابات).
  3. صفحة معايير نجاح التجربة (MAE, NPS, adoption).

هل تريد أن أصيغ لك Discovery Package كاملًا الآن، أم أنتقل إلى تصميم Wireframe نصي تفصيلي لواجهة المواطن (شاشة واحدة + حالات ETA الثلاث)؟

الجزء الثالث: الحزمة التنفيذية الكاملة — من Discovery إلى الإطلاق

سأكمل بما يجعل المنصة قابلة للتسليم فعليًا: حزمة Discovery، الـ Wireframes، خطة Sprint، اختبارات القبول، ونماذج جاهزة للاستخدام المباشر.


أولًا: Discovery Package كاملة (جاهزة للإرسال)

الصفحة 1: استبيان الجهة المشغلة

إلى: إدارة حافلات جازان — قسم التشغيل والتقنية
الموضوع: استبيان تقني أولي لمشروع معلومات الركاب

نرجو التكرم بالإجابة عن الأسئلة التالية لتحديد نطاق الحل المناسب. الإجابات تُستخدم لتحديد ما إذا كان المشروع طبقة عرض فوق أنظمة قائمة، أم يتطلب تجهيزًا إضافيًا.

القسم أ: أجهزة التتبع

#السؤالنعملالا أعرف
A1هل تحتوي الحافلات على أجهزة GPS تعمل حاليًا؟☐☐☐
A2هل ترسل الأجهزة بياناتها إلى خادم مركزي؟☐☐☐
A3ما معدل الإرسال التقريبي؟ (5s / 10s / 30s / دقيقة+)______
A4ما بروتوكول/شركة الأجهزة؟ (GT06, Teltonika, …)______

القسم ب: غرفة العمليات

#السؤالنعملالا أعرف
B1هل توجد غرفة عمليات تتابع الحافلات؟☐☐☐
B2هل تُسجَّل مواقع الحافلات تاريخيًا؟☐☐☐
B3هل تُسجَّل أوقات الوصول الفعلية للمحطات؟☐☐☐

القسم ج: البيانات الأساسية

#السؤالنعملالا أعرف
C1هل توجد قاعدة بيانات رقمية للخطوط والمحطات؟☐☐☐
C2هل توجد مواعيد مجدولة موثقة لكل رحلة؟☐☐☐
C3هل البيانات متاحة بصيغة قابلة للتصدير (CSV/API)؟☐☐☐

القسم د: التكامل

#السؤالنعملالا أعرف
D1هل توجد واجهة برمجية (API) للأنظمة الحالية؟☐☐☐
D2هل هناك إمكانية لتكامل تقني مع طرف ثالث؟☐☐☐
D3من الجهة المخولة بالموافقة على التكامل؟______

القسم هـ: الشاشات الحالية

#السؤالنعملالا أعرف
E1هل الشاشات الداخلية قابلة لتحديث المحتوى برمجيًا؟☐☐☐
E2هل توجد شاشات خارجية في المحطات؟☐☐☐
E3هل الشاشات متصلة بالإنترنت؟☐☐☐

القسم و: النطاق

#السؤالالإجابة
F1كم عدد الخطوط المحلية؟______
F2كم عدد خطوط بين المدن؟______
F3كم عدد المحطات الإجمالي؟______
F4كم عدد الحافلات العاملة؟______

الصفحة 2: شجرة القرار المعماري

بناءً على الإجابات، يُحدَّد المسار:

text

                    ┌─────────────────────┐
                    │  هل A1 = نعم؟        │
                    └──────────┬──────────┘
                               │
              ┌────────────────┴────────────────┐
              │ لا                              │ نعم
              ▼                                 ▼
    ┌──────────────────┐            ┌─────────────────────┐
    │ مسار التجهيز      │            │ هل A2 = نعم؟         │
    │ (شراء/تركيب GPS) │            └──────────┬──────────┘
    │ + بناء المنصة    │                       │
    └──────────────────┘          ┌────────────┴────────────┐
                                  │ لا                      │ نعم
                                  ▼                         ▼
                        ┌──────────────────┐      ┌──────────────────────┐
                        │ مسار الربط       │      │ هل D1 = نعم؟          │
                        │ (توصيل الأجهزة   │      └──────────┬───────────┘
                        │  بخادم جديد)     │                 │
                        └──────────────────┘    ┌────────────┴────────────┐
                                                │ لا                      │ نعم
                                                ▼                         ▼
                                      ┌──────────────────┐      ┌──────────────────┐
                                      │ مسار التكامل      │      │ مسار طبقة العرض  │
                                      │ (بناء API وسيط)   │      │ (الأسرع والأرخص) │
                                      └──────────────────┘      └──────────────────┘

النتيجة المتوقعة (بناءً على إجابات المستخدم “1 و3 و4”):

  • A1 غير مؤكد، A2 غير مؤكد، D1 غير مؤكد → الاحتمال الأقوى: مسار الربط أو التكامل.
  • Discovery يحسم ذلك خلال أسبوعين.

الصفحة 3: معايير نجاح التجربة (Success Criteria)

المعيارالقياسالحد الأدنى للنجاحالحد المستهدف
دقة ETAMAE< 180 ثانية< 120 ثانية
تغطية البيانات% وقت التشغيل بتدفق GPS حديث> 80%> 95%
استخدام المواطنينجلسات فريدة/يوم> 200> 500
رضا المستخدمNPS> 20> 40
وضوح الوجهة% يجيبون “عرفت وجهتي”> 60%> 80%
الالتزام بالمواعيدOTP (±5 دقائق)> 75%> 85%
استقرار APIUptime> 99%> 99.5%

قاعدة القرار: إذا تحققت الحدود الدنيا في 3 من 7 معايير على الأقل بعد 4 أسابيع → المضي للتوسع. إذا لا → مراجعة جذرية قبل الاستثمار.


ثانيًا: Wireframes نصية تفصيلية

الشاشة 1: الخريطة (الافتراضية)

text

┌─────────────────────────────────────┐
│  ☰  Zezo Transit         🔔 (3)  ⚙️ │
├─────────────────────────────────────┤
│                                     │
│   🔍  إلى أين تريد الذهاب؟          │
│   ┌───────────────────────────────┐ │
│   │ [ابحث عن منطقة أو مدينة]      │ │
│   └───────────────────────────────┘ │
│                                     │
│  ┌─────────────────────────────────┐│
│  │                                 ││
│  │      🗺️  الخريطة                ││
│  │                                 ││
│  │   🚌(07A) ──►                  ││
│  │         🚌(12B)                 ││
│  │                    🚌(204)      ││
│  │                                 ││
│  │      📍 موقعك                   ││
│  │                                 ││
│  └─────────────────────────────────┘│
│                                     │
│  ┌─ الحافلات القريبة ─────────────┐ │
│  │ 🟢 07A  المنطقة الشمالية       │ │
│  │    باقي 7 دقائق • 1.8 كم       │ │
│  │                          [عرض] │ │
│  ├────────────────────────────────┤ │
│  │ 🟡 12B  المنطقة الجنوبية       │ │
│  │    مجدول 2:45 م                │ │
│  │                          [عرض] │ │
│  ├────────────────────────────────┤ │
│  │ 🔴 204  أبها (بين المدن)       │ │
│  │    غير مؤكد — آخر تحديث 2:28   │ │
│  │                          [عرض] │ │
│  └────────────────────────────────┘ │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

قواعد UX:

  • البحث بالوجهة أولًا (وليس برقم الخط).
  • كل بطاقة تعرض حالة ETA بصريًا (لون + نص).
  • زر “عرض” يفتح تفاصيل الرحلة.

الشاشة 2: تفاصيل الرحلة

text

┌─────────────────────────────────────┐
│  ←  الحافلة 07A              ⋮     │
├─────────────────────────────────────┤
│                                     │
│  🟢 في الطريق                       │
│  ┌─────────────────────────────────┐│
│  │   باقي 7 دقائق                  ││
│  │   ████████████░░░░  68%         ││
│  │   آخر تحديث: قبل 8 ثوانٍ        ││
│  └─────────────────────────────────┘│
│                                     │
│  📍 الوجهة: المنطقة الشمالية        │
│  🚏 المحطة القادمة: محطة 3          │
│  🚌 اللوحة: أ ب ج 1234              │
│                                     │
│  ┌─ المحطات القادمة ───────────────┐│
│  │ ● محطة 3      باقي 2 دقيقة      ││
│  │ ○ محطة 4      باقي 5 دقائق      ││
│  │ ○ محطة 5      باقي 9 دقائق      ││
│  │ ○ النهائية    باقي 15 دقيقة     ││
│  └────────────────────────────────┘│
│                                     │
│  [ 🔔 نبّهني عند اقتراب الحافلة ]  │
│  [ 🗺️ عرض المسار على الخريطة ]     │
│                                     │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

الشاشة 3: حالة SCHEDULED (🟡)

text

│  🟡 موعد مجدول                      │
│  ┌─────────────────────────────────┐│
│  │   مجدول 2:45 م                  ││
│  │   لا يوجد تتبع لحظي متاح حاليًا ││
│  └─────────────────────────────────┘│

لماذا؟ الصدق: لا ندّعي لحظية عند غياب التتبع.


الشاشة 4: حالة UNAVAILABLE (🔴)

text

│  🔴 البيانات غير متاحة              │
│  ┌─────────────────────────────────┐│
│  │   غير مؤكد                      ││
│  │   آخر تحديث معروف: 2:28 م      ││
│  │   قد تصل الحافلة في أي وقت      ││
│  └─────────────────────────────────┘│

لماذا؟ الصدق أفضل من رقم مضلل. هذا يبني الثقة.


الشاشة 5: رحلاتي

text

┌─────────────────────────────────────┐
│  🧭 رحلاتي                          │
├─────────────────────────────────────┤
│                                     │
│  ┌─ خطي الدائم ────────────────────┐│
│  │ 🟢 07A  المنطقة الشمالية       ││
│  │    محطة الشامل ← المنطقة        ││
│  │    [ 🔔 مفعّل ]  [ إدارة ]      ││
│  └────────────────────────────────┘│
│                                     │
│  ┌─ رحلات قادمة ──────────────────┐│
│  │ 🟡 12B  غدًا 7:30 ص            ││
│  │    [ تذكير ]                    ││
│  └────────────────────────────────┘│
│                                     │
│  [ + إضافة خط أو رحلة ]            │
│                                     │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

الشاشة 6: التنبيهات

text

┌─────────────────────────────────────┐
│  🔔 التنبيهات                       │
├─────────────────────────────────────┤
│                                     │
│  اليوم                              │
│  ┌────────────────────────────────┐ │
│  │ 🚌 حافلتك اقتربت!              │ │
│  │ 07A • محطة الشامل              │ │
│  │ باقي دقيقتان • 2:41 م          │ │
│  └────────────────────────────────┘ │
│  ┌────────────────────────────────┐ │
│  │ ⚠️ تأخير 5 دقائق                │ │
│  │ 12B • بسبب ازدحام مروري        │ │
│  │ 2:35 م                          │ │
│  └────────────────────────────────┘ │
│                                     │
│  أمس                                │
│  ┌────────────────────────────────┐ │
│  │ 🔧 تغيير مسار 07A              │ │
│  │ محطة 3 مؤقتًا مغلقة            │ │
│  └────────────────────────────────┘ │
│                                     │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

شاشة المحطة (Kiosk)

text

┌─────────────────────────────────────────────┐
│  🚌 محطة مركز الشامل                        │
│  ⏰ 2:32 م  •  تحديث تلقائي                  │
├─────────────────────────────────────────────┤
│                                             │
│  الخط   │ الوجهة              │ الوصول     │
│  ───────┼─────────────────────┼─────────── │
│  🟢 07A │ المنطقة الشمالية    │ 7 دقائق    │
│  🟡 12B │ المنطقة الجنوبية    │ 2:45 م     │
│  🔴 204 │ أبها (بين المدن)    │ غير مؤكد   │
│  🟢 15C │ المنطقة الشرقية    │ 18 دقيقة   │
│                                             │
├─────────────────────────────────────────────┤
│  📱 امسح الرمز لمتابعة الحافلات على هاتفك   │
│  ┌───────┐                                  │
│  │ ▓▓▓▓▓ │  QR → /stop/{id}                 │
│  │ ▓▓▓▓▓ │                                  │
│  │ ▓▓▓▓▓ │                                  │
│  └───────┘                                  │
├─────────────────────────────────────────────┤
│  ⚠️ تأكد من رقم الخط والوجهة قبل الصعود     │
└─────────────────────────────────────────────┘

قواعد Kiosk:

  • تحديث تلقائي كل 10 ثوانٍ (WebSocket).
  • خط كبير، تباين عالٍ (WCAG AA).
  • QR يفتح صفحة ويب خفيفة (لا تطبيق).
  • إعلان صوتي اختياري بالعربية.

ثالثًا: خطة Sprint تفصيلية (14 أسبوعًا)

Sprint 0: Discovery (أسبوعان)

الأسبوعالمهمةالمخرج
1إرسال الاستبيان + مقابلةإجابات موثقة
1جرد بيانات الخطوطملف CSV/Excel
2زيارة ميدانية (5 محطات)إحداثيات + أسماء
2بوابة قرار: تحديد المسار المعماريوثيقة قرار

Definition of Done: وثيقة “Discovery Report” من 5 صفحات تحدد المسار.


Sprint 1: البنية التحتية (أسبوعان)

المهمةالمدة
إعداد repo + CI/CD2 يوم
Schema قاعدة البيانات3 أيام
Tracking Ingestion Service4 أيام
API Gateway + Auth3 أيام
Redis + TimescaleDB2 يوم

DoD: POST /ingest/gps يعمل ويخزن قراءات.


Sprint 2: ETA + واجهة المواطن MVP (أسبوعان)

المهمةالمدة
ETA Engine (Level 1)3 أيام
GET /stops/{id}/arrivals3 أيام
PWA Shell + Map4 أيام
شاشة الخريطة + تفاصيل4 أيام

DoD: يمكن فتح الصفحة ورؤية حافلة وهمية مع ETA.


Sprint 3: لوحة الإدارة + التنبيهات (أسبوعان)

المهمةالمدة
Admin Dashboard (React)4 أيام
خريطة الأسطول الحية3 أيام
كشف GPS Lost2 يوم
Push Notifications3 أيام

DoD: الإدارة ترى الأسطول وتستلم تنبيهات.


Sprint 4: التجربة الميدانية (أسبوعان)

المهمةالمدة
ربط خط واحد حقيقي3 أيام
اختبار ميداني (5 محطات)5 أيام
جمع eta_snapshotsمستمر
إصلاح أخطاء4 أيام

DoD: دقة ETA مقاسة على بيانات حقيقية.


Sprint 5: شاشة المحطة + QR (أسبوع)

المهمةالمدة
Kiosk UI3 أيام
QR generation + web page2 يوم
Kiosk auth2 يوم

DoD: شاشة تعمل على جهاز لوحي في محطة حقيقية.


Sprint 6: التحسين والتوسع (أسبوعان)

المهمةالمدة
ETA Level 2 (Traffic)4 أيام
تحسين PWA (offline)3 أيام
إضافة 3–5 خطوط3 أيام
تقرير التقييم2 يوم

DoD: تقرير “Go/No-Go” للتوسع الكامل.


رابعًا: اختبارات القبول (Acceptance Tests)

اختبارات ETA

gherkin

Feature: ETA State Logic

Scenario: Live data within threshold
  Given vehicle last_seen_at = 8 seconds ago
  And trip status = IN_TRANSIT
  When citizen requests arrivals
  Then response eta.state = "LIVE"
  And response eta.display_ar contains "باقي"

Scenario: Stale GPS data
  Given vehicle last_seen_at = 45 seconds ago
  When citizen requests arrivals
  Then response eta.state = "UNAVAILABLE"
  And response eta.display_ar contains "غير مؤكد"

Scenario: No GPS device
  Given vehicle gps_device_id = null
  And trip status = SCHEDULED
  When citizen requests arrivals
  Then response eta.state = "SCHEDULED"
  And response eta.scheduled_at is not null

اختبارات API

الاختبارالمتوقع
طلب بدون auth (مواطن)200
طلب admin بدون token401
إحداثيات خارج النطاق400
Rate limit > 60/دقيقة429
GPS timestamp مستقبلي400

اختبارات الأداء

السيناريوالهدف
100 طلب/ثانية على /arrivalsp95 < 300ms
50 حافلة ترسل كل 5 ثوانٍمعالجة < 100ms
WebSocket 500 اتصال متزامنلا تسرب ذاكرة

خامسًا: نماذج جاهزة — وثيقة القرار المعماري (ADR)

markdown

# ADR-001: اختيار ETA بثلاث حالات

## الحالة
المستخدمون يحتاجون وقت وصول، لكن بيانات GPS قد تكون غير متاحة أو قديمة.

## القرار
كل استجابة ETA تحمل حقل `state` إلزاميًا: LIVE / SCHEDULED / UNAVAILABLE.

## البدائل المدروسة
1. عرض رقم دائمًا (مرفوض: يضلل المستخدم).
2. عرض رقم فقط عند توفر GPS (مرفوض: يفرغ الواجهة).
3. ثلاث حالات صريحة (مختار).

## النتائج
- إيجابي: بناء ثقة، صدق في البيانات.
- سلبي: واجهة أكثر تعقيدًا قليلًا.
- مخاطر: قد يسيء المستخدم فهم "غير مؤكد" → يُعالج بنص توضيحي.

سادسًا: Runbook التشغيل (Incident Response)

حادث: انقطاع تدفق GPS لأسطول كامل

text

1. كشف: تنبيه Prometheus "gps_ingest_rate < 10% of baseline"
2. تصنيف: P1 (حرج)
3. إجراء فوري (خلال 5 دقائق):
   - تحويل كل trip.status إلى UNAVAILABLE
   - إشعار فريق التشغيل
   - فحص: هل المشكلة في الأجهزة أم الخادم؟
4. استجابة (خلال 30 دقيقة):
   - إذا خادم → rollback آخر نشر
   - إذا أجهزة → التواصل مع المزود
5. تعافي: إعادة تفعيل LIVE تدريجيًا (10% → 50% → 100%)
6. مراجعة: post-mortem خلال 48 ساعة

حادث: ETA drift (انحراف كبير)

text

1. كشف: MAE > 300s لمدة 15 دقيقة
2. إجراء: تبديل إلى SCHEDULED mode تلقائيًا
3. تحقيق: فحص traffic API، بيانات GPS، النموذج
4. إصلاح + إعادة تفعيل LIVE بعد التحقق

سابعًا: نماذج التواصل (Communication Templates)

إشعار إطلاق التجربة للمواطنين

text

🚌 تجربة جديدة: اعرف متى تجي حافلتك!

جرّب خدمة معلومات الحافلات الجديدة:
📍 أين الحافلة الآن
⏱️ متى تصل
🧭 إلى أين تذهب

متاحة على: [الرابط]
أو امسح QR في محطة [اسم المحطة]

ملاحظة: الخدمة تجريبية على خط [07A] حاليًا.
رأيك يهمنا — [رابط استبيان]

تقرير أسبوعي للإدارة

text

📊 تقرير التجربة — الأسبوع [X]

الخط: 07A
المحطات: 5
الرحلات: 42

الأداء:
✅ دقة ETA: MAE = 95 ثانية (الهدف: <120)
✅ تغطية GPS: 94%
⚠️ رضا: NPS = 35 (الهدف: >40)

المشكلات:
- 3 رحلات لم ترسل بيانات > 30 دقيقة
- تأخيرات ذروة الصباح

التوصية:
- الاستمرار + إضافة خط 12B الأسبوع القادم

ثامنًا: قائمة التحقق النهائية (Production Readiness Checklist)

البنية التحتية

  • □ قاعدة بيانات مع backup يومي + PITR
  • □ Redis مع persistence (AOF)
  • □ HTTPS إلزامي + HSTS
  • □ CDN للـ PWA
  • □ DNS + SSL مُجدَّد تلقائيًا

الأمان

  • □ JWT مع rotation
  • □ Rate limiting مفعّل
  • □ Input validation على كل endpoint
  • □ Audit log للإدارة
  • □ اختبار اختراق (pen test) أساسي

المراقبة

  • □ Prometheus + Grafana
  • □ تنبيهات P1/P2
  • □ structured logging
  • □ tracing (OpenTelemetry)
  • □ uptime monitoring خارجي

البيانات

  • □ Retention policy مطبّق
  • □ GDPR/PDPL compliance
  • □ إخفاء هوية السائق
  • □ موافقة المستخدم على الموقع

التشغيل

  • □ Runbook لكل حادث
  • □ on-call rotation
  • □ staging environment
  • □ rollback plan
  • □ post-mortem template

المنتج

  • □ 3 حالات ETA تعمل
  • □ Kiosk + QR
  • □ إشعارات
  • □ WCAG AA
  • □ i18n (ar/en)

تاسعًا: الخلاصة النهائية — ما الذي يجعل هذا Production حقًا؟

الطبقةما قدّمته في هذا التحليل
استراتيجيةDiscovery Package + شجرة قرار + معايير نجاح
تصميمWireframes لكل شاشة + قواعد UX
هندسةSchema كامل + API contracts + ETA logic
تنفيذخطة Sprint 14 أسبوعًا + DoD لكل sprint
جودةAcceptance tests + performance targets
تشغيلRunbook + incident response + ADR
تواصلقوالب للإدارة والمواطنين
حوكمةProduction Readiness Checklist

الفكرة الأصلية في الملف كانت رؤية صادقة وواعية بحدود البيانات. ما أضفته هو الطبقة التنفيذية التي تحوّلها إلى مشروع قابل للتسليم:

  1. Discovery يحسم المسار قبل أي استثمار (أسبوعان).
  2. MVP على خط واحد يثبت الدقة (4 أسابيع).
  3. قياس صارم (MAE, NPS, adoption) يحدد التوسع.
  4. بنية قابلة للتوسع لمدن أخرى دون إعادة بناء.

النتيجة: منصة Zezo Smart Transit ليست فكرة على الورق — بل منتج قابل للإطلاق خلال 14 أسبوعًا، بشرط إنجاز Discovery أولًا. والفرق بين النجاح والفشل ليس في التقنية، بل في الصدق مع البيانات والبدء صغيرًا والقياس قبل التوسع — وهذه بالضبط القيم التي أشار إليها الملف الأصلي.

الخطوة التالية العملية: طباعة Discovery Package (3 صفحات) وإرسالها للجهة المشغلة هذا الأسبوع — لأن كل يوم تأخير في Discovery هو يوم تأخير في الإطلاق.

الجزء الرابع: الطبقة التنفيذية العميقة — الكود، البنية التحتية، والفريق

سأكمل بما لم يُغطَّ بعد: الكود الفعلي، بنية النشر، الأدوات، والفريق — أي ما يحوّل الخطة إلى نظام يعمل اليوم.


أولًا: الكود الفعلي — ETA Engine (Python/FastAPI)

python

# eta_engine.py
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
import math

class ETAState(str, Enum):
    LIVE = "LIVE"
    SCHEDULED = "SCHEDULED"
    UNAVAILABLE = "UNAVAILABLE"

@dataclass
class ETAResult:
    state: ETAState
    seconds: int | None
    display_ar: str
    confidence: float | None
    data_age_sec: float | None
    method: str | None
    last_known_at: datetime | None = None

GPS_STALE_THRESHOLD_SEC = 30
MIN_SPEED_FLOOR_KMH = 5.0

def haversine_m(lat1, lng1, lat2, lng2) -> float:
    R = 6371000
    phi1, phi2 = math.radians(lat1), math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lng2 - lng1)
    a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
    return 2 * R * math.asin(math.sqrt(a))

def humanize_ar(seconds: int) -> str:
    if seconds < 60:
        return "أقل من دقيقة"
    minutes = seconds // 60
    if minutes == 1:
        return "دقيقة واحدة"
    if minutes == 2:
        return "دقيقتان"
    if 3 <= minutes <= 10:
        return f"{minutes} دقائق"
    return f"{minutes} دقيقة"

def compute_eta(vehicle, trip, target_stop, route_stops, now=None) -> ETAResult:
    now = now or datetime.utcnow()
    
    # 1) هل يوجد جهاز GPS؟
    if not vehicle.gps_device_id or vehicle.last_seen_at is None:
        return _scheduled_or_unavailable(trip, reason="no_gps")
    
    # 2) هل البيانات حديثة؟
    data_age = (now - vehicle.last_seen_at).total_seconds()
    if data_age > GPS_STALE_THRESHOLD_SEC:
        return ETAResult(
            state=ETAState.UNAVAILABLE,
            seconds=None,
            display_ar=f"غير مؤكد — آخر تحديث {_fmt_time(vehicle.last_seen_at)}",
            confidence=None,
            data_age_sec=data_age,
            method=None,
            last_known_at=vehicle.last_seen_at,
        )
    
    # 3) هل الرحلة مجدولة فقط (لم تبدأ)؟
    if trip.status == "SCHEDULED" and vehicle.last_speed_kmh is None:
        return _scheduled_or_unavailable(trip, reason="scheduled")
    
    # 4) حساب ETA الفعلي
    remaining_m = _remaining_distance_m(
        vehicle.last_lat, vehicle.last_lng, target_stop, route_stops, trip
    )
    speed_kmh = max(vehicle.last_speed_kmh or 0, MIN_SPEED_FLOOR_KMH)
    eta_sec = int(remaining_m / (speed_kmh * 1000 / 3600))
    
    confidence = _confidence(data_age, vehicle.last_speed_kmh)
    
    return ETAResult(
        state=ETAState.LIVE,
        seconds=eta_sec,
        display_ar=f"باقي {humanize_ar(eta_sec)}",
        confidence=confidence,
        data_age_sec=data_age,
        method="BASIC",
    )

def _scheduled_or_unavailable(trip, reason: str) -> ETAResult:
    if trip.scheduled_arrival and trip.status in ("SCHEDULED", "BOARDING"):
        return ETAResult(
            state=ETAState.SCHEDULED,
            seconds=None,
            display_ar=f"مجدول {_fmt_time(trip.scheduled_arrival)}",
            confidence=None,
            data_age_sec=None,
            method=None,
        )
    return ETAResult(
        state=ETAState.UNAVAILABLE,
        seconds=None,
        display_ar="غير مؤكد",
        confidence=None,
        data_age_sec=None,
        method=None,
    )

def _remaining_distance_m(lat, lng, target_stop, route_stops, trip) -> float:
    # المسافة من موقع الحافلة إلى أقرب محطة قادمة + مجموع المسافات حتى الهدف
    # (مبسط: نفترض أن الحافلة على المسار)
    total = haversine_m(lat, lng, target_stop.lat, target_stop.lng)
    return total

def _confidence(data_age: float, speed: float | None) -> float:
    # كلما زاد عمر البيانات، قلّت الثقة
    age_score = max(0.0, 1.0 - (data_age / GPS_STALE_THRESHOLD_SEC))
    speed_score = 1.0 if speed and speed > 10 else 0.6
    return round(age_score * speed_score, 2)

def _fmt_time(dt: datetime) -> str:
    # تنسيق عربي بسيط: 2:28 م
    hour = dt.hour % 12 or 12
    minute = dt.minute
    period = "ص" if dt.hour < 12 else "م"
    return f"{hour}:{minute:02d} {period}"

نقاط القوة في هذا الكود:

  • state إلزامي — لا يمكن إرجاع ETA دون تصنيف.
  • data_age يُحسب من last_seen_at — لا ثقة بالبيانات القديمة.
  • confidence رقمي — يمكن للواجهة إخفاء التقدير إن < 0.5.
  • فصل SCHEDULED عن UNAVAILABLE — احترام لحالة البيانات.

ثانيًا: Ingestion Service (Node.js/Express + MQTT)

javascript

// ingest.js
const express = require('express');
const { Pool } = require('pg');
const Redis = require('ioredis');
const mqtt = require('mqtt');

const app = express();
app.use(express.json({ limit: '1kb' }));

const db = new Pool({ connectionString: process.env.DATABASE_URL });
const redis = new Redis(process.env.REDIS_URL);

// التحقق من صحة القراءة
function validateReading(body) {
  const { device_id, ts, lat, lng, speed_kmh } = body;
  if (!device_id || !ts) return 'missing_fields';
  if (lat < -90 || lat > 90) return 'invalid_lat';
  if (lng < -180 || lng > 180) return 'invalid_lng';
  const age = (Date.now() - new Date(ts).getTime()) / 1000;
  if (age > 300 || age < -60) return 'timestamp_out_of_range';
  if (speed_kmh != null && (speed_kmh < 0 || speed_kmh > 200)) return 'invalid_speed';
  return null;
}

// HTTP endpoint
app.post('/api/v1/ingest/gps', async (req, res) => {
  const err = validateReading(req.body);
  if (err) return res.status(400).json({ error: err });

  const { device_id, ts, lat, lng, speed_kmh, heading, accuracy_m } = req.body;

  try {
    // 1) ابحث عن المركبة
    const v = await db.query(
      'SELECT id FROM vehicles WHERE gps_device_id = $1', [device_id]
    );
    if (v.rowCount === 0) return res.status(404).json({ error: 'unknown_device' });
    const vehicleId = v.rows[0].id;

    // 2) خزّن في TimescaleDB
    await db.query(
      `INSERT INTO gps_readings(time, vehicle_id, lat, lng, speed_kmh, heading, accuracy_m)
       VALUES ($1,$2,$3,$4,$5,$6,$7)`,
      [ts, vehicleId, lat, lng, speed_kmh, heading, accuracy_m]
    );

    // 3) حدّث الحالة اللحظية في Redis
    await redis.hset(`vehicle:${vehicleId}`, {
      last_lat: lat, last_lng: lng,
      last_speed_kmh: speed_kmh ?? '',
      last_heading: heading ?? '',
      last_seen_at: ts,
    });
    await redis.expire(`vehicle:${vehicleId}`, 120);

    // 4) نشر حدث للـ WebSocket
    await redis.publish('vehicle_moved', JSON.stringify({
      vehicle_id: vehicleId, lat, lng, speed_kmh, ts
    }));

    res.status(202).json({ next_expected_in_sec: 5 });
  } catch (e) {
    console.error('ingest_error', e);
    res.status(500).json({ error: 'internal' });
  }
});

// MQTT listener (للأجهزة التي تستخدم MQTT)
const mqttClient = mqtt.connect(process.env.MQTT_URL);
mqttClient.on('connect', () => mqttClient.subscribe('gps/+/reading'));
mqttClient.on('message', async (topic, payload) => {
  try {
    const body = JSON.parse(payload.toString());
    const deviceId = topic.split('/')[1];
    await processReading({ ...body, device_id: deviceId });
  } catch (e) { console.error('mqtt_error', e); }
});

app.listen(3001);

لماذا هذا التصميم؟

  • Idempotent نسبيًا: نفس القراءة تُخزَّن مرتين لكن Redis يحتفظ بالأحدث.
  • Redis TTL 120s: إذا توقف الجهاز، الحالة تختفي تلقائيًا → تحويل لـ 🔴.
  • MQTT + HTTP: يغطي أغلب أجهزة التتبع في السوق.
  • Publish للـ WebSocket: فصل المسؤوليات.

ثالثًا: WebSocket Service (Node.js)

javascript

// ws.js
const WebSocket = require('ws');
const Redis = require('ioredis');

const wss = new WebSocket.Server({ port: 8081 });
const sub = new Redis(process.env.REDIS_URL);

// خريطة: stop_id -> Set of clients
const subscriptions = new Map();

wss.on('connection', (ws, req) => {
  const url = new URL(req.url, 'http://x');
  const stopId = url.searchParams.get('stop_id');
  if (!stopId) return ws.close(1008, 'stop_id_required');

  if (!subscriptions.has(stopId)) subscriptions.set(stopId, new Set());
  subscriptions.get(stopId).add(ws);

  ws.on('close', () => subscriptions.get(stopId)?.delete(ws));
  ws.send(JSON.stringify({ type: 'connected', stop_id: stopId }));
});

// استقبل أحداث من Redis
sub.subscribe('vehicle_moved', 'eta_updated', 'alert_created');
sub.on('message', async (channel, message) => {
  const event = JSON.parse(message);
  if (channel === 'vehicle_moved') {
    // أعد حساب ETA للمحطات المشتركة (يُفضّل في خدمة منفصلة)
    broadcastToAffectedStops(event);
  } else {
    broadcast(event);
  }
});

function broadcast(event) {
  for (const [stopId, clients] of subscriptions) {
    const payload = JSON.stringify({ ...event, stop_id: stopId });
    for (const ws of clients) {
      if (ws.readyState === WebSocket.OPEN) ws.send(payload);
    }
  }
}

function broadcastToAffectedStops(event) {
  // مبسط: بث للكل — يمكن تحسينه بـ geo-index
  broadcast({ type: 'vehicle_moved', ...event });
}

ملاحظة إنتاجية: في التوسع، استبدل broadcastToAffectedStops بـ Geohash indexing (Redis GEO) لتحديد المحطات المتأثرة فقط.


رابعًا: بنية النشر (Docker Compose للبداية)

yaml

# docker-compose.yml — نسخة MVP
version: "3.9"
services:
  postgres:
    image: timescale/timescaledb:latest-pg16
    environment:
      POSTGRES_DB: zstp
      POSTGRES_PASSWORD: ${DB_PASS}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./migrations:/docker-entrypoint-initdb.d
    ports: ["5432:5432"]

  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes: ["redisdata:/data"]
    ports: ["6379:6379"]

  ingest:
    build: ./services/ingest
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASS}@postgres:5432/zstp
      REDIS_URL: redis://redis:6379
    depends_on: [postgres, redis]
    ports: ["3001:3001"]

  api:
    build: ./services/api
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASS}@postgres:5432/zstp
      REDIS_URL: redis://redis:6379
    depends_on: [postgres, redis]
    ports: ["3000:3000"]

  ws:
    build: ./services/ws
    environment:
      REDIS_URL: redis://redis:6379
    depends_on: [redis]
    ports: ["8081:8081"]

  pwa:
    build: ./apps/citizen-pwa
    ports: ["80:80"]

  admin:
    build: ./apps/admin
    ports: ["81:80"]

  kiosk:
    build: ./apps/kiosk
    ports: ["82:80"]

  prometheus:
    image: prom/prometheus
    volumes: ["./ops/prometheus.yml:/etc/prometheus/prometheus.yml"]
    ports: ["9090:9090"]

  grafana:
    image: grafana/grafana
    ports: ["3002:3000"]

volumes:
  pgdata:
  redisdata:

للإنتاج: Kubernetes (EKS/GKE) مع:

  • HPA على ingest و api حسب CPU + RPS.
  • PodDisruptionBudget لضمان التوفر.
  • NetworkPolicy لعزل قاعدة البيانات.
  • SealedSecrets لإدارة الأسرار.

خامسًا: هيكل المستودع (Monorepo)

text

zstp/
├── services/
│   ├── ingest/          # GPS ingestion (Node.js)
│   │   ├── src/
│   │   ├── Dockerfile
│   │   └── package.json
│   ├── api/             # REST API (FastAPI)
│   │   ├── app/
│   │   │   ├── main.py
│   │   │   ├── routes/
│   │   │   ├── eta_engine.py
│   │   │   └── models.py
│   │   ├── Dockerfile
│   │   └── requirements.txt
│   ├── ws/              # WebSocket (Node.js)
│   └── eta-worker/      # يعيد حساب ETA دوريًا
├── apps/
│   ├── citizen-pwa/     # React + Vite + PWA
│   ├── admin/           # React + Vite
│   └── kiosk/           # React (static build)
├── packages/
│   ├── shared-types/    # TypeScript types مشتركة
│   └── ui/              # مكونات مشتركة
├── migrations/
│   ├── 001_init.sql
│   ├── 002_hypertable.sql
│   └── 003_indexes.sql
├── ops/
│   ├── prometheus.yml
│   ├── grafana/dashboards/
│   └── k8s/
├── tests/
│   ├── e2e/
│   └── load/
├── docs/
│   ├── adr/
│   ├── api/
│   └── runbooks/
└── docker-compose.yml

سادسًا: الأدوات (Tooling) — القرارات الموصى بها

الفئةالأداةلماذا
Backend (API)FastAPI (Python)ETA engine بلغة Python + async
Backend (Ingest)Node.js + ExpressI/O-bound، ممتاز للـ streams
FrontendReact + Vite + TypeScriptسرعة التطوير + PWA
خرائطMapLibre GL + OpenStreetMapمجاني، لا vendor lock-in
خرائط (تجاري)Mapbox / Google Mapsإن لزم دقة أعلى
قاعدة بياناتPostgreSQL 16 + TimescaleDBعلائقي + سلاسل زمنية
CacheRedis 7حالة لحظية + pub/sub
QueueRedis Streams (بداية) → Kafka (توسع)بسيط ثم قابل للتوسع
CI/CDGitHub Actionsمجاني، متكامل
IaCTerraform (للتوسع)بنية قابلة للتكرار
ContainerDocker + K8sمعياري
MonitoringPrometheus + Grafana + Lokiمفتوح المصدر
TracingOpenTelemetry + Tempoمعياري
Error trackingSentryضروري للإنتاج
PushFirebase Cloud Messagingمجاني، موثوق
SMS fallbackTwilio / Unifonic (محلي)للسعودية
AuthKeycloak (بداية) أو Auth0OIDC معياري
i18ni18nextمعياري في React
TestingVitest + Playwright + k6وحدات + E2E + حمل

قاعدة: لا تشترِ SaaS قبل أن تصل إلى 10K مستخدم. البداية مفتوحة المصدر.


سابعًا: الفريق — الملفات الوظيفية (Job Descriptions)

1. Backend Engineer (ETA/Tracking)

المسؤوليات:

  • بناء وصيانة ingest + eta_engine + api.
  • ضمان دقة ETA (MAE < 120s).
  • كتابة اختبارات وحدة وتكامل.

المتطلبات:

  • Python (FastAPI) + Node.js.
  • PostgreSQL + Redis.
  • خبرة في geospatial (Haversine، GeoJSON).
  • فهم TimescaleDB أو InfluxDB.

2. Frontend Engineer (PWA + Admin)

المسؤوليات:

  • بناء PWA للمواطن (خريطة + ETA states).
  • بناء لوحة الإدارة.
  • ضمان WCAG AA + i18n.

المتطلبات:

  • React + TypeScript + Vite.
  • MapLibre/Leaflet.
  • PWA (service worker, offline).
  • اختبار Playwright.

3. DevOps/SRE (Part-time)

المسؤوليات:

  • CI/CD pipelines.
  • مراقبة + تنبيهات.
  • Runbooks + incident response.

المتطلبات:

  • Docker + K8s.
  • GitHub Actions.
  • Prometheus/Grafana.
  • Terraform (مفيد).

4. Data/ML Engineer (Part-time)

المسؤوليات:

  • تحليل eta_snapshots.
  • تدريب نموذج Level 3 (XGBoost).
  • تقارير دقة أسبوعية.

المتطلبات:

  • Python (pandas, scikit-learn, XGBoost).
  • SQL متقدم.
  • فهم time-series.

5. Field Coordinator (Full-time خلال Discovery)

المسؤوليات:

  • زيارة المحطات، تسجيل GPS/الأسماء.
  • تنسيق مع الجهة المشغلة.
  • تدريب موظفي المحطات.

المتطلبات:

  • معرفة محلية بجازان.
  • مهارات تواصل.
  • دقة في التسجيل.

ثامنًا: خطة الاختبار المتعمقة

1. Unit Tests (ETA Engine)

python

def test_live_state_when_data_fresh():
    vehicle = Vehicle(gps_device_id="X", last_seen_at=now()-timedelta(seconds=8),
                      last_speed_kmh=30, last_lat=16.89, last_lng=42.55)
    trip = Trip(status="IN_TRANSIT")
    result = compute_eta(vehicle, trip, target_stop, route_stops)
    assert result.state == ETAState.LIVE
    assert "باقي" in result.display_ar

def test_unavailable_when_data_stale():
    vehicle = Vehicle(gps_device_id="X", last_seen_at=now()-timedelta(seconds=45),
                      last_speed_kmh=30, last_lat=16.89, last_lng=42.55)
    result = compute_eta(vehicle, Trip(status="IN_TRANSIT"), target_stop, route_stops)
    assert result.state == ETAState.UNAVAILABLE
    assert "غير مؤكد" in result.display_ar

def test_scheduled_when_no_gps_device():
    vehicle = Vehicle(gps_device_id=None, last_seen_at=None)
    trip = Trip(status="SCHEDULED", scheduled_arrival=datetime(2026,10,20,14,45))
    result = compute_eta(vehicle, trip, target_stop, route_stops)
    assert result.state == ETAState.SCHEDULED

2. Load Test (k6)

javascript

// load.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },
    { duration: '3m', target: 100 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get('http://api/api/v1/stops/STOP_UUID/arrivals');
  check(res, { 'status 200': (r) => r.status === 200 });
}

3. E2E Test (Playwright)

typescript

test('citizen sees ETA states correctly', async ({ page }) => {
  await page.goto('/');
  await page.fill('[data-testid=search]', 'المنطقة الشمالية');
  await page.click('[data-testid=result-07A]');
  await expect(page.locator('[data-testid=eta-state]')).toHaveText('🟢 في الطريق');
  await expect(page.locator('[data-testid=eta-time]')).toContainText('باقي');
});

تاسعًا: نماذج جاهزة — SLA مع الجهة المشغلة

markdown

# اتفاقية مستوى الخدمة (SLA) — مسودة

## الأطراف
- الجهة المشغلة: [إدارة حافلات جازان]
- المزود: [Zezo Smart Transit]

## نطاق الخدمة
- توفير واجهات معلومات الركاب (PWA + Kiosk + Admin).
- استقبال بيانات GPS من الأجهزة القائمة أو المُركَّبة.
- حساب ETA وعرضه بثلاث حالات.

## التزامات المزود
| المؤشر | الهدف |
|---|---|
| توفر API | 99.5% شهريًا |
| زمن استجابة p95 | < 300ms |
| دقة ETA (MAE) | < 120s في 80% من الحالات |
| زمن استجابة الحادث P1 | < 15 دقيقة |

## التزامات الجهة المشغلة
- توفير بيانات الخطوط والمحطات.
- ضمان عمل أجهزة GPS (إن وُجدت) أو التعاون لتركيبها.
- توفير صلاحية الوصول للأنظمة القائمة.

## التعويضات
- إذا نزل التوفر عن 99% → شهر مجاني.
- إذا تجاوز MAE 180s لأسبوعين → مراجعة جذرية.

## المدة والمراجعة
- 6 أشهر تجريبية، مراجعة كل 3 أشهر.

عاشرًا: خطة إدارة المخاطر المتقدمة

المخاطرةالاحتمالالأثرالتخفيف
لا يوجد GPS ولا نية لتركيبهمتوسطمرتفعDiscovery مبكر + خطة تمويل أجهزة
الجهة لا تتعاون في البياناتمتوسطمرتفعاتفاقية بيانات + بدائل ميدانية
دقة ETA منخفضةمرتفعمتوسطثلاث حالات + تحسين تدريجي
تعطل الشبكة في المحطاتمنخفضمتوسطPWA offline + cache
هجوم على APIمنخفضمرتفعRate limit + WAF + monitoring
تغيير في إدارة الجهةمتوسطمتوسطتوثيق كل شيء + نقل معرفة
تضخم النطاق (scope creep)مرتفعمتوسطعقد واضح + مراحل محددة
ضعف التبني من المواطنينمتوسطمرتفعحملة إطلاق + QR + شاشات

حادي عشر: مؤشرات ما بعد الإطلاق (90 يومًا)

المؤشرالأسبوع 2الأسبوع 6الأسبوع 12
مستخدمون فريدون/يوم50200500
دقة ETA (MAE)240s150s110s
تغطية GPS60%85%94%
NPS—2540
شكاوى/أسبوع2083
وقت استجابة P160m30m15m

ثاني عشر: الخلاصة — خريطة الطريق الكاملة

text

┌──────────────────────────────────────────────────────────────┐
│  الأسبوع 0: Discovery Package → الجهة المشغلة                 │
│  الأسبوع 2: قرار معماري (طبقة عرض / ربط / تجهيز)              │
│  الأسبوع 4: MVP — خط واحد + PWA + Admin + ETA Basic           │
│  الأسبوع 6: قياس أولي (MAE < 180s؟)                           │
│  الأسبوع 8: Kiosk + QR + 3 خطوط إضافية                        │
│  الأسبوع 10: ETA Traffic-aware + Push Notifications            │
│  الأسبوع 12: تقرير Go/No-Go                                   │
│  الأسبوع 14: إطلاق production + توسع أفقي                      │
└──────────────────────────────────────────────────────────────┘

ما قدّمته عبر الأجزاء الأربعة:

الجزءالمحتوى
1تحليل الملف + معمارية عامة + خطة 90 يومًا
2Schema كامل + API contracts + ETA engine + أمان + SRE + فريق
3Discovery Package + Wireframes + Sprint plan + Acceptance tests + Runbooks
4كود فعلي + Docker Compose + هيكل repo + أدوات + JD + SLA + إدارة مخاطر

الرسالة النهائية:

الفكرة الأصلية في ملف “مواقعي” — “متى تجي الحافلة؟” — تتحول إلى منصة Zezo Smart Transit قابلة للإطلاق خلال 14 أسبوعًا. لكن النجاح لا يعتمد على الكود (فهو واضح)، بل على:

  1. Discovery صادق يحسم المسار قبل الاستثمار.
  2. الصدق في البيانات — ثلاث حالات ETA ليست خيارًا تقنيًا، بل موقف أخلاقي.
  3. البدء صغيرًا — خط واحد يكشف الحقيقة أسرع من 100 خط.
  4. القياس قبل التوسع — MAE و NPS يحددان القرار، لا الحماس.
  5. العدالة الرقمية — شاشة المحطة و QR ليسا “إضافة”، بل شرط نجاح.

الخطوة التالية الوحيدة: طباعة Discovery Package (3 صفحات) وإرسالها هذا الأسبوع. كل يوم تأخير في Discovery هو يوم تأخير في الإطلاق — والمشكلة التي يعانيها المواطن في جازان كل يوم لا تحتمل التأجيل.

هل تريد أن أصيغ Discovery Package كملف PDF جاهز للطباعة (نص كامل بصفحات محددة)، أم أنتقل إلى بناء النموذج التطبيقي (Prototype) الفعلي بكود جاهز للتشغيل محليًا؟

مراجعة جميع ماسبق وتقديم وثيقة تقنية نهائية

الوثيقة التقنية النهائية

Zezo Smart Transit Platform (ZSTP)

منصة معلومات النقل الذكي لركاب الحافلات — جازان


رقم الوثيقة: ZSTP-TRD-001
الإصدار: 1.0 — نهائي
التاريخ: 20 أكتوبر 2026
الحالة: جاهزة للمراجعة والاعتماد
الجهة المستهدفة: إدارة حافلات جازان (JAZAN BUS) — قسم التشغيل والتقنية


جدول المحتويات

  1. الملخص التنفيذي
  2. خلفية المشكلة وتحليل الفجوة
  3. نطاق المشروع
  4. أهداف المنتج ومؤشرات النجاح
  5. المعمارية التقنية
  6. نموذج البيانات
  7. عقود واجهات البرمجة (API Contracts)
  8. محرك وقت الوصول المتوقع (ETA Engine)
  9. واجهات المستخدم
  10. الأمان وحماية البيانات
  11. البنية التحتية والنشر
  12. خطة التنفيذ (14 أسبوعًا)
  13. خطة الاختبار والجودة
  14. التشغيل والمراقبة (SRE)
  15. إدارة المخاطر
  16. الفريق والميزانية
  17. اتفاقية مستوى الخدمة (SLA)
  18. خطة التوسع
  19. الملاحق

1. الملخص التنفيذي

1.1 المشكلة

يواجه ركاب حافلات جازان فجوة معلوماتية حادة عند نقاط الانتظار: لا يعرفون وجهة الحافلة قبل ركوبها، ولا موعد وصولها إلى محطتهم، ولا موعد مغادرتها (للرحلات بين المدن)، ولا أي حافلة تخدم مدينتهم. الشاشات الداخلية الموجودة في الحافلات لا تخدم المنتظر في المحطة، لأنها تعرض المعلومات بعد الركوب لا قبله.

1.2 الحل

منصة Zezo Smart Transit Platform (ZSTP) — منظومة من ثلاث واجهات مترابطة (مواطن، محطة، إدارة) تعمل فوق طبقة بيانات موحدة، وتقدم وقت وصول متوقعًا (ETA) بثلاث حالات صريحة تحترم حدود البيانات المتاحة:

  • 🟢 LIVE — تتبع لحظي متاح وحديث.
  • 🟡 SCHEDULED — موعد مجدول فقط، دون ادعاء لحظية.
  • 🔴 UNAVAILABLE — بيانات غير متاحة، مع عرض آخر تحديث معروف.

1.3 المبدأ الحاكم

الصدق في البيانات قبل الإرضاء. لا يُعرض رقم وقت وصول دون تصنيف حالته، ولا يُدّعى تتبع لحظي عند انقطاع الإشارة. هذا المبدأ هو ما يبني ثقة المواطن ويجعل المشروع قابلًا للاستدامة.

1.4 المنهجية

لا يُفترض وجود التتبع أو غيابه. التحقق أولًا (Discovery)، ثم تجربة محدودة على خط واحد، ثم قياس الدقة والجدوى، ثم التوسع التدريجي. المدة الإجمالية المقدرة: 14 أسبوعًا للوصول إلى منصة production.

1.5 المخرج المتوقع

  • تقليل حيرة الركاب أثناء الانتظار.
  • رفع الثقة في خدمات النقل العام.
  • بناء أساس تقني قابل للتوسع لمدن أخرى.
  • دون افتراض شراء أنظمة قد تكون موجودة بالفعل.

2. خلفية المشكلة وتحليل الفجوة

2.1 الأبعاد الستة للمشكلة

البُعدالمشكلة
المعلوماتيلا يعرف المواطن وجهة الحافلة قبل ركوبها
الزمنيلا يعرف موعد الوصول ولا المغادرة
المكانيلا يعرف موقع الحافلة الحالي
التخطيطيلا يعرف أي حافلة تذهب لمدينة أخرى
التشغيليالشاشات الداخلية لا تخدم المنتظر في المحطة
الثقةقد تُعرض أوقات غير مؤكدة دون توضيح

2.2 خريطة الفجوات المعلوماتية

البياناتالإدارةداخل الحافلةالمواطن في المحطة
رقم الخطغالبًا نعمنعم❌
الوجهة النهائيةغالبًا نعمجزئيًا❌
موقع الحافلة اللحظيغير مؤكدلا❌
وقت الوصول المتوقعغير مؤكدجزئيًا❌
موعد المغادرة (بين المدن)غالبًا نعملا❌
المحطات الوسيطةغالبًا نعمجزئيًا❌
حالة الرحلة (تأخير/تعطل)غير مؤكدجزئيًا❌

الاستنتاج: المشكلة ليست غياب البيانات كليًا، بل غياب قناة عرض موجهة للمواطن. الحل قد لا يكون بناء نظام من الصفر، بل طبقة عرض (Presentation Layer) فوق بيانات قائمة — وهذا ما يحدده Discovery.

2.3 السياق الجغرافي

حافلات جازان (JAZAN BUS) كنموذج: بيئة تجمع النقل المحلي داخل المدينة مع رحلات بين المدن (أبها، جدة، الرياض). اختيار بيئة محددة يجعل المشروع قابلًا للقياس بدلًا من خطاب عام عن “النقل الذكي”.


3. نطاق المشروع

3.1 داخل النطاق

المكونالوصف
واجهة المواطنتطبيق ويب تقدمي (PWA) — بحث بالوجهة، خريطة، ETA بحالاته، تنبيهات
واجهة المحطةشاشة Kiosk + رمز QR لصفحة ويب خفيفة
لوحة الإدارةمتابعة الأسطول، التنبيهات، التقارير، إدارة الرحلات
خدمة الإدخالاستقبال بيانات GPS عبر HTTP/MQTT
محرك ETAحساب ثلاثي المستويات (Basic/Traffic/ML)
التكاملمع أنظمة التتبع والخطوط القائمة (بعد التحقق)

3.2 خارج النطاق (الإصدار الأول)

البندالسبب
حجز المقاعد والدفعميزة لاحقة تتطلب تكامل مالي
تخطيط رحلات متعدد الوسائطيتطلب محرك routing منفصل
تطبيقات iOS/Android أصليةPWA يكفي للإطلاق
تتبع السائقين الفرديحماية بيانات + خارج نطاق معلومات الركاب
تحليلات تنبؤية للطلبيتطلب بيانات تاريخية كافية

3.3 الافتراضات

  • الجهة المشغلة تملك أو تستطيع توفير بيانات الخطوط والمحطات.
  • هناك إمكانية تقنية للوصول إلى بيانات التتبع (أو تركيب أجهزة).
  • المواطن يملك هاتفًا أو تستطيع المحطة توفير شاشة.
  • الاتفاقيات التنظيمية تسمح بعرض البيانات للعامة.

3.4 القيود

  • لا يمكن ضمان ETA دقيق دون تتبع فعلي.
  • جودة الاتصال في الحافلات تؤثر على حداثة البيانات.
  • التكامل مع أنظمة قديمة قد يتطلب وسيطًا برمجيًا.

4. أهداف المنتج ومؤشرات النجاح

4.1 الأهداف الاستراتيجية

  1. تمكين المواطن من معرفة الحافلة المناسبة لوجهته قبل ركوبها.
  2. توفير وقت وصول متوقع موثوق وشفاف.
  3. توفير معلومات متساوية عبر الهاتف والشاشة العامة.
  4. بناء أساس تقني قابل للتوسع لمدن أخرى.

4.2 مؤشرات النجاح (90 يومًا)

المؤشركيف يُقاسالحد الأدنىالمستهدف
دقة ETAMAE من eta_snapshots< 180s< 120s
تغطية GPS% وقت التشغيل بتدفق حديث> 80%> 95%
استخدام المواطنينجلسات فريدة/يوم> 200> 500
رضا المستخدمNPS> 20> 40
وضوح الوجهة% “عرفت وجهتي”> 60%> 80%
الالتزام بالمواعيدOTP (±5 دقائق)> 75%> 85%
استقرار APIUptime شهري> 99%> 99.5%

قاعدة القرار: إذا تحققت الحدود الدنيا في 3 من 7 معايير بعد 4 أسابيع → المضي للتوسع.


5. المعمارية التقنية

5.1 المبادئ التصميمية

المبدأالتطبيق الهندسي
الصدق في البياناتeta.state إلزامي في كل استجابة
العدالة الرقميةKiosk + QR + WCAG AA
عدم افتراض الشراءDiscovery قبل أي اقتناء
التدرجخط واحد → قياس → توسع
البساطةصفحة واحدة + 3 تبويبات + بحث بالوجهة

5.2 المعمارية العامة

text

┌─────────────────────────────────────────────────────────────┐
│                    طبقة العرض (Presentation)                 │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ تطبيق المواطن │  شاشة المحطة  │  QR Web Page │  لوحة الإدارة   │
│ (PWA/React)  │ (Kiosk Web)  │ (Responsive) │  (React Admin)  │
└──────┬───────┴──────┬───────┴──────┬───────┴───────┬────────┘
       │              │              │               │
       └──────────────┴──────┬───────┴───────────────┘
                             │  HTTPS / WebSocket
                    ┌────────▼────────┐
                    │   API Gateway   │
                    │  (Rate Limit,   │
                    │   Auth, Cache)  │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
┌───────▼──────┐   ┌─────────▼────────┐  ┌───────▼────────┐
│ Tracking Svc │   │  ETA Engine      │  │  Trip/Route    │
│ (Ingest GPS) │   │  (Prediction)    │  │  Service       │
└───────┬──────┘   └─────────┬────────┘  └───────┬────────┘
        │                    │                    │
        └────────────────────┼────────────────────┘
                             │
                    ┌────────▼────────┐
                    │  Message Bus    │
                    │ (Redis Streams) │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
┌───────▼──────┐   ┌─────────▼────────┐  ┌───────▼────────┐
│ Time-Series  │   │  Relational DB   │  │  Cache Layer   │
│ DB (GPS)     │   │ (Routes/Stops)   │  │  (Redis)       │
│ TimescaleDB  │   │  PostgreSQL      │  │                │
└──────────────┘   └──────────────────┘  └────────────────┘

5.3 المكونات

أ. Tracking Ingestion Service

  • يستقبل بيانات GPS كل 5–10 ثوانٍ (HTTP/MQTT).
  • يتحقق من صحة البيانات (نطاق الإحداثيات، صحة الطابع الزمني، السرعة).
  • يخزن في TimescaleDB (تاريخ) + Redis (حالة لحظية بـ TTL 120s).
  • كشف انقطاع الإشارة: إذا لم تصل بيانات خلال 30 ثانية → 🔴.

ب. ETA Engine

  • المستوى 1: المسافة ÷ متوسط السرعة (Basic).
  • المستوى 2: تكامل مع بيانات مرور (Traffic-aware).
  • المستوى 3: نموذج XGBoost على البيانات التاريخية (ML).
  • مخرجات ثلاثية إلزامية + confidence_score.

ج. Trip/Route Service

  • قاعدة بيانات الخطوط، المحطات، المسارات، المواعيد.
  • تمييز صريح: LOCAL vs INTERCITY.
  • فصل scheduled_departure عن scheduled_arrival.

د. Notification Service

  • Push (FCM) + SMS fallback.
  • قواعد: قرب الحافلة (500م أو دقيقتان)، تأخير كبير (>5 دقائق)، تغيير مسار.

هـ. Admin Control Room

  • خريطة حية للأسطول.
  • تنبيهات: أجهزة منقطعة، رحلات متأخرة، حافلات لم تغادر.
  • تقارير OTP.

5.4 قرارات معمارية (ADRs)

ADR-001: اختيار ETA بثلاث حالات

  • القرار: كل استجابة ETA تحمل state إلزاميًا (LIVE/SCHEDULED/UNAVAILABLE).
  • البديل المرفوض: عرض رقم دائمًا — يضلل المستخدم.
  • النتيجة: بناء ثقة، صدق في البيانات، واجهة أكثر تعقيدًا قليلًا.

ADR-002: Redis Streams قبل Kafka

  • القرار: البدء بـ Redis Streams، الانتقال إلى Kafka عند تجاوز 10K حدث/ثانية.
  • السبب: بساطة تشغيلية في MVP، قابلية ترقية.

ADR-003: MapLibre + OSM قبل Google Maps

  • القرار: البدء مفتوح المصدر، الترقية عند الحاجة لدقة أعلى.
  • السبب: تكلفة صفرية في MVP، لا vendor lock-in.

6. نموذج البيانات

6.1 المخطط الكامل

sql

-- المراجع الجغرافية
CREATE TABLE cities (
    id UUID PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100),
    region VARCHAR(100)
);

CREATE TABLE stops (
    id UUID PRIMARY KEY,
    city_id UUID REFERENCES cities(id),
    name_ar VARCHAR(200) NOT NULL,
    code VARCHAR(20) UNIQUE,
    lat DECIMAL(10,7) NOT NULL,
    lng DECIMAL(10,7) NOT NULL,
    is_terminal BOOLEAN DEFAULT FALSE,
    has_display BOOLEAN DEFAULT FALSE,
    qr_enabled BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- الخطوط والمسارات
CREATE TYPE route_type AS ENUM ('LOCAL', 'INTERCITY');

CREATE TABLE routes (
    id UUID PRIMARY KEY,
    code VARCHAR(20) NOT NULL,
    name_ar VARCHAR(200) NOT NULL,
    type route_type NOT NULL,
    origin_city_id UUID REFERENCES cities(id),
    destination_city_id UUID REFERENCES cities(id),
    is_active BOOLEAN DEFAULT TRUE,
    color_hex VARCHAR(7),
    UNIQUE(code, type)
);

CREATE TABLE route_stops (
    route_id UUID REFERENCES routes(id) ON DELETE CASCADE,
    stop_id UUID REFERENCES stops(id),
    sequence INT NOT NULL,
    distance_from_prev_m INT,
    avg_travel_sec_from_prev INT,
    PRIMARY KEY (route_id, sequence)
);

-- المركبات
CREATE TYPE vehicle_status AS ENUM ('ACTIVE','IDLE','MAINTENANCE','OFFLINE');

CREATE TABLE vehicles (
    id UUID PRIMARY KEY,
    plate VARCHAR(20) UNIQUE NOT NULL,
    gps_device_id VARCHAR(50) UNIQUE,
    gps_protocol VARCHAR(20),
    capacity INT,
    status vehicle_status DEFAULT 'IDLE',
    last_seen_at TIMESTAMPTZ,
    last_lat DECIMAL(10,7),
    last_lng DECIMAL(10,7),
    last_speed_kmh DECIMAL(5,2),
    last_heading INT
);

-- الرحلات
CREATE TYPE trip_status AS ENUM (
    'SCHEDULED','BOARDING','IN_TRANSIT','ARRIVED','CANCELLED','DELAYED'
);

CREATE TABLE trips (
    id UUID PRIMARY KEY,
    route_id UUID REFERENCES routes(id),
    vehicle_id UUID REFERENCES vehicles(id),
    driver_id UUID,
    scheduled_departure TIMESTAMPTZ NOT NULL,
    scheduled_arrival TIMESTAMPTZ NOT NULL,
    actual_departure TIMESTAMPTZ,
    actual_arrival TIMESTAMPTZ,
    status trip_status DEFAULT 'SCHEDULED',
    direction SMALLINT DEFAULT 1,
    created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_trips_route_time ON trips(route_id, scheduled_departure);

-- قراءات GPS (TimescaleDB)
CREATE TABLE gps_readings (
    time TIMESTAMPTZ NOT NULL,
    vehicle_id UUID NOT NULL,
    trip_id UUID,
    lat DECIMAL(10,7) NOT NULL,
    lng DECIMAL(10,7) NOT NULL,
    speed_kmh DECIMAL(5,2),
    heading INT,
    accuracy_m DECIMAL(6,2),
    odometer_km DECIMAL(10,2),
    ignition BOOLEAN,
    source VARCHAR(20) DEFAULT 'GPS'
);
SELECT create_hypertable('gps_readings', 'time');
CREATE INDEX idx_gps_vehicle_time ON gps_readings(vehicle_id, time DESC);

-- ETA Snapshots (للتقييم)
CREATE TABLE eta_snapshots (
    id BIGSERIAL PRIMARY KEY,
    trip_id UUID,
    stop_id UUID,
    computed_at TIMESTAMPTZ NOT NULL,
    eta_seconds INT,
    confidence DECIMAL(3,2),
    data_state VARCHAR(12),
    method VARCHAR(20),
    actual_arrival_at TIMESTAMPTZ,
    error_seconds INT
);

-- التنبيهات
CREATE TABLE alerts (
    id UUID PRIMARY KEY,
    trip_id UUID,
    type VARCHAR(30),
    severity SMALLINT,
    message_ar TEXT,
    created_at TIMESTAMPTZ DEFAULT now(),
    resolved_at TIMESTAMPTZ
);

6.2 سياسة الاحتفاظ بالبيانات

البياناتالمدةالسبب
GPS خام90 يومًاتحليل + تحسين النموذج
GPS مجمّع2 سنةتقارير + اتجاهات
eta_snapshots1 سنةتقييم الدقة
التنبيهات6 أشهرمراجعة تشغيلية
سجلات الإدارة2 سنةتدقيق

7. عقود واجهات البرمجة (API Contracts)

7.1 GET /api/v1/stops/{stop_id}/arrivals

الوصف: الحافلات القادمة إلى محطة معينة.

الاستجابة:

json

{
  "stop": {
    "id": "uuid",
    "name_ar": "محطة مركز الشامل",
    "lat": 16.889,
    "lng": 42.551,
    "last_updated": "2026-10-20T14:32:10Z"
  },
  "arrivals": [
    {
      "trip_id": "uuid",
      "route_code": "07A",
      "route_type": "LOCAL",
      "destination_ar": "المنطقة الشمالية",
      "vehicle_plate": "أ ب ج 1234",
      "eta": {
        "state": "LIVE",
        "seconds": 420,
        "display_ar": "باقي 7 دقائق",
        "confidence": 0.92,
        "data_age_sec": 8,
        "method": "TRAFFIC"
      },
      "next_stops": [
        {"name_ar": "المحطة 2", "sequence": 4},
        {"name_ar": "المحطة 3", "sequence": 5}
      ]
    },
    {
      "trip_id": "uuid2",
      "route_code": "12B",
      "route_type": "LOCAL",
      "destination_ar": "المنطقة الجنوبية",
      "eta": {
        "state": "SCHEDULED",
        "scheduled_at": "2026-10-20T14:45:00Z",
        "display_ar": "مجدول 2:45 م",
        "confidence": null,
        "data_age_sec": null
      }
    },
    {
      "trip_id": "uuid3",
      "route_code": "204",
      "route_type": "INTERCITY",
      "destination_ar": "أبها",
      "eta": {
        "state": "UNAVAILABLE",
        "display_ar": "غير مؤكد — آخر تحديث 2:28 م",
        "last_known_at": "2026-10-20T14:28:00Z"
      }
    }
  ]
}

قاعدة إلزامية: حقل eta.state موجود في كل استجابة. لا يُسمح بعرض رقم دون حالة.

7.2 GET /api/v1/admin/fleet/live

الوصف: حالة الأسطول الحية (لوحة الإدارة).

json

{
  "summary": {
    "total": 42, "active": 35, "idle": 4, "offline": 3,
    "on_time_pct": 88.5, "avg_delay_sec": 95
  },
  "vehicles": [
    {
      "vehicle_id": "uuid",
      "plate": "أ ب ج 1234",
      "trip_id": "uuid",
      "route_code": "07A",
      "status": "IN_TRANSIT",
      "position": {"lat": 16.89, "lng": 42.55, "speed_kmh": 34},
      "gps_age_sec": 6,
      "health": "OK",
      "next_stop": {"name_ar": "المحطة 3", "eta_sec": 120},
      "delay_sec": 45
    }
  ],
  "alerts": [
    {"type": "GPS_LOST", "vehicle_plate": "س ص ع 5678", "since": "2026-10-20T14:20:00Z", "severity": 2},
    {"type": "DELAY", "trip_id": "uuid", "delay_sec": 480, "severity": 2}
  ]
}

7.3 POST /api/v1/ingest/gps

الوصف: استقبال قراءة GPS من جهاز تتبع.

الطلب:

json

{
  "device_id": "GPS-00123",
  "ts": "2026-10-20T14:32:08Z",
  "lat": 16.8891,
  "lng": 42.5512,
  "speed_kmh": 32.5,
  "heading": 145,
  "accuracy_m": 5.0,
  "ignition": true,
  "odometer_km": 12450.3
}

الاستجابة: 202 Accepted + {"next_expected_in_sec": 5}

التحقق: lat ∈ [-90,90]، lng ∈ [-180,180]، ts ضمن ±5 دقائق، speed_kmh ∈ [0,200].

7.4 wss://api/ws/live?stop_id=...

الوصف: تحديثات لحظية عبر WebSocket.

json

{"type": "vehicle_moved", "trip_id": "uuid", "eta_sec": 380, "state": "LIVE"}
{"type": "alert", "trip_id": "uuid", "type": "DELAY", "delta_sec": 120}

7.5 رموز الأخطاء الموحدة

الرمزHTTPالمعنى
invalid_lat400إحداثي عرض خارج النطاق
timestamp_out_of_range400طابع زمني غير منطقي
unknown_device404جهاز غير مسجل
rate_limited429تجاوز حد الطلبات
unauthorized401بدون توكن
forbidden403صلاحية غير كافية
internal500خطأ داخلي

8. محرك وقت الوصول المتوقع (ETA Engine)

8.1 المنطق الإلزامي

python

class ETAState(str, Enum):
    LIVE = "LIVE"
    SCHEDULED = "SCHEDULED"
    UNAVAILABLE = "UNAVAILABLE"

GPS_STALE_THRESHOLD_SEC = 30
MIN_SPEED_FLOOR_KMH = 5.0

def compute_eta(vehicle, trip, target_stop, route_stops, now=None) -> ETAResult:
    now = now or datetime.utcnow()

    # 1) هل يوجد جهاز GPS؟
    if not vehicle.gps_device_id or vehicle.last_seen_at is None:
        return _scheduled_or_unavailable(trip, reason="no_gps")

    # 2) هل البيانات حديثة؟
    data_age = (now - vehicle.last_seen_at).total_seconds()
    if data_age > GPS_STALE_THRESHOLD_SEC:
        return ETAResult(
            state=ETAState.UNAVAILABLE,
            display_ar=f"غير مؤكد — آخر تحديث {_fmt_time(vehicle.last_seen_at)}",
            data_age_sec=data_age,
            last_known_at=vehicle.last_seen_at,
        )

    # 3) هل الرحلة مجدولة فقط؟
    if trip.status == "SCHEDULED" and vehicle.last_speed_kmh is None:
        return _scheduled_or_unavailable(trip, reason="scheduled")

    # 4) حساب ETA
    remaining_m = _remaining_distance_m(
        vehicle.last_lat, vehicle.last_lng, target_stop, route_stops, trip
    )
    speed_kmh = max(vehicle.last_speed_kmh or 0, MIN_SPEED_FLOOR_KMH)
    eta_sec = int(remaining_m / (speed_kmh * 1000 / 3600))
    confidence = _confidence(data_age, vehicle.last_speed_kmh)

    return ETAResult(
        state=ETAState.LIVE,
        seconds=eta_sec,
        display_ar=f"باقي {humanize_ar(eta_sec)}",
        confidence=confidence,
        data_age_sec=data_age,
        method="BASIC",
    )

8.2 مستويات ETA

المستوىالمعادلةشرط التفعيل
Level 1 (Basic)distance / max(avg_speed, floor)MVP
Level 2 (Traffic)basic × traffic_factorبعد تكامل مرور
Level 3 (ML)نموذج XGBoostبعد 5000 snapshot

8.3 حساب الثقة (Confidence)

python

def _confidence(data_age: float, speed: float | None) -> float:
    age_score = max(0.0, 1.0 - (data_age / GPS_STALE_THRESHOLD_SEC))
    speed_score = 1.0 if speed and speed > 10 else 0.6
    return round(age_score * speed_score, 2)

إذا confidence < 0.5 → الواجهة تعرض تحذيرًا خفيًا “تقدير تقريبي”.

8.4 معايير الجودة

  • MAE < 120 ثانية في 80% من الحالات.
  • لا عرض LIVE إذا data_age > 30s.
  • لا عرض رقم إذا confidence < 0.3.

9. واجهات المستخدم

9.1 مبادئ UX

  • البساطة: صفحة واحدة + تنقل سفلي بثلاث تبويبات.
  • البحث بالوجهة لا برقم الخط (المواطن لا يحفظ الأرقام).
  • حالة ETA مرئية دائمًا (لون + نص).
  • العدالة: Kiosk + QR لمن لا يملك هاتفًا.
  • إمكانية الوصول: WCAG 2.1 AA.

9.2 شاشة الخريطة (الافتراضية)

text

┌─────────────────────────────────────┐
│  ☰  Zezo Transit         🔔 (3)  ⚙️ │
├─────────────────────────────────────┤
│   🔍  إلى أين تريد الذهاب؟          │
│   ┌───────────────────────────────┐ │
│   │ [ابحث عن منطقة أو مدينة]      │ │
│   └───────────────────────────────┘ │
│  ┌─────────────────────────────────┐│
│  │      🗺️  الخريطة                ││
│  │   🚌(07A) ──►  🚌(12B)         ││
│  │      📍 موقعك     🚌(204)       ││
│  └─────────────────────────────────┘│
│  ┌─ الحافلات القريبة ─────────────┐ │
│  │ 🟢 07A  المنطقة الشمالية       │ │
│  │    باقي 7 دقائق • 1.8 كم       │ │
│  ├────────────────────────────────┤ │
│  │ 🟡 12B  المنطقة الجنوبية       │ │
│  │    مجدول 2:45 م                │ │
│  ├────────────────────────────────┤ │
│  │ 🔴 204  أبها (بين المدن)       │ │
│  │    غير مؤكد — آخر تحديث 2:28   │ │
│  └────────────────────────────────┘ │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

9.3 شاشة تفاصيل الرحلة (حالة LIVE)

text

┌─────────────────────────────────────┐
│  ←  الحافلة 07A              ⋮     │
├─────────────────────────────────────┤
│  🟢 في الطريق                       │
│  ┌─────────────────────────────────┐│
│  │   باقي 7 دقائق                  ││
│  │   ████████████░░░░  68%         ││
│  │   آخر تحديث: قبل 8 ثوانٍ        ││
│  └─────────────────────────────────┘│
│  📍 الوجهة: المنطقة الشمالية        │
│  🚏 المحطة القادمة: محطة 3          │
│  🚌 اللوحة: أ ب ج 1234              │
│  ┌─ المحطات القادمة ───────────────┐│
│  │ ● محطة 3      باقي 2 دقيقة      ││
│  │ ○ محطة 4      باقي 5 دقائق      ││
│  │ ○ النهائية    باقي 15 دقيقة     ││
│  └────────────────────────────────┘│
│  [ 🔔 نبّهني عند اقتراب الحافلة ]  │
│  [ 🗺️ عرض المسار على الخريطة ]     │
├─────────────────────────────────────┤
│   🗺️ الخريطة  │  🧭 رحلاتي  │ 🔔  │
└─────────────────────────────────────┘

9.4 شاشة المحطة (Kiosk)

text

┌─────────────────────────────────────────────┐
│  🚌 محطة مركز الشامل                        │
│  ⏰ 2:32 م  •  تحديث تلقائي                  │
├─────────────────────────────────────────────┤
│  الخط   │ الوجهة              │ الوصول     │
│  ───────┼─────────────────────┼─────────── │
│  🟢 07A │ المنطقة الشمالية    │ 7 دقائق    │
│  🟡 12B │ المنطقة الجنوبية    │ 2:45 م     │
│  🔴 204 │ أبها (بين المدن)    │ غير مؤكد   │
│  🟢 15C │ المنطقة الشرقية    │ 18 دقيقة   │
├─────────────────────────────────────────────┤
│  📱 امسح الرمز لمتابعة الحافلات على هاتفك   │
│  ┌───────┐  QR → /stop/{id}                 │
│  └───────┘                                  │
├─────────────────────────────────────────────┤
│  ⚠️ تأكد من رقم الخط والوجهة قبل الصعود     │
└─────────────────────────────────────────────┘

قواعد Kiosk: تحديث تلقائي كل 10 ثوانٍ (WebSocket) — خط كبير — تباين عالٍ — QR يفتح صفحة ويب خفيفة — إعلان صوتي اختياري.


10. الأمان وحماية البيانات

10.1 المصادقة والتفويض

الفئةآلية المصادقةالصلاحيات
المواطنمجهول + JWT خفيف (اختياري)قراءة البيانات العامة
شاشة المحطةAPI Key ثابت (kiosk)قراءة محطة واحدة
جهاز GPSmTLS + device certificateكتابة قراءات فقط
موظف الإدارةOIDC (SSO) + MFARBAC حسب الدور
مدير النظامOIDC + MFA + IP allowlistكامل

10.2 الأدوار (RBAC)

text

viewer:      قراءة لوحة الإدارة
operator:    + حل التنبيهات
supervisor:  + تعديل الرحلات، إدارة الأسطول
admin:       + إدارة المستخدمين والإعدادات

10.3 حماية البيانات الشخصية

  • موقع السائق لا يُعرض للمواطن — فقط موقع المركبة.
  • إخفاء الهوية: التحليلات على vehicle_id لا driver_id.
  • موافقة صريحة على الموقع + إمكانية السحب.
  • توافق مع GDPR/PDPL: أساس المعالجة، حق الوصول، حق الحذف.

10.4 حماية API

  • Rate limiting: 60 طلب/دقيقة للمواطن، 600 للـ kiosk.
  • CORS محدود للنطاقات المعروفة.
  • Input validation صارم.
  • Audit log لكل وصول إداري.
  • HTTPS إلزامي + HSTS.

11. البنية التحتية والنشر

11.1 Docker Compose (MVP)

yaml

version: "3.9"
services:
  postgres:
    image: timescale/timescaledb:latest-pg16
    environment: { POSTGRES_DB: zstp, POSTGRES_PASSWORD: ${DB_PASS} }
    volumes: ["pgdata:/var/lib/postgresql/data", "./migrations:/docker-entrypoint-initdb.d"]
    ports: ["5432:5432"]

  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes: ["redisdata:/data"]
    ports: ["6379:6379"]

  ingest:
    build: ./services/ingest
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASS}@postgres:5432/zstp
      REDIS_URL: redis://redis:6379
    depends_on: [postgres, redis]
    ports: ["3001:3001"]

  api:
    build: ./services/api
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASS}@postgres:5432/zstp
      REDIS_URL: redis://redis:6379
    depends_on: [postgres, redis]
    ports: ["3000:3000"]

  ws:
    build: ./services/ws
    environment: { REDIS_URL: redis://redis:6379 }
    depends_on: [redis]
    ports: ["8081:8081"]

  pwa:    { build: ./apps/citizen-pwa, ports: ["80:80"] }
  admin:  { build: ./apps/admin, ports: ["81:80"] }
  kiosk:  { build: ./apps/kiosk, ports: ["82:80"] }

  prometheus:
    image: prom/prometheus
    volumes: ["./ops/prometheus.yml:/etc/prometheus/prometheus.yml"]
    ports: ["9090:9090"]

  grafana: { image: grafana/grafana, ports: ["3002:3000"] }

volumes: { pgdata: {}, redisdata: {} }

11.2 الإنتاج (Kubernetes)

  • HPA على ingest و api حسب CPU + RPS.
  • PodDisruptionBudget لضمان التوفر.
  • NetworkPolicy لعزل قاعدة البيانات.
  • SealedSecrets لإدارة الأسرار.
  • Ingress + TLS (cert-manager).

11.3 هيكل المستودع (Monorepo)

text

zstp/
├── services/{ingest,api,ws,eta-worker}/
├── apps/{citizen-pwa,admin,kiosk}/
├── packages/{shared-types,ui}/
├── migrations/
├── ops/{prometheus.yml,grafana/,k8s/}
├── tests/{e2e,load}/
├── docs/{adr,api,runbooks}/
└── docker-compose.yml

11.4 الأدوات الموصى بها

الفئةالأداة
Backend (API)FastAPI (Python)
Backend (Ingest)Node.js + Express
FrontendReact + Vite + TypeScript
خرائطMapLibre GL + OpenStreetMap
قاعدة بياناتPostgreSQL 16 + TimescaleDB
CacheRedis 7
QueueRedis Streams → Kafka
CI/CDGitHub Actions
ContainerDocker + K8s
MonitoringPrometheus + Grafana + Loki
TracingOpenTelemetry + Tempo
Error trackingSentry
PushFirebase Cloud Messaging
SMSTwilio / Unifonic
AuthKeycloak / Auth0
i18ni18next
TestingVitest + Playwright + k6

12. خطة التنفيذ (14 أسبوعًا)

Sprint 0: Discovery (أسبوعان)

الأسبوعالمهمةالمخرج
1إرسال استبيان Discovery + مقابلةإجابات موثقة
1جرد بيانات الخطوطملف CSV/Excel
2زيارة ميدانية (5 محطات)إحداثيات + أسماء
2بوابة قرار: تحديد المسار المعماريوثيقة قرار

شجرة قرار Discovery:

text

هل يوجد GPS؟ ── لا ──► مسار التجهيز (شراء + تركيب)
     │ نعم
هل يرسل لخادم؟ ── لا ──► مسار الربط (توصيل)
     │ نعم
هل يوجد API؟ ── لا ──► مسار التكامل (وسيط)
     │ نعم
     └──► مسار طبقة العرض (الأسرع والأرخص)

Sprint 1–6: التنفيذ

SprintالمدةالمخرجDoD
1أسبوعانبنية تحتية (DB, Redis, CI/CD)POST /ingest/gps يعمل
2أسبوعانETA Level 1 + PWA MVPصفحة تعرض حافلة وهمية بـ ETA
3أسبوعانAdmin + التنبيهاتالإدارة ترى الأسطول
4أسبوعانتجربة ميدانية (خط واحد)دقة ETA مقاسة
5أسبوعKiosk + QRشاشة تعمل في محطة
6أسبوعانETA Level 2 + 3–5 خطوط + تقريرتقرير Go/No-Go

الجدول الزمني الكلي

text

الأسبوع 0: Discovery Package → الجهة المشغلة
الأسبوع 2: قرار معماري
الأسبوع 4: MVP — خط واحد
الأسبوع 6: قياس أولي
الأسبوع 8: Kiosk + QR + خطوط إضافية
الأسبوع 10: ETA Traffic + Push
الأسبوع 12: تقرير Go/No-Go
الأسبوع 14: إطلاق Production

13. خطة الاختبار والجودة

13.1 اختبارات الوحدة (ETA)

python

def test_live_state_when_data_fresh():
    vehicle = Vehicle(gps_device_id="X", last_seen_at=now()-timedelta(seconds=8),
                      last_speed_kmh=30)
    result = compute_eta(vehicle, Trip(status="IN_TRANSIT"), target_stop, route_stops)
    assert result.state == ETAState.LIVE

def test_unavailable_when_data_stale():
    vehicle = Vehicle(gps_device_id="X", last_seen_at=now()-timedelta(seconds=45))
    result = compute_eta(vehicle, Trip(status="IN_TRANSIT"), target_stop, route_stops)
    assert result.state == ETAState.UNAVAILABLE

def test_scheduled_when_no_gps_device():
    vehicle = Vehicle(gps_device_id=None, last_seen_at=None)
    trip = Trip(status="SCHEDULED", scheduled_arrival=datetime(2026,10,20,14,45))
    result = compute_eta(vehicle, trip, target_stop, route_stops)
    assert result.state == ETAState.SCHEDULED

13.2 اختبارات القبول (Gherkin)

gherkin

Scenario: Live data within threshold
  Given vehicle last_seen_at = 8 seconds ago
  When citizen requests arrivals
  Then response eta.state = "LIVE"
  And response eta.display_ar contains "باقي"

Scenario: Stale GPS data
  Given vehicle last_seen_at = 45 seconds ago
  Then response eta.state = "UNAVAILABLE"

13.3 اختبارات الحمل (k6)

javascript

export const options = {
  stages: [
    { duration: '1m', target: 50 },
    { duration: '3m', target: 100 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],
    http_req_failed: ['rate<0.01'],
  },
};

13.4 اختبارات E2E (Playwright)

  • المواطن يبحث عن وجهة ويرى ETA بحالته.
  • Kiosk يعرض الحافلات القادمة.
  • الإدارة ترى تنبيه GPS Lost.

13.5 اختبارات الأداء

السيناريوالهدف
100 طلب/ثانية على /arrivalsp95 < 300ms
50 حافلة ترسل كل 5 ثوانٍمعالجة < 100ms
WebSocket 500 اتصاللا تسرب ذاكرة

14. التشغيل والمراقبة (SRE)

14.1 مؤشرات SLI/SLO

SLISLO
توفر API99.5% شهريًا
زمن استجابة p95< 300ms
عمر بيانات GPS p95< 15 ثانية
دقة ETA (MAE)< 120 ثانية
توفر WebSocket99.0%

14.2 المراقبة

  • Metrics: Prometheus — كل خدمة تصدّر /metrics.
  • Logs: structured JSON → Loki.
  • Traces: OpenTelemetry → Tempo.
  • Dashboards: Grafana.
  • Alerting: PagerDuty/Opsgenie.

14.3 Runbook — حادث P1: انقطاع GPS

text

1. كشف: gps_ingest_rate < 10% of baseline
2. إجراء فوري (5 دقائق):
   - تحويل كل trip.status إلى UNAVAILABLE
   - إشعار فريق التشغيل
   - فحص: أجهزة أم خادم؟
3. استجابة (30 دقيقة):
   - خادم → rollback آخر نشر
   - أجهزة → التواصل مع المزود
4. تعافي: إعادة تفعيل LIVE تدريجيًا (10% → 50% → 100%)
5. مراجعة: post-mortem خلال 48 ساعة

14.4 Runbook — حادث ETA Drift

text

1. كشف: MAE > 300s لمدة 15 دقيقة
2. إجراء: تبديل إلى SCHEDULED تلقائيًا
3. تحقيق: فحص traffic API + بيانات GPS + النموذج
4. إعادة تفعيل LIVE بعد التحقق

15. إدارة المخاطر

المخاطرةالاحتمالالأثرالتخفيف
لا يوجد GPS ولا نية لتركيبهمتوسطمرتفعDiscovery مبكر + خطة تمويل
عدم تعاون الجهة في البياناتمتوسطمرتفعاتفاقية بيانات + بدائل ميدانية
دقة ETA منخفضةمرتفعمتوسطثلاث حالات + تحسين تدريجي
تعطل الشبكة في المحطاتمنخفضمتوسطPWA offline + cache
هجوم على APIمنخفضمرتفعRate limit + WAF + monitoring
تغيير في إدارة الجهةمتوسطمتوسطتوثيق + نقل معرفة
تضخم النطاقمرتفعمتوسطعقد واضح + مراحل
ضعف التبنيمتوسطمرتفعحملة إطلاق + QR + شاشات

16. الفريق والميزانية

16.1 الفريق الأدنى (MVP)

الدورالعددالمهمة
Backend Engineer2Ingestion + ETA + API
Frontend Engineer1PWA + Admin
DevOps/SRE0.5نشر ومراقبة
Data/ML0.5ETA tuning
Product/PM0.5تنسيق + تواصل
Field Coordinator1جمع بيانات المحطات

الإجمالي: ~5.5 شخص × 14 أسبوعًا.

16.2 الميزانية التشغيلية الشهرية

البندالتكلفة
استضافة (VPS/Small K8s)$150–400
قاعدة بيانات مُدارة$100–300
خرائط + مرور$200–500
Push/SMS$50–150
مراقبة$0–100
إجمالي$500–1,450

بدون تكلفة الأجهزة (إن لزم شراء GPS: $50–150 لكل حافلة).


17. اتفاقية مستوى الخدمة (SLA) — مسودة

الأطراف: الجهة المشغلة ↔ المزود.

التزامات المزود:

المؤشرالهدف
توفر API99.5% شهريًا
زمن استجابة p95< 300ms
دقة ETA (MAE)< 120s في 80%
زمن استجابة P1< 15 دقيقة

التزامات الجهة المشغلة:

  • توفير بيانات الخطوط والمحطات.
  • ضمان عمل أجهزة GPS أو التعاون لتركيبها.
  • توفير صلاحية الوصول للأنظمة القائمة.

التعويضات: إذا نزل التوفر عن 99% → شهر مجاني. إذا تجاوز MAE 180s لأسبوعين → مراجعة جذرية.

المدة: 6 أشهر تجريبية، مراجعة كل 3 أشهر.


18. خطة التوسع

18.1 التوسع الأفقي (خطوط أكثر)

  • إضافة خط: < يوم عمل (بيانات + تحقق).
  • العائق ليس تقنيًا، بل جمع بيانات المحطات.

18.2 التوسع الرأسي (مدن أخرى)

  • إعادة استخدام كل المنصة.
  • استيراد خطوط المدينة الجديدة فقط.
  • البنية: city_id في كل جدول من البداية (multi-tenant).

18.3 التوسع الوظيفي

الميزةالبنية المطلوبة
حجز مقاعدseat_reservations + دفع
تخطيط متعدد الوسائطOSRM/GraphHopper
تحليلات تنبؤيةClickHouse
مساعد ذكي (LLM)RAG على بيانات التشغيل

19. الملاحق

الملحق أ: Discovery Package (3 صفحات)

الصفحة 1 — استبيان الجهة المشغلة:

القسمالأسئلة الرئيسية
أ: أجهزة التتبعهل يوجد GPS؟ يرسل لخادم؟ معدل الإرسال؟ البروتوكول؟
ب: غرفة العملياتهل توجد؟ تُسجَّل المواقع؟ تُسجَّل أوقات الوصول؟
ج: البياناتقاعدة بيانات الخطوط؟ مواعيد موثقة؟ قابلة للتصدير؟
د: التكاملAPI؟ إمكانية تكامل؟ من يخوّل؟
هـ: الشاشاتداخلية قابلة للتحديث؟ خارجية؟ متصلة؟
و: النطاقعدد الخطوط المحلية/بين المدن/المحطات/الحافلات؟

الصفحة 2 — شجرة القرار المعماري (كما في القسم 12).

الصفحة 3 — معايير نجاح التجربة (كما في القسم 4.2).

الملحق ب: قائمة التحقق للإنتاج (Production Readiness)

البنية التحتية: backup يومي + PITR، Redis AOF، HTTPS+HSTS، CDN، DNS+SSL تلقائي.
الأمان: JWT rotation، Rate limit، Input validation، Audit log، Pen test.
المراقبة: Prometheus+Grafana، تنبيهات P1/P2، structured logging، tracing، uptime خارجي.
البيانات: Retention policy، GDPR/PDPL، إخفاء السائق، موافقة الموقع.
التشغيل: Runbook لكل حادث، on-call، staging، rollback، post-mortem template.
المنتج: 3 حالات ETA، Kiosk+QR، إشعارات، WCAG AA، i18n.

الملحق ج: المصطلحات

المصطلحالتعريف
ETAEstimated Time of Arrival — وقت الوصول المتوقع
GPSGlobal Positioning System — نظام تحديد المواقع
PWAProgressive Web App — تطبيق ويب تقدمي
Kioskشاشة معلومات عامة في المحطة
OTPOn-Time Performance — الالتزام بالمواعيد
MAEMean Absolute Error — متوسط الخطأ المطلق
RBACRole-Based Access Control — التحكم بالصلاحيات
SLO/SLIService Level Objective/Indicator
ADRArchitecture Decision Record
Discoveryمرحلة التحقق من الأنظمة القائمة

خاتمة الوثيقة

هذه الوثيقة تحوّل فكرة “متى تجي الحافلة؟” إلى منصة إنتاجية قابلة للتسليم عبر:

  1. Discovery صادق يحسم المسار قبل الاستثمار (أسبوعان).
  2. MVP على خط واحد يثبت الدقة (4 أسابيع).
  3. قياس صارم (MAE, NPS, adoption) يحدد التوسع.
  4. بنية قابلة للتوسع لمدن أخرى دون إعادة بناء.
  5. الصدق في البيانات — ثلاث حالات ETA ليست خيارًا تقنيًا، بل موقف أخلاقي.
  6. العدالة الرقمية — Kiosk و QR شرط نجاح، لا إضافة.

النتيجة: منصة Zezo Smart Transit قابلة للإطلاق خلال 14 أسبوعًا، بشرط إنجاز Discovery أولًا. والفرق بين النجاح والفشل ليس في التقنية، بل في الصدق مع البيانات والبدء صغيرًا والقياس قبل التوسع.

الخطوة التالية: اعتماد هذه الوثيقة، ثم إرسال Discovery Package للجهة المشغلة هذا الأسبوع.


نهاية الوثيقة — ZSTP-TRD-001 v1.0

Scroll to Top

🤖 موصى به لك بناءً على اهتماماتك