Files
railwaysystem/docs/_meta/legacy-analytics-v2-audit.md
T

9.7 KiB

Legacy Analytics V2 Contract Audit

1. Scope and evidence

  • Branch: fix/legacy-analytics-v2-contract
  • Base: d162954cd1e26c6948408a8304ca333ba9fe7a40
  • Scope: /train/report, /train/checkin, /train/duration, /train/portrait
  • Sources, in precedence order: frozen API/PRD contract, V2 migration DDL, read-only live Kingbase metadata, current V2 domain/service implementation, legacy generator code.
  • Live database: KingbaseES V009R003C018, database_mode=mysql; all four audited tables exist and contain zero rows.
  • The live check queried only metadata, constraints, indexes, counts, and non-sensitive enum distributions. No DDL or DML was executed.

2. End-to-end fact chains

2.1 Report

Layer Current fact
Vue build_ui/src/views/train/report/index.vue; generator CRUD showing raw plan/student/resource IDs
Frontend API build_ui/src/api/train/report.js; /train/report/list, /{id}, POST, PUT, DELETE
Controller TrainClassReportController; generator CRUD
Permission Backend system:report:*, frontend train:report:*; inconsistent
Service ITrainClassReportService / TrainClassReportServiceImpl; pass-through CRUD
Mapper TrainClassReportMapper.xml
Domain TrainClassReport
V2 table train_class_report
Live columns report_id, enrollment_id, report_way, report_device_sn, report_time, verify_result, verify_note, create_by, create_time, remark
Compatibility INCOMPATIBLE: legacy mapper references plan_id, student_id, report_status, alloc_dorm_id, alloc_classroom_id, alloc_site_id, none of which exists
Runtime capability before fix READ/CREATE/UPDATE fail at SQL level; DELETE could execute by ID but is not a valid report lifecycle operation
Formal contract Confirm via POST /train/enrollments/{enrollmentId}/report; plan report list is P1. Report belongs to phase 1, but generic table CRUD does not.
Disposition KEEP_READ_ONLY for this legacy list; future report confirmation remains a dedicated transactional implementation

report_status and verify_result are different concepts. Enrollment status owns not-reported/reported/leave/abandoned lifecycle; verify_result records identity verification outcome. The legacy field must be retired, not renamed. Accommodation comes from train_accommodation; classroom/site come from train_lesson scheduling relationships and must not be stored on report.

2.2 Checkin

Layer Current fact
Vue build_ui/src/views/train/checkin/index.vue; raw generator CRUD
Frontend API build_ui/src/api/train/checkin.js; /train/checkin/list, /{id}, POST, PUT, DELETE
Controller TrainStudentCheckinController; generator CRUD
Permission Backend system:checkin:*, frontend train:checkin:*; inconsistent with formal train:attendance:*
Service ITrainStudentCheckinService / implementation; pass-through CRUD
Mapper TrainStudentCheckinMapper.xml
Domain TrainStudentCheckin
V2 table train_student_checkin
Live columns checkin_id, enrollment_id, lesson_id, board_id, checkin_way, sign_in_time, sign_out_time, attendance_status, manual_flag plus audit columns
Compatibility INCOMPATIBLE: legacy mapper references plan_id, c_id, student_id, checkin_time, checkin_status, lesson_start_time, lesson_end_time
Runtime capability before fix READ/CREATE/UPDATE fail; DELETE is SQL-capable but violates the attendance audit model
Formal contract V2 attendance is directly bound to enrollment_id + lesson_id; sign-in, sign-out and manual adjustment are dedicated APIs. It is phase-1 business.
Disposition KEEP_READ_ONLY for the legacy global list; formal attendance writes remain dedicated business actions

The live V2 model proves that lesson_id is already the formal relationship. attendance_status is the stored final attendance result; it is not the separate legacy checkin_status field and must use values 0 absent, 1 normal, 2 late, 3 early leave, 4 leave. checkin_way uses 1 face, 2 card, 3 QR, 4 teacher/manual.

2.3 Duration

Layer Current fact
Vue build_ui/src/views/train/duration/index.vue; generator CRUD
Frontend API build_ui/src/api/train/duration.js; /train/duration/list, /{id}, POST, PUT, DELETE
Controller TrainStudyDurationController; generator CRUD
Permission Backend system:duration:*, frontend train:duration:*; inconsistent with formal train:learning:*
Service ITrainStudyDurationService / implementation; pass-through CRUD
Mapper TrainStudyDurationMapper.xml
Domain TrainStudyDuration
V2 table train_study_duration
Live columns dur_id, enrollment_id, plan_course_id, resource_id, learning_content, source_system, source_event_id, start_time, end_time, study_seconds, sync_time plus audit columns
Compatibility INCOMPATIBLE: legacy mapper references student_id, plan_id, c_id, device_type
Runtime capability before fix READ/CREATE/UPDATE fail; DELETE is SQL-capable but invalid for immutable source events
Formal contract P0 write path is device/system event ingestion; P1 list/detail is read-only. Phase-1 business.
Disposition KEEP_READ_ONLY

