جسر القياس
جسر القياس هو الوصلة بين قلب القياس وسطوح التقارير. يُحوِّل التشخيص المُنتهي إلى حالات per-skill وper-domain وحالات الألواح الكلية التي يقرأها كل تقرير معلم وكل PDF للوالد وكل ملخص لوحة إدارة وكل تجميع متعدد المدارس. وهو إسقاط من جانب الخادم مُعلَّق على انتهاء التشخيص، ولا يُدخل أي مسارات جديدة.
قبل وصول الجسر، كان finishSession يكتب θ لكل مهارة فرعية في student_assessment_result
ثم يتوقف. لم يكن أي شيء يكتب evidence_row، ولا يستدعي إعادة حساب حالة المهارة، ولا يكتب
macro_domain_status_snapshot إطلاقاً، فكان كل سطح للتقارير والوالد ولوحة الإدارة
ومتعدد المدارس يقرأ جدولاً فارغاً في الإنتاج. يُغلق الجسر تلك الفجوة بإصدار الأدلة
واللقطات لحظة انتهاء التشخيص.
الفجوة التي يُغلقها الجسر
يُغلق الجسر شَقَّين تُركا مفتوحَين بعد انتهاء التشخيص.
- لم تكن نتائج θ تُحوَّل إلى
evidence_row. لم يكن لدى محرك حالة المهارة أي دليل لكل مهارة فرعية يُعيد الحساب منه. - كتب محرك القرار كل
domain_status_snapshotلكن لم يكن أي شيء يجمع الألواح الكلية الستة، فظل الجدول الكلي فارغاً.
تبقى θ بالضبط حيث كانت: مُخزَّنة على student_assessment_result ويستهلكها بَذر التدرب.
الجسر إصدار موازٍ، لا بديل. لا يُحرِّك θ ولا يُعيد تسجيل أي شيء.
سلسلة التحفيز
يجري التدفق المُطبَّق بهذا الشكل:
finishSession (modules/session/diagnostic.orchestrator.ts)
├─ (in the finish tx, first-finish gated) writeScreeningEvidence → evidence_row {correct,total}
└─ (AFTER the tx commits) projectBridge (modules/sse/bridge/diagnostic-bridge.ts)
├─ recomputeForEvidence × N sub-skills → skill_status_snapshot + domain_status_snapshot
└─ recomputeMacroForStudent ONCE → macro_domain_status_snapshot (6 tiles + GLOBAL)حقائق رئيسية:
- يجري الجسر بعد إيداع معاملة الانتهاء، لا متداخلاً داخلها. والسبب اثنان. يجب أن يكون
evidence_rowدائماً قبل أن تُعيدrecomputeForEvidenceقراءته، ويجب ألا يمتد قفل الانتهاء على(org, profile.id)ليشمل قفل حالة المهارة على(org, student, window). - السلسلة كلها متزامنة وداخل العملية. لا يوجد طابور ولا وسيط، لأن التجربة تعمل على نحو 30 مدرسة.
- العطل بين الإيداع والجسر يُصلح نفسه عند إعادة الانتهاء، لأن كل كتابة حتمية مُثبَّتة بالإصدار.
- المرور الكلي مُهدَّأ (debounced): تدفع N مهارة فرعية N إعادة حساب للمهارة والنطاق، لكن يجري مرور كلي واحد بالضبط على أحدث المُغذِّيات المُودَعة.
ما يحمله evidence_row (أعداد، لا θ)
يستهلك محرك حالة المهارة نتيجة تمحيص من 3 بنود لكل مهارة فرعية، لا θ. وفق قاعدة تمحيص
المهارة في v5، تُحلّ البنود الثلاثة كالتالي: 3/3 = no need و2/3 = monitor و
0-1/3 = training need. يكتب الجسر ذلك المُدخل مباشرةً:
evidence_row.value = { correct, total, sourceSessionId }معevidenceType = "screening_score".correctمجموع منinputResponses.isCorrectالمُصحَّحة على الخادم عبر البنود المُسجَّلة المتميزة. وهو لا يُشتقّ أبداً من θ، لأن مسار تسجيل ثانياً سيكون خطراً على V-6.studentId = StudentProfile.idمن البداية للنهاية.- النِّظام الاحتياطي للحتمية هو بوابة أول انتهاء في
finishSession. جدولevidence_rowنفسه لا يحمل قيد UNIQUE. - نوافذ
continuousتُنتج لا دليل تمحيص. تُرجعscreeningCycleToEvidenceWindowقيمة null ويُتخطَّى الجسر. دليل التمحيص يكون BOY أو MOY أو EOY فقط.
الإسقاط الكلي (6 ألواح + GLOBAL)
تعيش writeMacroSnapshot في repository/snapshot-writer.ts، مسار الكتابة الوحيد لحالة
المهارة الذي يفرضه الـ lint. تجمع engine/macro-rollup.ts النقية عبر rollupMacroTile
أحدث إصدارات domain_status_snapshot المُودَعة لكل مُغذٍّ في الألواح الستة بالإضافة إلى
GLOBAL.
التجميع الأسوأ يغلب وليس رقمياً أبداً (FR-MDR-1). سلم الأسبقية هو Severe ثم Below ثم
Approaching ثم Partial ثم Contextual ثم Meets ثم Not_Assessed. Not_Assessed ليست صفراً
أبداً وليست متوسطاً أبداً. لآليات التجميع الكاملة انظر
محرك القرار.
يُكتب صف GLOBAL الميتا لكل طالب، مع isMetaRow=true. وهو التجميع الشامل عبر كل
المُغذِّيات ويستهلكه Profile_Rules_v2 PR2-00 DATA_INCOMPLETE. وهو صف ميتا، لا لوحة في
لوحة المعلومات أبداً.
حقيقتان إضافيتان عن اللقطة الكلية:
- يحمل
feederStatusesالشكل[{domainId, domainStatus, dsmRuleRef, engineSnapshotRef}]ليتمكن القارئ من النزول إلى أي النطاقات دفعت لوحاً. - يُجمَّد
computedAtوoccurredAtإلىsession.finishedAt(V-6)، فتُعيد الصفوف الكلية تشغيلها بايتاً ببايت.
عقد مفتاح المعرِّف (StudentProfile.id مقابل User.id)
جداول لقطات حالة المهارة الثلاثة، بالإضافة إلى evidence_row وstudent_assessment_result،
تُفتِّح studentId على StudentProfile.id، لا على User.id. أما جداول سلسلة الخطة
والتنبيه اللاحقة فتبقى على فضاء معرِّفات User.id. فداخل وحدة واحدة تكون قراءة
اللقطة على StudentProfile.id بينما تكون قراءة التعيين أو التنبيه بجانبها على User.id. هذه
أكثر حقيقة إرباكاً لأي قارئ مستقبلي: لا تفترض أن فضاءي المعرِّفات قابلان للتبادل.
أصلحت كنسة مفتاح المعرِّف كل قارئ كان يقرأ لقطة على User.id، على مستويَين:
- مستهلكو الكلي المُسمَّون: التقارير وPDF لوحة الإدارة ومتعدد المدارس وPDF الوالد ودالة
readProfileInputContextفي وحدة الملف. - قُرَّاء لقطات خط القرار: bundle وrti وdata-room وism وworkflow. كلٌّ منها يحلّ User.id إلى StudentProfile.id لقراءات لقطاته فقط.
الحدّ (المُتتبَّع كـ Q-BRIDGE-5، مؤجَّل) هو أن جداول السلسلة
(student_profile_assignment وbundle_assignment وbundle_recommendation و
student_tier_status وacute_regression_alert وstudent_scaffold_assignment و
monitoring_status_review وdata_room_* وdecision_packet_*) تبقى على فضاء معرِّفات
User.id المتسق ذاتياً الذي تُمرِّره سلسلة الملف والحزمة وrti والمراقبة من البداية للنهاية.
وتطبيع فضاءات معرِّفات تلك الجداول إلى StudentProfile.id متابعة واحدة متماسكة، لا تُجزَّأ
قطعة قطعة.
كان عدم التطابق كامناً لأن اختبارات التكامل تَبذر في قاعدة بيانات لكل ملف يساوي فيها
User.id قيمة StudentProfile.id بمصادفة الزيادة التلقائية. ولا يتباعد المعرِّفان إلا حين
يُنشأ مستخدمان أو أكثر قبل الطالب. اختبار الإثبات واسع الشبكة هو
__tests__/id-key-sweep.integration.test.ts.
الإلحاق الحصري والحتمية وإعادة V-6
macro_domain_status_snapshot وevidence_row صفوف .create() بالإلحاق الحصري، يفرضها
lint-no-update-on-audit-tables.ts. تمر الكتابات الكلية فقط عبر snapshot-writer.ts،
يفرضها lint-sse-snapshot-single-write-path.ts.
قيد الحتمية UNIQUE المُطبَّق هو
@@unique([studentId, key, evidenceWindow, ruleVersion, seedVersion])، بالإضافة إلى عمود
evidenceWindow جديد. القيد القديم @@unique([studentId, key, computedAt]) لم يكن يمنع
أي تكرار، لأن computedAt يأخذ افتراضياً قيمة now() ولا يتصادم أبداً. تُجري
writeMacroSnapshot بحثاً findUnique قبل الإدراج تحت القفل الاستشاري، فيكون إعادة
التحفيز بنفس الإصدار عملية لاغية. وإعادة الانتهاء لا تكتب شيئاً جديداً.
فارغ حتى ينتهي تشخيص
الجداول الكلية فارغة في منظمة جديدة وتبقى فارغة حتى يجري finishSession لتشخيص لطالب.
وحتى ذلك الحين، تُرجع سطوح التقارير ولوحة الإدارة والوالد شكل dataIncomplete أو
Not_Assessed مع HTTP 200. تفشل مُغلقةً ولا تُرجع 404 أبداً. والتقرير الذي تكون كل قيمه
Not_Assessed قبل أن ينتهي أي تشخيص سلوك متوقَّع، لا خلل. يرتبط هذا بسلوك
dataIncomplete: true القائم الموصوف في
التقارير وPDF الوالد.
الأنظمة المرتبطة
- التشخيص والتدرّب: المُنتِج الذي يُنهي جلسة ويُحفِّز الجسر.
- محرك حالة المهارة: مستهلك
evidence_rowومسكن الكاتب الكلي. - التقارير وPDF الوالد: المستهلك الكلي الذي يقرأ اللقطات.
- محرك القرار: آليات التجميع الكاملة وصف GLOBAL الميتا.
- قاموس المصطلحات: evidence_row واللقطة الكلية وصف GLOBAL الميتا وعقد مفتاح المعرِّف والتهدئة.