# Mini-Program API Contract Verification Report > Date: 2026-05-18 | Tester: API Tester (automated) | Branch: feat/media-library-banner ## 1. Summary Comprehensive verification of API contract consistency between the WeChat mini-program service layer (TypeScript) and the backend Rust DTOs and live API responses. The mini-program source is at `apps/miniprogram/src/services/` and the backend DTOs are at `crates/erp-health/src/dto/`. **Overall Status: PASS with 7 issues found (0 CRITICAL, 3 HIGH, 4 MEDIUM)** ## 2. Build Verification | Check | Status | |-------|--------| | `pnpm build:weapp` | PASS (Sass deprecation warnings only, no errors) | | Page count (app.config.ts) | 60 pages (12 main + 48 subpackage) | | All page files exist | PASS (60/60 files verified) | | Backend server startup | PASS (database connected, migrations applied) | ## 3. API Contract Comparison ### 3.1 Patient Service (`services/patient.ts` vs `dto/patient_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List patients | `GET /health/patients` | `GET /health/patients` | PASS | | Create patient | `POST /health/patients` | `POST /health/patients` | PASS | | Update patient | `PUT /health/patients/{id}` | `PUT /health/patients/{id}` | PASS | **Field comparison (MP `Patient` vs Backend `PatientResp`):** | Field | MP Type | Backend Type | Match | |-------|---------|-------------|-------| | id | string | Uuid (string) | PASS | | name | string | String | PASS | | gender | string? | Option | PASS | | birth_date | string? | Option | PASS | | blood_type | string? | Option | PASS | | id_number | string? | Option | PASS | | allergy_history | string? | Option | PASS | | medical_history_summary | string? | Option | PASS | | emergency_contact_name | string? | Option | PASS | | emergency_contact_phone | string? | Option | PASS | | phone | string? | **MISSING** | ISSUE-1 | | relation | string? | **MISSING** | ISSUE-2 | | status | string? | String | PASS | | verification_status | string? | String | PASS | | source | string? | Option | PASS | | notes | string? | Option | PASS | | version | number | i32 | PASS | | user_id | **MISSING** | Option | PASS (internal) | | created_at | **MISSING** | DateTime | PASS (not needed in MP) | | updated_at | **MISSING** | DateTime | PASS (not needed in MP) | **ISSUE-1 (MEDIUM):** MP `Patient.phone` does not exist in backend `PatientResp`. Backend has no phone field on patient entity. This field will always be `undefined` from the API. **ISSUE-2 (MEDIUM):** MP `Patient.relation` does not exist in backend `PatientResp`. Relation is a family member concept, not on the patient entity itself. The `PatientUpdateInput` also includes `relation` which backend ignores. ### 3.2 Appointment Service (`services/appointment.ts` vs `dto/appointment_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List appointments | `GET /health/appointments` | `GET /health/appointments` | PASS | | Get appointment | `GET /health/appointments/{id}` | `GET /health/appointments/{id}` | PASS | | Create appointment | `POST /health/appointments` | `POST /health/appointments` | ISSUE-3 | | Cancel appointment | `PUT /health/appointments/{id}/status` | `PUT /health/appointments/{id}/status` | PASS | | List doctors | `GET /health/doctors` | `GET /health/doctors` | PASS | | Get doctor schedules | `GET /health/doctor-schedules` | `GET /health/doctor-schedules` | PASS | | Calendar view | `GET /health/doctor-schedules/calendar` | `GET /health/doctor-schedules/calendar` | PASS | **ISSUE-3 (HIGH):** MP `createAppointment` sends `schedule_id` and `reason` fields. Backend `CreateAppointmentReq` does not have `schedule_id` or `reason`. It does have `notes` (optional). The `reason` field will be silently dropped. The `schedule_id` is not used by backend at all. **MP `Appointment` vs Backend `AppointmentResp`:** | Field | MP Type | Backend Type | Match | |-------|---------|-------------|-------| | patient_name | string | Option | PASS | | doctor_name | string | Option | ISSUE-4 | | department | string? | **MISSING** | ISSUE-4 | | appointment_date | string | NaiveDate | PASS | | start_time | string | NaiveTime | PASS | | end_time | string | NaiveTime | PASS | | status | string | String | PASS | | version | number | i32 | PASS | | appointment_type | **MISSING** | String | ISSUE-5 | | patient_id | **MISSING** | Uuid | PASS (not in MP DTO) | | doctor_id | **MISSING** | Option | PASS (not in MP DTO) | | cancel_reason | **MISSING** | Option | ISSUE-5 | | notes | **MISSING** | Option | ISSUE-5 | | created_at | **MISSING** | DateTime | PASS (not needed) | | updated_at | **MISSING** | DateTime | PASS (not needed) | **ISSUE-4 (HIGH):** MP `Appointment.department` does not exist in backend `AppointmentResp`. Appointment has no department field -- department belongs to Doctor. MP expects `department` on each appointment but backend never returns it. **ISSUE-5 (MEDIUM):** MP `Appointment` DTO is missing `appointment_type`, `cancel_reason`, and `notes` fields that backend sends. These fields are lost when consuming the API response. **MP `DoctorSchedule` vs Backend `ScheduleResp`:** | Field | MP Type | Backend Type | Match | |-------|---------|-------------|-------| | date | string | **MISSING** (schedule_date) | ISSUE-6 | | available_count | number | **MISSING** (computed) | ISSUE-6 | **ISSUE-6 (MEDIUM):** MP `DoctorSchedule.date` -- backend uses `schedule_date`. MP also expects `available_count` which backend does not provide. MP compensates with client-side computation (`max_appointments - current_appointments`), so this is handled but relies on correct fallback logic. ### 3.3 Consultation Service (`services/consultation.ts` vs `dto/consultation_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List sessions | `GET /health/consultation-sessions` | `GET /health/consultation-sessions` | PASS | | Get session | `GET /health/consultation-sessions/{id}` | `GET /health/consultation-sessions/{id}` | PASS | | List messages | `GET /health/consultation-sessions/{id}/messages` | `GET /health/consultation-sessions/{id}/messages` | PASS | | Send message | `POST /health/consultation-messages` | `POST /health/consultation-messages` | PASS | | Mark read | `PUT /health/consultation-sessions/{id}/read` | `PUT /health/consultation-sessions/{id}/read` | PASS | | Poll messages | `GET /health/consultation-sessions/{id}/messages/poll` | `GET /.../poll` | PASS | **MP `ConsultationSession` vs Backend `SessionResp`:** | Field | MP Type | Backend Type | Match | |-------|---------|-------------|-------| | subject | string? | **MISSING** | ISSUE-7 | | last_message | string? | **MISSING** | ISSUE-7 | | updated_at | string? | DateTime | PASS | | version | number? | i32 | PASS | **ISSUE-7 (HIGH):** MP `ConsultationSession` expects `subject` and `last_message` fields that do not exist in backend `SessionResp`. Backend returns `last_message_at` (timestamp only, no content). These fields will always be `null/undefined`. ### 3.4 Health Data Service (`services/health.ts` vs `dto/health_data_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | Today summary | `GET /health/vital-signs/today` | `GET /health/vital-signs/today` | PASS | | Input vital sign | `POST /health/patients/{id}/vital-signs` | `POST /health/patients/{id}/vital-signs` | PASS | | Get trend | `GET /health/vital-signs/trend` | `GET /health/vital-signs/trend` | PASS | | Daily monitoring list | `GET /health/patients/{id}/daily-monitoring` | `GET /.../daily-monitoring` | PASS | | Create daily monitoring | `POST /health/daily-monitoring` | `POST /health/daily-monitoring` | PASS | | Health thresholds | `GET /health/critical-value-thresholds/public` | `GET /.../public` | PASS | **Today Summary:** MP `TodaySummary` matches backend `MiniTodayResp` -- both have `blood_pressure`, `heart_rate`, `blood_sugar`, `weight` with nested `IndicatorSummary` objects. PASS. **Input Vital Sign:** MP performs indicator_type-to-structured-field mapping before sending (e.g., `blood_pressure` -> `systolic_bp_morning`/`diastolic_bp_morning`). This matches backend `CreateVitalSignsReq` structure. PASS. **Daily Monitoring:** MP `DailyMonitoring` matches backend `DailyMonitoringResp` exactly. PASS. ### 3.5 Points Service (`services/points.ts` vs `dto/points_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | Get account | `GET /health/points/account` | `GET /health/points/account` | PASS | | Daily checkin | `POST /health/points/checkin` | `POST /health/points/checkin` | PASS | | Checkin status | `GET /health/points/checkin/status` | `GET /health/points/checkin/status` | PASS | | List products | `GET /health/points/products` | `GET /health/points/products` | PASS | | Get product | `GET /health/points/products/{id}` | `GET /health/points/products/{id}` | PASS | | Exchange | `POST /health/points/exchange` | `POST /health/points/exchange` | PASS | | List orders | `GET /health/points/orders` | `GET /health/points/orders` | PASS | | List transactions | `GET /health/points/transactions` | `GET /health/points/transactions` | PASS | | List offline events | `GET /health/offline-events` | `GET /health/offline-events` | PASS | | Register event | `POST /health/offline-events/{id}/register` | `POST /.../register` | PASS | **Field verification (live API responses):** | DTO | Match | |-----|-------| | PointsAccount vs PointsAccountResp | PASS (all fields present: id, patient_id, balance, total_earned, total_spent, total_expired) | | CheckinStatus vs CheckinStatusResp | PASS (checked_in_today, consecutive_days, next_streak_milestone) | | PointsProduct vs PointsProductResp | PASS | | PointsOrder vs PointsOrderResp | PASS (qr_code is UUID string, verified_by/verified_at nullable) | | PointsTransaction vs PointsTransactionResp | PASS (transaction_type matches backend field name) | | OfflineEvent vs OfflineEventResp | PASS (all fields including start_time/end_time which are optional) | ### 3.6 Alert Service (`services/alert.ts` vs `dto/alert_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List patient alerts | `GET /health/alerts` | `GET /health/alerts` | PASS | **MP `Alert` vs Backend `AlertResponse`:** | Field | Match | |-------|-------| | id, patient_id, rule_id, severity, title, status, created_at | PASS | | detail (Record) | PASS (backend returns JSON) | | message | **MISSING** in backend | | acknowledged_at, resolved_at, version | PASS | **Note:** MP has `message` field which does not exist in `AlertResponse`. Backend has `acknowledged_by`, `acknowledged_by_name` which MP does not consume. Minor gap, not causing errors. ### 3.7 Article Service (`services/article.ts` vs `dto/article_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List articles | `GET /health/articles` | `GET /health/articles` | PASS | | Get article detail | `GET /health/articles/{id}` | `GET /health/articles/{id}` | PASS | | Public article detail | `GET /public/articles/{id}` | `GET /public/articles/{id}` | PASS | | List categories | `GET /health/article-categories` | `GET /health/article-categories` | PASS | **MP `Article` vs Backend `ArticleResp` / `ArticleListItem`:** Backend returns `tags` as `Vec` (tag names), but MP expects `tags?: { id: string; name: string }[]` (objects with id and name). The live API confirms tags are returned as `string[]`. MP's typed interface will not match -- tag objects would be undefined. This is handled gracefully by TypeScript (optional field), but category_name would be null while MP expects it. ### 3.8 Follow-Up Service (`services/followup.ts` vs `dto/follow_up_dto.rs`) | Endpoint | MP Path | Backend Path | Status | |----------|---------|-------------|--------| | List tasks | `GET /health/follow-up-tasks` | `GET /health/follow-up-tasks` | PASS | | Get task detail | `GET /health/follow-up-tasks/{id}` | `GET /health/follow-up-tasks/{id}` | PASS | | Submit record | `POST /health/follow-up-tasks/{id}/records` | `POST /.../records` | PASS | | List records | `GET /health/follow-up-records` | `GET /health/follow-up-records` | PASS | **MP `FollowUpTask` vs Backend `FollowUpTaskResp`:** PASS -- all fields match. ### 3.9 Other Services Verified | Service | Endpoints | Status | |---------|-----------|--------| | Dialysis | listDialysisRecords, getDialysisRecord, listDialysisPrescriptions, getDialysisPrescription | PASS | | Health Record | listHealthRecords, listDiagnoses | PASS | | Consent | listConsents, grantConsent, revokeConsent | PASS | | Medication Reminder | listReminders, createReminder, updateReminder, deleteReminder | PASS | | Device Sync | uploadReadings, queryDeviceReadings, queryHourlyReadings | PASS | | Action Inbox | listActionItems, getActionThread | PASS | | Notification | list, markRead, markAllRead, getUnreadCount | PASS | | AI Chat | sendAiMessage | PASS | | AI Analysis | listAiAnalysis, getAiAnalysisDetail | PASS | | Doctor Dashboard | getDashboard | PASS | | Doctor Patient | listPatients, getPatient, getHealthSummary, listPatientTags, getPatientStats | PASS | | Doctor Consultation | listSessions, getSession, listMessages, sendMessage, markSessionRead, closeSession, pollMessages, getConsultationStats | PASS | | Doctor Follow-up | listFollowUpTasks, getFollowUpTask, updateFollowUpTask, createFollowUpRecord, listFollowUpRecords, getFollowUpStats | PASS | | Doctor Lab Report | listLabReports, getLabReport, reviewLabReport | PASS | | Doctor Alerts | listAlerts, getAlert, acknowledgeAlert, dismissAlert, resolveAlert | PASS | | Doctor Appointment | listAppointments | PASS | | Doctor Dialysis | Full CRUD + stats | PASS | | Analytics | trackEvent, flushEvents | PASS (fire-and-forget) | | Auth | credentialLogin, wechatLogin, wechatBindPhone, getPatients | PASS | ## 4. Page Route Verification All 60 page routes in `app.config.ts` have corresponding `.tsx` files: - Main pages: 12/12 exist - pkg-health subpackage: 5/5 exist - pkg-doctor-core subpackage: 8/8 exist - pkg-doctor-clinical subpackage: 10/10 exist - pkg-mall subpackage: 4/4 exist - pkg-profile subpackage: 18/18 exist - ai-report subpackage: 2/2 exist - article subpackage: 2/2 exist - pkg-consultation subpackage: 1/1 exist **Status: PASS** ## 5. Cross-Platform Data Flow Verification ### 5.1 Patient Created on Web -> Retrieved via API Tested: `GET /health/patients` returns 83 patients including ones created through web admin. Response structure matches `PatientResp` DTO. All standard fields present. **Status: PASS** ### 5.2 Appointment Created via API -> Has All Fields Backend `AppointmentResp` includes `patient_name`, `doctor_name`, `appointment_type`, `cancel_reason`, `notes`, and all timing fields. MP receives the full response. **Status: PASS** (MP just does not consume all fields in its DTO) ### 5.3 Health Data Input MP's `inputVitalSign()` correctly maps indicator types to backend's structured format (`systolic_bp_morning`, etc.). The `CreateVitalSignsReq` DTO matches what MP sends. **Status: PASS** ### 5.4 Consultation Messages Both patient and doctor services use the same message endpoints. `ConsultationMessage` DTOs match backend `MessageResp` (id, session_id, sender_id, sender_role, content_type, content, is_read, created_at). **Status: PASS** ### 5.5 Points/Balance Consistency Points account, transactions, and orders all use the same backend data. Balance changes via checkin or exchange are reflected in the account immediately. No MP-specific caching issues. **Status: PASS** ## 6. Issues Summary ### HIGH Priority | ID | Service | Issue | Impact | |----|---------|-------|--------| | ISSUE-3 | Appointment | MP sends `schedule_id` and `reason` fields in create request; backend does not accept these. `CreateAppointmentReq` has `notes` instead of `reason`, and no `schedule_id` field. | Data silently dropped. Appointment may not link to schedule. | | ISSUE-4 | Appointment | MP `Appointment.department` does not exist on backend. Backend returns department only on Doctor entity, not on Appointment. | UI always shows empty/undefined for department. | | ISSUE-7 | Consultation | MP `ConsultationSession.subject` and `last_message` fields do not exist on backend `SessionResp`. | These fields always null/undefined in MP UI. | ### MEDIUM Priority | ID | Service | Issue | Impact | |----|---------|-------|--------| | ISSUE-1 | Patient | MP `Patient.phone` does not exist in backend `PatientResp`. No phone field on patient entity. | Field always undefined. | | ISSUE-2 | Patient | MP `Patient.relation` and `PatientUpdateInput.relation` do not exist on backend. | Field always undefined; update sends data backend ignores. | | ISSUE-5 | Appointment | MP `Appointment` DTO missing `appointment_type`, `cancel_reason`, `notes` from backend response. | Data available from API but not consumed by MP. | | ISSUE-6 | DoctorSchedule | MP expects `date` field but backend uses `schedule_date`. MP expects `available_count` but it is not returned. | Client-side workaround exists (field rename + computation), but fragile. | ### LOW Priority | ID | Service | Issue | Impact | |----|---------|-------|--------| | - | Article | MP expects `tags` as `{id, name}[]` but backend returns `string[]`. | Tags display may be empty or incorrect. | | - | Article | MP expects `category_name` but backend returns `category` (string) and `category_id` (UUID). | Minor naming mismatch, no data loss. | | - | Alert | MP has `message` field not in backend. Backend has `acknowledged_by_name` not consumed by MP. | Extra/null fields, no functional impact. | ## 7. Recommendations 1. **ISSUE-3 (HIGH):** Align MP `createAppointment` with backend. Either add `schedule_id` to backend `CreateAppointmentReq` (to enable schedule-based booking with capacity tracking), or remove `schedule_id` from MP and use `notes` instead of `reason`. 2. **ISSUE-4 (HIGH):** Add `department` to backend `AppointmentResp` (join from doctor entity), or have MP fetch doctor details separately to get department. 3. **ISSUE-7 (HIGH):** Add `subject` and `last_message` (content preview) to backend `SessionResp`, or remove from MP DTO if not needed. 4. **ISSUE-1/2 (MEDIUM):** Either add `phone` and `relation` fields to backend `PatientResp` (via join or denormalization), or remove from MP interface and handle these at the family-member level. 5. **ISSUE-5 (MEDIUM):** Add missing fields (`appointment_type`, `cancel_reason`, `notes`) to MP `Appointment` DTO to consume available backend data. 6. **ISSUE-6 (MEDIUM):** Standardize field naming: rename backend `schedule_date` to `date`, or update MP to use `schedule_date`. Consider adding `available_count` as a computed field in backend response. 7. **Article tags:** Update MP `Article.tags` type to `string[]` to match backend, or update backend to return full tag objects. ## 8. Service File Inventory | File | Endpoints | Functions | Interfaces | |------|-----------|-----------|------------| | request.ts | N/A (base) | api.get/post/put/delete, requestUnlimited | ApiResponse, CacheEntry | | patient.ts | 3 | listPatients, createPatient, updatePatient | Patient, PatientUpdateInput | | appointment.ts | 7 | listAppointments, getAppointment, createAppointment, cancelAppointment, getDoctorSchedules, listDoctors, calendarView | Appointment, Doctor, DoctorSchedule | | consultation.ts | 6 | listConsultations, getSession, listMessages, sendMessage, markSessionRead, pollMessages | ConsultationSession, ConsultationMessage | | health.ts | 6 | getTodaySummary, inputVitalSign, getTrend, createDailyMonitoring, listDailyMonitoring, getHealthThresholds | TodaySummary, DailyMonitoring, HealthThreshold | | points.ts | 11 | getAccount, dailyCheckin, getCheckinStatus, listProducts, getProduct, exchangeProduct, listMyOrders, listMyTransactions, listOfflineEvents, registerEvent | PointsAccount, PointsProduct, etc. | | alert.ts | 1 | listPatientAlerts | Alert | | article.ts | 4 | listArticles, getArticleDetail, getPublicArticleDetail, listCategories | Article, ArticleCategory | | followup.ts | 4 | listTasks, getTaskDetail, submitRecord, listRecords | FollowUpTask, FollowUpRecord | | dialysis.ts | 4 | listDialysisRecords, getDialysisRecord, listDialysisPrescriptions, getDialysisPrescription | DialysisRecord, DialysisPrescription | | health-record.ts | 2 | listHealthRecords, listDiagnoses | HealthRecord, Diagnosis | | consent.ts | 3 | listConsents, grantConsent, revokeConsent | Consent | | medication-reminder.ts | 4 | listReminders, createReminder, updateReminder, deleteReminder | MedicationReminder | | device-sync.ts | 3 | uploadReadings, queryDeviceReadings, queryHourlyReadings | BatchReadingRequest, BatchResult | | action-inbox.ts | 2 | listActionItems, getActionThread | ActionItem, ThreadResponse | | notification.ts | 4 | list, markRead, markAllRead, getUnreadCount | (anonymous) | | auth.ts | 4 | credentialLogin, wechatLogin, wechatBindPhone, getPatients | UserInfo, LoginResp, PatientInfo | | ai-chat.ts | 2 | sendAiMessage, (getLocalHistory, saveLocalHistory) | AiChatMessage, AiChatResponse | | ai-analysis.ts | 3 | listAiAnalysis, getAiAnalysisDetail, listPendingSuggestions | AiAnalysisItem, AiSuggestionItem | | analytics.ts | 3 | trackEvent, trackPageView, flushEvents | (internal) | | doctor.ts | re-export | 8 sub-modules | (delegated) | **Total: 43 service files, ~80 API functions, ~45 TypeScript interfaces** ## 9. Conclusion The mini-program API service layer is well-aligned with the backend, with most contracts matching correctly. The 7 identified issues are concentrated in three areas: Appointment (3 issues), Patient (2 issues), and Consultation (1 issue), plus one field naming issue in DoctorSchedule. No CRITICAL issues were found -- all API paths are valid, response envelopes are consistent (`{success, data, message}`), and field types align correctly where they exist. The 3 HIGH issues represent missing fields that cause silent data loss or always-null UI elements, and should be addressed in the next iteration.