The old device_type label set describes source devices, but V2 deliberately models this as source_system plus content/resource references. No replacement column is added.

2.4 Portrait

Layer Current fact
Vue build_ui/src/views/train/portrait/index.vue; generator CRUD
Frontend API build_ui/src/api/train/portrait.js; /train/portrait/list, /{id}, POST, PUT, DELETE
Controller TrainStudentPortraitController; generator CRUD
Permission Backend system:portrait:*, frontend train:portrait:*; inconsistent
Service ITrainStudentPortraitService / implementation; pass-through CRUD
Mapper TrainStudentPortraitMapper.xml
Domain TrainStudentPortrait
V2 table train_student_portrait
Live columns portrait_id, enrollment_id, attendance_rate, theory_score, practical_score, total_study_hour, label_tag, portrait_content, generated_time plus audit columns
Compatibility INCOMPATIBLE: legacy mapper references student_id, plan_id, avg_score, checkin_rate; it also omits V2 theory/practical/generated fields
Runtime capability before fix READ/CREATE/UPDATE fail; DELETE is SQL-capable but invalid for generated analytics
Formal contract P1 generation/refresh is a system calculation; queries are read-only. Phase-1 business.
Disposition KEEP_READ_ONLY

3. CRUD reasonableness matrix

Module CREATE READ UPDATE DELETE
Report REQUIRED, but only through dedicated report-confirm business action REQUIRED ADMIN_ONLY, only through an audited correction workflow not generic CRUD NOT_REQUIRED
Checkin DEVICE_GENERATED or dedicated manual-add action REQUIRED ADMIN_ONLY, dedicated adjustment must force manual_flag=1 and preserve reason NOT_REQUIRED
Duration DEVICE_GENERATED through idempotent ingestion REQUIRED NOT_REQUIRED NOT_REQUIRED
Portrait SYSTEM_GENERATED REQUIRED SYSTEM_GENERATED refresh/recalculation NOT_REQUIRED

4. Legacy code versus V2 database compatibility matrix

Module Legacy key/model V2 key/model Missing live columns referenced by legacy mapper Before fix Target
Report plan_id + student_id; allocations embedded enrollment_id; verification event only plan_id, student_id, report_status, alloc_dorm_id, alloc_classroom_id, alloc_site_id BROKEN Joined read model; no legacy writes
Checkin plan_id + c_id + student_id + lesson times enrollment_id + lesson_id; sign-in/out and final attendance plan_id, c_id, student_id, checkin_time, checkin_status, lesson_start_time, lesson_end_time BROKEN Joined read model; no raw CRUD
Duration student/plan/course plus device_type enrollment/plan-course/resource plus source event student_id, plan_id, c_id, device_type BROKEN Joined read model; no manual CRUD
Portrait student/plan, average/checkin rate enrollment, attendance/theory/practical/time student_id, plan_id, avg_score, checkin_rate BROKEN Joined read model; no manual CRUD

5. Permission debt

All four backend controllers use legacy system:* permissions while their Vue buttons use a partially different train:* family. The formal API contract uses train:report:*, train:attendance:*, train:learning:*, and train:portrait:*. This is LEGACY_PERMISSION_TECH_DEBT and can block otherwise valid business roles. The reconciliation should update these four read paths only; a global RBAC/menu migration is outside this patch.

6. Architecture decision gate

ARCHITECT_DECISION_REQUIRED = NO for the minimal reconciliation: the frozen contract and live V2 schema agree on identifiers, ownership, read/write semantics, and the removal of legacy fields. This patch does not implement the future transactional report, attendance, learning-ingestion, or portrait-generation workflows; it only makes the retained legacy analytics views safe and truthful.

7. Existing V2 write-action gap

EXISTING_V2_WRITE_ACTION_GAP = YES.

The frozen contract declares Report confirm, Attendance sign-in, Attendance sign-out, and Attendance adjust actions, but neither the base revision nor this worktree contains their dedicated Controller/Service implementations. This is a pre-existing delivery gap, not a regression caused by converting the legacy analytics generator pages to V2 read models. Implementing these transactional actions remains outside this reconciliation.