> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# الترميز الطبي بالذكاء الاصطناعي: أين ينجح وأين يخطئ؟
- URL: https://arabroot.io/articles/الترميز-الطبي-بالذكاء-الاصطناعي/
- Published: 2026-10-12T18:15:00.000Z
- Updated: 2026-10-12T10:12:06.000Z
- Description: كيف تساعد القواعد ونماذج اللغة المرمز في اختيار الرموز، وكيف تقيس دقتها وتمنعها من اختراع رموز غير موجودة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, مجالات متخصصة, الاستضافة الذاتية

في مقال [الترميز الطبي](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A-clinical-coding/) رأينا أن المرمز يقرأ كل ملف ويختار رموز التشخيص والإجراءات بقواعد مكتوبة، وأن كل رمز ناقص قد يغير المبلغ، وأن المطالبة المرفوضة ترجع إليه ليصححها. والمستشفى الكبير يخرج منه مئات المرضى كل يوم، فمن الطبيعي أن يسأل المدير: لماذا لا يقرأ الحاسب الملف ويقترح الرموز، ويكتفي المرمز بالمراجعة؟ وهذه الفكرة ليست جديدة، فهي معروفة باسم الترميز بمساعدة الحاسب Computer-Assisted Coding واختصاراً CAC، والجديد أن نماذج اللغة الكبيرة LLM تبدو وكأنها تقرأ الملاحظة كما يقرؤها الإنسان، فهل تستطيع أن ترمزها أيضاً؟

وسوف نتناول في هذا المقال ما يلي:

- الطرق الثلاث للترميز بمساعدة الحاسب: القواعد، ونماذج معالجة اللغة الطبيعية NLP المدربة، ونماذج اللغة الكبيرة.
- لماذا تبقى مراجعة المرمز، وماذا تقول الدراسات المنشورة عن دقة نماذج اللغة في الترميز.
- كيف تقيس الأداة: الدقة Precision والاستدعاء Recall وF1 على مستوى الرمز، ولماذا مقياس المطابقة التامة Exact Match قاس.
- المخاطر: الترميز الأعلى، والرموز المخترعة، والانحياز، وخصوصية البيانات الصحية بحسب نظام حماية البيانات الشخصية.
- مختبر على 26 ملاحظة طبية اصطناعية كتبناها بالعربية والإنجليزية: طريقة بسيطة بالكلمات المفتاحية، ثم نموذجان محليان عبر Ollama، ثم النموذج مع البحث في ملف الرموز الرسمي، وحاجز يرفض أي رمز غير موجود.

## ما هو الترميز بمساعدة الحاسب؟

الترميز بمساعدة الحاسب هو برنامج يقرأ توثيق الزيارة ويقترح الرموز مع الجملة التي اعتمد عليها في الملف، ثم يقبلها المرمز أو يعدلها أو يرفضها، والكلمة المهمة هنا «يقترح». والسبب أن المسؤول عن المطالبة أمام شركة التأمين وأمام المدقق هو المرمز والمنشأة، وليس البرنامج، ودليل تطبيق DRG من [مجلس الضمان الصحي](https://www.chi.gov.sa/en/Rules/MedicalStandards/CHI-DRG-ImplementationGuidelines.pdf?ref=arabroot.io) يطلب أن يكون الترميز في التنويم من مرمزين يحملون شهادة معتمدة أو في طريقهم إليها. والصورة التالية تبين الطرق الثلاث التي تبنى بها هذه البرامج، وأين تلتقي:

![الملاحظة الطبية تدخل في ثلاث طرق: القواعد Rules بقواميس وكلمات نفي، ومثالها HTN إلى I10 مع تجاهلها إذا سبقتها no، ونقاط قوتها أنها متوقعة وقابلة للتفسير ورخيصة، ونقاط ضعفها أنها تنكسر مع الصياغات الجديدة وكل رمز دقيق يحتاج قاعدة. والمصنف المدرب Trained NLP classifier يتعلم من ملاحظات مرمزة سابقاً، ومثاله CAML على MIMIC-III بمقياس micro-F1 0.54، ويتعلم الصياغات الحقيقية ولكنه يحتاج ملاحظات مرمزة كثيرة والرموز النادرة لا يتعلمها تقريباً. ونموذج اللغة الكبير يقرأ النفي وتاريخ العائلة والعربية مع الإنجليزية دون قواعد ولكنه قد يخترع رموزاً غير موجودة، ومثاله GPT-4 بمطابقة تامة 33.9% لرموز ICD-10-CM. والثلاث تنتهي في حواجز Guardrails تتحقق من وجود الرمز في الملف الرسمي ومن وجود الدليل في الملاحظة، ثم المرمز الذي يقبل أو يعدل أو يرفض](https://arabroot.io/content/images/2026/10/ai-medical-coding-01-approaches.webp)

كل طريقة لها نقاط قوة ونقاط ضعف، والقرار الأخير عند المرمز في الثلاث

## الطريقة الأولى: القواعد والقواميس

أبسط برنامج ترميز هو قاموس: إذا وجدت الكلمة «hypertension» أو «HTN» أو «ارتفاع ضغط الدم» فاقترح `I10`. وهذه الطريقة تعمل من أول يوم، ولا تحتاج بيانات تدريب، وكل اقتراح فيها له سبب واضح تستطيع أن تريه للمرمز. ولكنها تنكسر بسرعة: فالجملة «لا يوجد سكري» فيها كلمة «سكري»، والجملة «الأم مصابة بالسكري» كذلك، والطبيب يكتب «DM2» مرة و«T2DM» مرة و«سكري نوع ثاني» مرة، وكل رمز دقيق مثل السكري مع مرض الكلى يحتاج قاعدة خاصة. لذلك أضاف الباحثون منذ سنوات طبقة لكشف السياق: كلمات نفي مثل «no» و«denies» و«لا يوجد» قبل المصطلح، وكلمات شك مثل «possible» و«rule out»، وكلمات تدل على أحد الأقارب، وهذا ما سوف نبنيه في المختبر لنرى أين ينجح وأين يفشل.

## الطريقة الثانية: مصنفات NLP مدربة على ملاحظات مرمزة

الطريقة الثانية تعامل الترميز كمسألة تصنيف متعدد التسميات Multi-label Classification: تأخذ آلاف الملاحظات التي رمزها مرمزون سابقاً، وتدرب نموذجاً يتوقع من النص مجموعة الرموز، كما شرحنا فكرة التعلم من الأمثلة في مقال [التعلم الآلي للمبتدئين](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A7%D9%84%D9%8A-machine-learning-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/). ومن أشهر الأوراق في هذا المجال [CAML](https://arxiv.org/abs/1802.05695?ref=arabroot.io) (NAACL 2018) التي دربت شبكة بالانتباه Attention على ملخصات الخروج في قاعدة MIMIC-III وحصلت على micro-F1 بقيمة 0.54، أي أن أفضل نموذج منشور في وقتها كان يخطئ في نحو نصف الرموز تقريباً على تلك المهمة. والمشكلة الأساسية في هذه الطريقة أمران: تحتاج ملاحظات مرمزة كثيرة بنفس اللغة ونفس نظام الرموز (وهذا نادر للعربية ولنظام ICD-10-AM)، والرموز النادرة تظهر مرات قليلة في البيانات فلا يتعلمها النموذج تقريباً، وهي أحياناً أغلى الرموز.

## الطريقة الثالثة: نماذج اللغة الكبيرة

نموذج اللغة الكبير لم يتدرب على مستشفاك، ولكنه قرأ نصوصاً طبية كثيرة، فيفهم النفي وتاريخ العائلة والاختصارات والخلط بين العربية والإنجليزية دون أن تكتب له قاعدة، وهذا ما يجعل الفكرة مغرية. وقد شرحنا كيف يولد النموذج النص كلمة بعد كلمة في مقال [كيف تعمل نماذج اللغة الكبيرة](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D9%83%D8%A8%D9%8A%D8%B1%D8%A9-llm-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9/)، وهذه هي المشكلة بالضبط: الرمز `E11.22` بالنسبة للنموذج سلسلة من التوكنات يولدها لأنها محتملة، وليس قيمة يبحث عنها في جدول، لذلك يستطيع أن يولد رمزاً قريباً من الصحيح أو رمزاً لا وجود له بنفس الثقة.

وأوضح دراسة منشورة على ذلك هي [Large Language Models Are Poor Medical Coders](https://doi.org/10.1056/AIdbp2300040?ref=arabroot.io) في مجلة NEJM AI سنة 2024، حيث أعطى الباحثون النماذج وصف الرمز الرسمي وطلبوا الرمز نفسه، وهي أسهل مهمة ممكنة لأنه لا توجد ملاحظة طبية ولا قواعد، وكانت المطابقة التامة لأفضل نموذج وقتها GPT-4 بنسبة 33.9% فقط في ICD-10-CM. وفي المقابل نشرت [ورقة على arXiv في 2024](https://arxiv.org/abs/2407.12849?ref=arabroot.io) أن النموذج يصبح أدق بكثير إذا لم يطلب منه أن يتذكر الرموز، وإنما أن يختار من مرشحين يجلبهم بحث في جدول الرموز (طريقة البحث ثم الترتيب Retrieve-Rank)، وإن كان الكاتب نفسه ينبه إلى أن اختباره كان على 100 مصطلح بسيط وليس على ملاحظات حقيقية. وسوف نجرب الفكرتين في المختبر.

## لماذا يبقى المرمز؟

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

- **المسؤولية:** المطالبة ورقة مالية، والخطأ المنظم فيها يتحول من خطأ إلى احتيال كما رأيت في [قسم الرفض في مقال الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A-clinical-coding/#denials)، والمحاسبة تقع على المنشأة ومن وقع المطالبة.
- **التوثيق الناقص:** أهم ما يفعله المرمز أحياناً هو أن يرسل استفساراً للطبيب لأن الملف ناقص، والنموذج الذي يرمز ما أمامه فقط لا يعرف ما لم يكتب.
- **القواعد تتغير:** الرموز والإرشادات تتحدث كل سنة، والنموذج تدرب قبل ذلك، فيقترح رموزاً حذفت أو أصبحت عناوين.
- **الاعتماد الزائد Automation Bias:** عندما يرى المرمز اقتراحاً جاهزاً بثقة عالية يميل إلى قبوله، لذلك يجب أن تقيس في التشغيل كم اقتراحاً قبله المرمز كما هو وكم عدله، فإذا كان القبول قريباً من 100% فهذا قد يعني أن المراجعة توقفت وليس أن الأداة ممتازة.

## كيف تقيس أداة ترميز؟

شرحنا الدقة والحساسية والقيمة التنبؤية لنموذج يصنف صورة واحدة في مقال [الشركة الناشئة في الذكاء الاصطناعي الطبي](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%B4%D8%B1%D9%83%D8%A9-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A/#metrics)، والترميز يختلف لأن كل ملاحظة لها مجموعة رموز وليس إجابة واحدة. لذلك نقيس على مستوى الرمز: كل زوج (ملاحظة، رمز) صحيح اقترحته الأداة يحسب إيجابياً صحيحاً True Positive، وكل رمز اقترحته وليس في الإجابة الصحيحة إيجابياً خاطئاً False Positive، وكل رمز صحيح لم تقترحه سلبياً خاطئاً False Negative، ثم:

- **الدقة Precision** \= TP ÷ (TP + FP): من الرموز التي اقترحتها الأداة، كم منها صحيح؟ وهذا ما يهم المدقق وشركة التأمين، لأن الرمز الزائد قد يكون ترميزاً أعلى.
- **الاستدعاء Recall** \= TP ÷ (TP + FN): من الرموز الصحيحة، كم وجدت الأداة؟ وهذا ما يهم المستشفى، لأن الرمز الناقص مال ضائع.
- **F1**: المتوسط التوافقي للاثنين، ويعطي رقماً واحداً للمقارنة.

وعندما نجمع كل الأزواج من كل الملاحظات ثم نحسب فهذا يسمى Micro Average، وهو ما نستخدمه في المختبر. أما المطابقة التامة Exact Match فهي نسبة الملاحظات التي جاءت كل رموزها صحيحة دون زيادة أو نقصان، وهي قاسية جداً كما تبين الصورة التالية من حالة حقيقية في المختبر:

![الملاحظة الاصطناعية n04: المريض عنده DM2 وHTN والأب توفي بجلطة قلبية ويستمر على metformin. دائرة الإجابة الصحيحة فيها E11.9 وI10 وZ79.84 وZ82.49 تاريخ عائلي، ودائرة اقتراح القواعد البسيطة فيها E11.9 وI10 وZ79.84 وI25.10 مرض الأب. التقاطع ثلاثة رموز صحيحة TP يساوي 3، والناقص Z82.49 أي FN يساوي 1، والزائد I25.10 أي FP يساوي 1، فالدقة 0.75 والاستدعاء 0.75 وF1 0.75، والمطابقة التامة صفر](https://arabroot.io/content/images/2026/10/ai-medical-coding-02-metrics.webp)

ثلاثة رموز صحيحة من أربعة، والمطابقة التامة تعطي الملاحظة صفراً

لاحظ أن الخطأين في الصورة ليسا بنفس الوزن: الرمز الزائد `I25.10` يقول إن المريض نفسه عنده مرض في شرايين القلب، وهذا قد يغير المبلغ ويعد ترميزاً أعلى، أما الناقص `Z82.49` فأثره المالي أقل غالباً. لذلك لا تكتف بالأرقام المجمعة، واقرأ الأخطاء نفسها كما سوف نفعل. ونحسب أيضاً المقاييس على مستوى الفئة Category، أي أول ثلاث خانات فقط، لأنها تبين هل فهمت الأداة المرض وأخطأت في التفاصيل، أم لم تفهمه أصلاً.

## المخاطر قبل أن تشغل أداة كهذه

- **الترميز الأعلى Upcoding:** أداة تقترح تشخيصات إضافية من تاريخ المريض أو من ذكر عابر في الملاحظة ترفع درجة التعقيد والمبلغ، فإذا قبلها المرمز دون توثيق في نفس الزيارة فهذا هو بالضبط النمط الذي عوقبت عليه جهات كبيرة، كما في القصة التي ذكرناها في [مقال الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A-clinical-coding/#denials).
- **الرموز المخترعة:** النموذج قد يولد رمزاً بشكل صحيح لا وجود له، أو رمزاً كان موجوداً قبل سنوات، أو عنواناً غير صالح للفوترة، والحل الوحيد الموثوق هو التحقق من كل رمز في الملف الرسمي للإصدار الصحيح، كما سوف نفعل.
- **الانحياز Bias:** النماذج تدربت على نصوص أغلبها إنجليزية من أنظمة صحية أخرى، فأداؤها على ملاحظة عربية أو مختلطة، أو على اختصارات محلية، أو على تخصص قليل البيانات، قد يكون أسوأ بكثير مما تقوله الأرقام المنشورة، فقس على بياناتك أنت وبحسب اللغة والتخصص كل على حدة.
- **إخراج البيانات:** إرسال الملاحظات إلى خدمة ذكاء اصطناعي خارجية هو نقل لبيانات صحية، وهذا موضوع القسم التالي.

## الخصوصية: الملاحظة الطبية بيانات حساسة

[نظام حماية البيانات الشخصية PDPL](https://laws.boe.gov.sa/BoeLaws/Laws/LawDetails/b7cfae89-828e-4994-b167-adaa00e37188/1?ref=arabroot.io) يضع البيانات الصحية ضمن البيانات الحساسة كما شرحنا في [قسم الخصوصية في مقال HL7 وFHIR](https://arabroot.io/articles/%D8%AA%D8%A8%D8%A7%D8%AF%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AD%D9%8A%D8%A9-hl7-%D9%88-fhir/#privacy)، ونقل البيانات الشخصية إلى خارج المملكة له شروطه في النظام و[لائحة النقل خارج المملكة](https://dgp.sdaia.gov.sa/wps/portal/pdp/knowledgecenter/details/RegulationonPersonalDataTransferOutsidetheKingdom?ref=arabroot.io). والطريقة البسيطة التي يبدأ بها أغلب الفرق هي أن ترسل الملاحظة كاملة إلى واجهة نموذج تجاري لأن ذلك أسرع، وهذا يعني أن نص الطبيب بكل ما فيه خرج من شبكتك إلى سيرفر لا تعرف أين هو ولا كم يحتفظ بالبيانات، لذلك لا تقم بذلك قبل أن يوافق مسؤول حماية البيانات في منشأتك وقبل اتفاقية معالجة واضحة. والصورة التالية تبين المسار الذي نقترحه:

![داخل شبكة المستشفى خمس خطوات: 1 ملاحظة السجل الطبي بالنص اللازم فقط، 2 نموذج محلي بإصدار محدد دون اتصال بالإنترنت، 3 حواجز تتحقق من أن الرمز صالح للفوترة وأن الدليل موجود في الملاحظة، والرموز المرفوضة تسجل ولا تعرض كأنها صحيحة، 4 مراجعة المرمز بالقبول أو التعديل أو الرفض، 5 سجل تدقيق فيه من وماذا ولماذا وإصدار النموذج والتعليمات، ثم المطالبة بالرموز فقط بتوقيع المرمز. وفي الأسفل بلون أحمر الطريق المختصر الذي يجب تجنبه: إرسال الملاحظات كاملة إلى خدمة ذكاء اصطناعي خارجية، لأن البيانات الصحية حساسة في نظام حماية البيانات الشخصية والنقل خارج المملكة له شروط](https://arabroot.io/content/images/2026/10/ai-medical-coding-03-pipeline.webp)

النموذج داخل الشبكة، والحواجز قبل المرمز، وكل قرار في سجل التدقيق

وسجل التدقيق هنا ليس رفاهية، فهو الذي يجيب المدقق عن سؤال «من اقترح هذا الرمز ومن قبله»، كما شرحنا في [مقال سجل التدقيق](https://arabroot.io/articles/%D8%B3%D8%AC%D9%84-%D8%A7%D9%84%D8%AA%D8%AF%D9%82%D9%8A%D9%82-audit-trail-%D9%85%D9%86-%D9%81%D8%B9%D9%84-%D9%85%D8%A7%D8%B0%D8%A7-%D9%88%D9%85%D8%AA%D9%89/)، ومخاطر تطبيقات النماذج نفسها مثل حقن التعليمات Prompt Injection من نص الملاحظة مشروحة في [OWASP Top 10 لتطبيقات LLM](https://arabroot.io/articles/owasp-top-10-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-llm/)، وتذكر أن الملاحظة نص يكتبه إنسان وقد يحتوي أي شيء. أما الحوكمة والامتثال بشكل عام فقد شرحناها في [مصطلحات GRC](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-grc-%D8%A7%D9%84%D8%AD%D9%88%D9%83%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D9%85%D8%AE%D8%A7%D8%B7%D8%B1-%D9%88%D8%A7%D9%84%D8%A7%D9%85%D8%AA%D8%AB%D8%A7%D9%84/).

## المختبر: الملاحظات والإجابات الصحيحة

⚠️

كل الملاحظات في هذا المختبر اصطناعية كتبناها للمقال، ولا تحتوي أي بيانات مريض حقيقي، وكل ملاحظة تبدأ بعبارة SYNTHETIC NOTE أو «ملاحظة اصطناعية». والإجابات الصحيحة وضعناها نحن بحسب إرشادات ICD-10-CM للعيادات الخارجية لسنة 2027، وليست من مرمز معتمد، فالمرمز المحترف قد يختلف معنا في بعضها.

نستخدم ملف رموز ICD-10-CM للسنة المالية 2027 لأنه منشور للتنزيل من CMS، وقد شرحنا في [مختبر مقال الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A-clinical-coding/#lab) كيف ننزله وحالة استخدامه، ولا نستخدم ملفات SNOMED CT ولا ICD-10-AM لأنها مرخصة. والملاحظات 26 ملاحظة قصيرة من العيادات الخارجية: إنجليزية وعربية ومختلطة، وفيها عمداً الحالات الصعبة: النفي مثل «لا يوجد سكري»، والتاريخ العائلي، والاختصارات مثل DM2 وHTN وUTI وCAD، والتشخيص غير المؤكد، والرموز المركبة. وهذه أربعة أسطر منها من الملف `notes.jsonl`:

```json
{"id": "n02", "note": "ملاحظة اصطناعية. امرأة 45 سنة، لا يوجد سكري. التشخيص: ارتفاع ضغط الدم الأساسي، بدأنا amlodipine 5 mg.", "gold": ["I10"]}
{"id": "n04", "note": "ملاحظة اصطناعية. المريض عنده DM2 و HTN، والأب توفي بجلطة قلبية. Plan: continue metformin.", "gold": ["E119", "I10", "Z8249", "Z7984"]}
{"id": "n05", "note": "SYNTHETIC NOTE. Cough and fever for 3 days. CXR ordered to rule out pneumonia.", "gold": ["R051", "R509"]}
{"id": "n22", "note": "ملاحظة اصطناعية. الأم مصابة بالسكري والضغط. المريض نفسه سليم وجاء لفحص ما قبل التوظيف.", "gold": ["Z021", "Z833", "Z8249"]}
```

في الملاحظات أعلاه لاحظ التالي:

- في n05 الطبيب كتب «rule out pneumonia»، و[إرشادات 2027](https://www.cms.gov/files/document/fy-2027-icd-10-cm-coding-guidelines.pdf?ref=arabroot.io) تنص في قسم العيادات الخارجية على ألا ترمز التشخيصات الموصوفة بكلمات مثل «probable» و«suspected» و«rule out»، وإنما ترمز الأعراض، لذلك الإجابة الصحيحة هي السعال الحاد والحمى وليس الالتهاب الرئوي.
- في n04 المريض يأخذ metformin، فنضيف `Z79.84` للاستخدام طويل المدى لأدوية السكر الفموية بحسب الإرشادات نفسها، ومرض الأب يرمز تاريخاً عائلياً `Z82.49` وليس مرضاً للمريض.
- الرموز مكتوبة دون نقطة كما في ملف CMS، والمقارنة تتم بعد حذف النقطة من أي إجابة.

والملفات كلها (الملاحظات، والسكربتات الأربعة) موجودة في مجلد `lab` مع المقال. ونجهز البيئة بشبكة خاصة، وOllama دون أي منفذ منشور لأن واجهته لا تطلب مصادقة، وContainer بايثون بإصدار محدد:

```bash
cd lab-mc        # the folder with notes.jsonl, the scripts and icd10cm_codes_2027.txt
cp "Code Descriptions/icd10cm_codes_2027.txt" .
docker network create lab-mc-net
docker volume create lab-mc-ollama
docker run -d --name lab-mc-ollama --network lab-mc-net -v lab-mc-ollama:/root/.ollama -e OLLAMA_KEEP_ALIVE=30m ollama/ollama:0.40.3
docker exec lab-mc-ollama ollama pull qwen2.5:7b
docker exec lab-mc-ollama ollama pull qwen2.5:3b
docker run -d --name lab-mc-py --network lab-mc-net -v "$PWD:/work" -w /work python:3.13.16-slim sleep infinity
docker exec lab-mc-ollama ollama list
```

```text
NAME          ID              SIZE      MODIFIED
qwen2.5:3b    357c53fb659c    1.9 GB    19 seconds ago
qwen2.5:7b    845dbda0ea48    4.7 GB    14 minutes ago
```

في الإعداد أعلاه لاحظ التالي:

- اخترنا نموذجين من نفس العائلة بحجمين حتى نرى أثر الحجم وحده: [Qwen2.5-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-7B-Instruct?ref=arabroot.io) ورخصته Apache-2.0، و[Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct?ref=arabroot.io) ورخصته Qwen Research License، أي أنه للبحث وليس للاستخدام التجاري دون إذن، فانتبه لهذا الفرق قبل أن تضع أي نموذج في منتج. والاثنان بتكميم Q4\_K\_M في Ollama.
- التشغيل كان على المعالج فقط CPU دون بطاقة رسومية، وعلى جهاز يشغل أعمالاً أخرى في نفس الوقت، لذلك الأزمنة التي سوف تراها للمقارنة بين الطرق على نفس الجهاز فقط، وليست مقياساً لسرعة النموذج. وفي إحدى المرات انتهت مهلة أول طلب بعد التبديل بين النموذجين لأن تحميل النموذج الأكبر لم ينته في 15 دقيقة والجهاز مشغول، فأوقفنا النموذج الآخر بالأمر `ollama stop qwen2.5:3b` وحملنا الأكبر بطلب تجريبي قبل إعادة التشغيل، لذلك لا تشغل نموذجين على جهاز محدود الذاكرة إلا عند الحاجة. وإذا أردت أن تعرف كم ذاكرة تحتاج لكل حجم فقد شرحناه في [تشغيل نماذج اللغة على جهازك](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-%D8%B9%D9%84%D9%89-%D8%AC%D9%87%D8%A7%D8%B2%D9%83-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

## الطريقة البسيطة: كلمات مفتاحية فقط

السكربت `rules.py` فيه قاموس من 33 نمطاً بالعربية والإنجليزية، كل نمط يقابله رمز، ويعمل بوضعين: `plain` يبحث عن الكلمة في أي مكان، و`context` يقسم الملاحظة إلى جمل وعبارات، ويتجاهل المصطلح إذا سبقته كلمة نفي، أو كانت في عبارته كلمة شك، ويحوله إلى تاريخ عائلي إذا ذكر فيها أحد الأقارب، ثم يطبق قاعدتين للرموز المركبة. وهذا جزء القاموس والسياق منه:

```python
TERMS = [
    (r"\bt2dm\b|\bdm2\b|type 2 diabetes|سكري نوع ثاني|سكري النوع الثاني|\bسكري\b|\bالسكري\b", "E119"),
    (r"\bhtn\b|hypertension|ارتفاع ضغط الدم|\bالضغط\b", "I10"),
    (r"\buti\b|urinary tract infection|التهاب مجاري بولية", "N390"),
    # ... 33 patterns in total, see lab/rules.py
]
NEG = r"\bno\b|\bdenies\b|\bwithout\b|لا يوجد|لا توجد|لا يشتكي|بدون|ليس لديه|نفي"
UNCERTAIN = r"rule out|\br/o\b|possible|probable|suspected|query|محتمل|اشتباه|للتأكد من"
FAMILY = r"mother|father|family|الأم|الأب|العائلة|الأخ|الأخت"

def clauses(text):
    # sentence and clause boundaries in both languages; the Arabic comma and «و» between clauses
    return [c for c in re.split(r"[.;:\n،,]|\bو(?=ال)|\band\b", text) if c.strip()]
```

ونشغل الوضعين ونقيس كلاً منهما بالسكربت `evaluate.py` الذي يقارن الاقتراحات بالإجابات الصحيحة على مستوى الرمز وعلى مستوى الفئة:

```bash
docker exec lab-mc-py sh -c 'python rules.py plain > rules-plain.jsonl; python rules.py context > rules-context.jsonl
echo "== plain"; python evaluate.py rules-plain.jsonl; echo "== context"; python evaluate.py rules-context.jsonl --details'
```

```text
== plain
code      TP= 30 FP= 23 FN= 15  precision=0.57 recall=0.67 F1=0.61  exact-match=6/26
category  TP= 35 FP= 17 FN=  9  precision=0.67 recall=0.80 F1=0.73  exact-match=9/26
== context
code      TP= 35 FP=  7 FN= 10  precision=0.83 recall=0.78 F1=0.80  exact-match=17/26
category  TP= 38 FP=  3 FN=  6  precision=0.93 recall=0.86 F1=0.89  exact-match=20/26
  n05: missed=['R051'] extra=['R059']
  n12: missed=['H6691'] extra=['H6690', 'R509']
  n13: missed=['Z6835'] extra=[]
  n14: missed=['Z87891'] extra=[]
  n15: missed=['G43009'] extra=['G43909']
  n19: missed=['I480'] extra=['I4891']
  n21: missed=['Z0000'] extra=[]
  n22: missed=['Z8249', 'Z833'] extra=['I10']
  n23: missed=['M170'] extra=['M1990']
```

في المخرج أعلاه لاحظ التالي:

- الوضع البسيط `plain` رمز السكري في «لا يوجد سكري»، والاكتئاب في «لا توجد أعراض اكتئاب»، وفقر الدم في «No anemia»، ومرض الأب كأنه مرض المريض، لذلك كانت الدقة 0.57 فقط: أكثر من رمز زائد من كل ثلاثة اقتراحات.
- طبقة السياق رفعت الدقة إلى 0.83 والمطابقة التامة من 6 إلى 17 ملاحظة، دون أي نموذج، وهذا أهم درس في هذا القسم: قبل أن تبحث عن نموذج، تأكد أن النفي وتاريخ العائلة والشك معالجة.
- أغلب الأخطاء المتبقية أخطاء تحديد Specificity وليست أخطاء فهم: الأذن اليمنى `H66.91` بدل غير المحددة `H66.90`، والصداع النصفي بدون أورة `G43.009`، والرجفان الأذيني الانتيابي `I48.0`، وخشونة الركبتين `M17.0`، ولهذا ترتفع المقاييس على مستوى الفئة إلى 0.89\. وكل واحد من هذه يحتاج قاعدة جديدة، وهذه هي مشكلة القواعد التي لا تنتهي.
- الملاحظة n22 فشلت رغم طبقة السياق: «الأم مصابة بالسكري والضغط» قسمها السكربت عند «و» قبل «الضغط» إلى عبارتين، فخرجت كلمة «الضغط» من عبارة «الأم» ورمزت كضغط للمريض، وكلمة «بالسكري» لم تطابق النمط أصلاً بسبب حرف الباء. خطآن صغيران في قاعدة واحدة ينتجان رمزاً خاطئاً بثقة كاملة.

📌

هذه النتيجة متفائلة لصالح القواعد، والسبب أننا كتبنا القاموس ونحن نرى نفس الملاحظات التي نقيس عليها، وفي الواقع ترى القواعد ملاحظات جديدة بصياغات لم تتوقعها. لذلك عندما تقيم أي أداة فافصل الملاحظات التي تبني عليها عن الملاحظات التي تقيس عليها.

## نموذج اللغة وحده: نطلب الرموز مباشرة

السكربت `llm.py` يرسل كل ملاحظة إلى Ollama مع تعليمات فيها قواعد العيادات الخارجية الأساسية (لا ترمز المنفي، ولا المشكوك فيه، ومرض القريب تاريخ عائلي)، ويطلب الإجابة بصيغة JSON مع الجملة التي اعتمد عليها النموذج، وبدرجة حرارة Temperature صفر حتى تكون الإجابة ثابتة قدر الإمكان:

```python
SYSTEM = """You are a clinical coding assistant for outpatient visits in the United States.
Return the ICD-10-CM diagnosis codes for the note, following the official outpatient guidelines:
- Do not code conditions that are negated (for example "no", "denies", "لا يوجد").
- Do not code uncertain diagnoses ("rule out", "possible", "suspected"); code the symptoms instead.
- A disease of a family member is coded as a family history code (Z80-Z84), not as the patient's disease.
- Use the most specific billable code. Add Z79 codes for long-term drug use when documented.
The note may be in Arabic, English or both.
Answer only with JSON: {"codes": [{"code": "E11.9", "evidence": "exact words from the note"}]}"""

def ask(model, note):
    body = {"model": model, "stream": False, "format": "json",
            "options": {"temperature": 0, "seed": 42, "num_predict": 400},
            "messages": [{"role": "system", "content": SYSTEM}, {"role": "user", "content": note}]}
    req = urllib.request.Request(OLLAMA, data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=600) as r:
        return json.loads(r.read())["message"]["content"]
```

والجزء الأهم في السكربت هو الحاجز Guardrail بعد الإجابة: كل رمز يحذف منه أي حرف غير الحروف والأرقام، ثم يرفض إذا لم يكن في ملف الرموز الصالحة للفوترة لسنة 2027، أو إذا لم تكن الجملة التي استشهد بها موجودة حرفياً في الملاحظة. والسكربت يكتب ملفين: الرموز كما قالها النموذج `-noguard`، والرموز بعد الحاجز `-guard`:

```python
        code = re.sub(r"[^A-Z0-9]", "", str(it.get("code", "")).upper())
        ev = str(it.get("evidence", "")).strip()
        noguard.append(code)
        if code not in VALID:
            rejected.append((n["id"], code, "not a valid billable ICD-10-CM FY2027 code")); continue
        if not ev or ev.lower() not in n["note"].lower():
            rejected.append((n["id"], code, f"evidence not found in note: {ev[:40]!r}")); continue
        guard.append(code)
```

```bash
docker exec lab-mc-py python llm.py qwen2.5:7b q7
docker exec lab-mc-py sh -c 'python evaluate.py q7-noguard.jsonl; python evaluate.py q7-guard.jsonl'
```

```text
qwen2.5:7b: 26 notes in 1792 s
  rejected n04 Z851 not a valid billable ICD-10-CM FY2027 code
  rejected n04 Z790 not a valid billable ICD-10-CM FY2027 code
  rejected n05 J109 not a valid billable ICD-10-CM FY2027 code
  rejected n06 G229 not a valid billable ICD-10-CM FY2027 code
  rejected n08 Z7981 not a valid billable ICD-10-CM FY2027 code
  rejected n09 J039 not a valid billable ICD-10-CM FY2027 code
  rejected n13 E689 not a valid billable ICD-10-CM FY2027 code
  rejected n13 Z851 not a valid billable ICD-10-CM FY2027 code
  rejected n14 Z7981 not a valid billable ICD-10-CM FY2027 code
  rejected n14 R059 evidence not found in note: 'exact words from the note'
  rejected n14 Z8511 not a valid billable ICD-10-CM FY2027 code
  rejected n15 G4400 not a valid billable ICD-10-CM FY2027 code
  rejected n17 T819 not a valid billable ICD-10-CM FY2027 code
  rejected n19 D094 not a valid billable ICD-10-CM FY2027 code
  rejected n20 D509 evidence not found in note: 'Fقر دم'
  rejected n20 E529 not a valid billable ICD-10-CM FY2027 code
  rejected n20 Z790 not a valid billable ICD-10-CM FY2027 code
  rejected n25 Z795 not a valid billable ICD-10-CM FY2027 code
  rejected n26 R549 not a valid billable ICD-10-CM FY2027 code
code      TP=  5 FP= 37 FN= 40  precision=0.12 recall=0.11 F1=0.11  exact-match=0/26
category  TP= 10 FP= 32 FN= 34  precision=0.24 recall=0.23 F1=0.23  exact-match=2/26
code      TP=  5 FP= 18 FN= 40  precision=0.22 recall=0.11 F1=0.15  exact-match=0/26
category  TP=  8 FP= 15 FN= 36  precision=0.35 recall=0.18 F1=0.24  exact-match=1/26
```

النتيجة أسوأ بكثير من الكلمات المفتاحية: 5 رموز صحيحة فقط من 45، والحاجز رفض 19 رمزاً من 42 اقترحها النموذج، أي قرابة النصف. واقرأ الإجابات نفسها في الملف `q7-raw.jsonl` لتفهم السبب:

```text
{"id": "n21", "answer": "{\"codes\": [{\"code\": \"E11.9\", \"evidence\": \"No anemia\"}]}"}
{"id": "n10", "answer": "{\"codes\": [{\"code\": \"E11.9\", \"evidence\": \"Hypothyroidism على levothyroxine، وال TSH طبيعي\"}]}"}
{"id": "n03", "answer": "{\"codes\": [{\"code\": \"E11.9\", \"evidence\": \"prediabetes\"}]}"}
{"id": "n06", "answer": "{\"codes\": [{\"code\": \"G22.9\", \"evidence\": \"burning urination\"}]}"}
{"id": "n14", "answer": "{\"codes\": [{\"code\": \"Z79.81\", \"evidence\": \"long-term drug use of tiotropium\"}, {\"code\": \"R05.9\", \"evidence\": \"exact words from the note\"}, {\"code\": \"Z85.11\", \"evidence\": \"exact words from the note\"}]}"}
{"id": "n19", "answer": "{\"codes\": [{\"code\": \"I47.9\", \"evidence\": \"Paroxysmal atrial fibrillation\"}, {\"code\": \"D09.4\", \"evidence\": \"Apixaban, a direct oral anticoagulant, is coded under anticoagulants, blood thinners\"}]}"}
```

في المخرج أعلاه لاحظ التالي:

- النموذج فهم الملاحظة غالباً، فالجملة التي استشهد بها في كل سطر هي الجملة الصحيحة، ولكنه لا يعرف الرموز: كتب `E11.9` (السكري) لملاحظة «No anemia»، ولقصور الغدة الدرقية، وللحالة ما قبل السكري، وكتب `G22.9` وهو رمز غير موجود للحرقان في البول. والرمز `E11.9` ظهر في 11 ملاحظة من 26، وكأنه الرمز الذي يكتبه النموذج عندما لا يعرف.
- في n14 نسخ النموذج النص المثال من التعليمات `exact words from the note` بدل أن يستشهد بالملاحظة، وفي n19 كتب شرحاً بدل الاستشهاد، وهذا ما أمسكه فحص الدليل.
- الحاجز رفع الدقة من 0.12 إلى 0.22 لأنه حذف الرموز المخترعة، ولكنه لم يرفع الاستدعاء إطلاقاً، فالحاجز يمنع الخطأ من الوصول ولا يصنع الإجابة الصحيحة. ولاحظ أيضاً أن `E11.9` لملاحظة «No anemia» مر من الحاجز، لأن الرمز موجود في الملف والجملة موجودة في الملاحظة، فالحاجز لا يفهم أن الجملة لا تبرر الرمز.

والنموذج الأصغر `qwen2.5:3b` بنفس السكربت كان أسوأ كما هو متوقع:

```bash
docker exec lab-mc-py python llm.py qwen2.5:3b q3
docker exec lab-mc-py sh -c 'python evaluate.py q3-noguard.jsonl; python evaluate.py q3-guard.jsonl'
```

```text
qwen2.5:3b: 26 notes in 2225 s
  rejected n02 1579 not a valid billable ICD-10-CM FY2027 code
  rejected n04 I109 not a valid billable ICD-10-CM FY2027 code
  rejected n06 N109 not a valid billable ICD-10-CM FY2027 code
  rejected n08 R020 not a valid billable ICD-10-CM FY2027 code
  rejected n08 N330 not a valid billable ICD-10-CM FY2027 code
  rejected n10 E069 evidence not found in note: 'Hypothyroidism on levothyroxine, TSH nor'
  rejected n12 O9081 evidence not found in note: '发热和右耳疼痛'
  rejected n13 Z850 not a valid billable ICD-10-CM FY2027 code
  rejected n14 J459 not a valid billable ICD-10-CM FY2027 code
  rejected n15 H369 not a valid billable ICD-10-CM FY2027 code
  rejected n16 2649 not a valid billable ICD-10-CM FY2027 code
  rejected n18 R020 not a valid billable ICD-10-CM FY2027 code
  rejected n19 I47111 not a valid billable ICD-10-CM FY2027 code
  rejected n20 B849 not a valid billable ICD-10-CM FY2027 code
  rejected n23 R6211 not a valid billable ICD-10-CM FY2027 code
code      TP=  3 FP= 33 FN= 42  precision=0.08 recall=0.07 F1=0.07  exact-match=0/26
category  TP=  6 FP= 30 FN= 38  precision=0.17 recall=0.14 F1=0.15  exact-match=0/26
code      TP=  3 FP= 18 FN= 42  precision=0.14 recall=0.07 F1=0.09  exact-match=0/26
category  TP=  5 FP= 16 FN= 39  precision=0.24 recall=0.11 F1=0.15  exact-match=0/26
```

ومن أخطائه أنه رمز «الأم مصابة بالسكري» بالرمز `E11.9` كأنه مرض المريض، وأنه كتب الرمز `157.9` الذي ليس له شكل رمز ICD أصلاً، وأنه استشهد في ملاحظة عربية بجملة صينية `发热和右耳疼痛` لم تكتب في الملاحظة، والحاجز رفض الأخيرين.

## النموذج يقرأ، والملف يعطي الرموز

النتيجة السابقة تقول إن النموذج الصغير يفهم الملاحظة ولا يحفظ الرموز، وهذا يقودنا إلى تقسيم العمل كما في فكرة البحث ثم الترتيب: النموذج يفعل ما يجيده، أي قراءة النص واستخراج الحالات وحالتها (موجودة أو منفية أو عائلية أو مشكوك فيها) بمصطلحات إنجليزية قياسية، ثم بحث بسيط بالكلمات في أوصاف الرموز الرسمية يجلب 8 رموز مرشحة لكل حالة، ثم يختار النموذج من المرشحين فقط. والسكربت `llm_retrieve.py` يطبق ذلك، وهذا البحث فيه:

```python
STOP = {"of", "the", "and", "or", "in", "a", "an", "to", "for", "unspecified", "other", "not", "elsewhere", "classified"}
words = lambda t: [w for w in re.findall(r"[a-z0-9]+", t.lower()) if w not in STOP]

VALID = {}
for line in open("icd10cm_codes_2027.txt", encoding="utf-8"):
    VALID[line[:7].strip()] = line[8:].strip()
INDEX = [(code, title, set(words(title))) for code, title in VALID.items()]

def search(query, k=8):
    q = set(words(query))
    scored = []
    for code, title, w in INDEX:
        hit = len(q & w)
        if hit:
            scored.append((hit / math.sqrt(len(w)) + hit, code, title))
    return [(c, t) for _, c, t in sorted(scored, reverse=True)[:k]]
```

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

```bash
docker exec lab-mc-py python llm_retrieve.py qwen2.5:7b r7
docker exec lab-mc-py python evaluate.py r7-guard.jsonl
```

```text
qwen2.5:7b with retrieval: 26 notes in 3110 s
  rejected n01 E119 not one of the candidates from the code file
  rejected n01 I10 not one of the candidates from the code file
  rejected n02 T551 not one of the candidates from the code file
  rejected n03 R7301 evidence not found in note: 'high fasting glucose'
  rejected n04 E119 not one of the candidates from the code file
  rejected n04 I10 not one of the candidates from the code file
  rejected n04 Z8021 not one of the candidates from the code file
  rejected n05 J129 not one of the candidates from the code file
  rejected n06 R391 not one of the candidates from the code file
  rejected n06 Z9881 not one of the candidates from the code file
  rejected n07 M5459 evidence not found in note: 'Other low back pain'
  rejected n08 J459 not one of the candidates from the code file
  rejected n08 K219 not one of the candidates from the code file
  rejected n08 K2441 not one of the candidates from the code file
  rejected n11 E119 not one of the candidates from the code file
  rejected n11 N183 not one of the candidates from the code file
  rejected n12 H6691 evidence not found in note: 'otitis media in the right ear'
  rejected n14 Z973 not one of the candidates from the code file
  rejected n15 G439 not one of the candidates from the code file
  rejected n16 R5383 evidence not found in note: 'Other fatigue'
  rejected n18 I251 not one of the candidates from the code file
  rejected n18 Z7982 evidence not found in note: 'long term (current) use of aspirin'
  rejected n20 D539 evidence not found in note: 'Nutritional anemia, unspecified'
  rejected n20 E559 evidence not found in note: 'Vitamin D deficiency, unspecified'
  rejected n22 Z833 evidence not found in note: 'family history of diabetes mellitus'
  rejected n22 Z8489 evidence not found in note: 'family history of hypertension'
  rejected n22 Z021 evidence not found in note: 'pre-employment medical check'
  rejected n24 J00 evidence not found in note: 'Acute nasopharyngitis [common cold]'
  rejected n25 E119 evidence not found in note: 'diabetes mellitus type 2'
  rejected n25 R119 not one of the candidates from the code file
  rejected n25 N043 not one of the candidates from the code file
  rejected n26 R429 not one of the candidates from the code file
code      TP=  9 FP=  4 FN= 36  precision=0.69 recall=0.20 F1=0.31  exact-match=5/26
category  TP= 11 FP=  2 FN= 33  precision=0.85 recall=0.25 F1=0.39  exact-match=6/26
```

الدقة ارتفعت إلى 0.69 والمطابقة التامة إلى 5 ملاحظات، وهذا أفضل بوضوح من النموذج وحده، ولكن الاستدعاء 0.20 فقط، أي أن الحاجز رفض أغلب ما اقترحه النموذج هذه المرة، وليس لأنه خاطئ دائماً. واقرأ سطرين من الملف `r7-raw.jsonl` لتعرف السبب:

```text
{"id": "n04", "items": [{"term": "DM2", "status": "present", "evidence": "DM2"}, {"term": "HTN", "status": "present", "evidence": "HTN"}, {"term": "MI", "status": "family", "evidence": "الأب توفي بجلطة قلبية"}], "chosen": [{"code": "E11.9", "evidence": "DM2"}, {"code": "I10", "evidence": "HTN"}, {"code": "Z80.21", "evidence": "family history of MI"}]}
{"id": "n22", "items": [{"term": "diabetes mellitus", "status": "family", "evidence": "الأم مصابة بالسكري"}, {"term": "hypertension", "status": "family", "evidence": "الأم مصابة بالضغط"}, {"term": "pre-employment medical check", "status": "present", "evidence": "جاء لفحص ما قبل التوظيف"}], "chosen": [{"code": "Z833", "evidence": "family history of diabetes mellitus"}, {"code": "Z8489", "evidence": "family history of hypertension"}, {"code": "Z021", "evidence": "pre-employment medical check"}]}
```

في المخرج أعلاه لاحظ التالي:

- في n04 فهم النموذج كل شيء: السكري والضغط موجودان، والجلطة عند الأب حالة عائلية. ولكنه استخرج المصطلحين كما كتبهما الطبيب `DM2` و`HTN` رغم أن التعليمات طلبت مصطلحاً إنجليزياً قياسياً، والبحث بالكلمات في أوصاف الرموز الرسمية لا يعرف هذه الاختصارات فلم يجلب مرشحين، فاختار النموذج `E11.9` و`I10` من ذاكرته، وهما صحيحان، ولكن الحاجز رفضهما لأنهما ليسا من المرشحين. وهذا هو الحاجز يعمل كما صممناه، والمشكلة في خطوة البحث.
- في n22 اختار النموذج الرموز الصحيحة `Z833` و`Z021`، ولكنه استشهد بوصف الرمز الإنجليزي بدل كلمات الملاحظة العربية، ففشل فحص الدليل. والدليل الصحيح موجود أصلاً في خطوة الاستخراج (`الأم مصابة بالسكري`)، فالإصلاح أن يربط كل رمز بالحالة التي جاء منها ويأخذ دليلها، بدل أن يطلب من النموذج أن يكتب الدليل مرة ثانية.
- الرموز التي مرت من الحاجز كانت صحيحة غالباً: 9 صحيحة و4 خاطئة فقط، ومن الخاطئة `R52` (الألم) مع خشونة الركبتين، وهو عرض يدخل في التشخيص نفسه ولا يرمز معه بحسب الإرشادات.

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

## النتائج في جدول واحد، وحالات الفشل

الصورة التالية تجمع F1 على مستوى الرمز لكل الطرق على نفس الملاحظات ونفس الجهاز:

![رسم بالأعمدة للدقة والاستدعاء وF1 لست طرق: الكلمات البسيطة 0.57 و0.67 و0.61، والكلمات مع السياق 0.83 و0.78 و0.80، وqwen2.5:7b دون حاجز 0.12 و0.11 و0.11، ومع الحاجز 0.22 و0.11 و0.15، وqwen2.5:3b مع الحاجز 0.14 و0.07 و0.09، وqwen2.5:7b مع البحث في ملف الرموز والحاجز 0.69 و0.20 و0.31](https://arabroot.io/content/images/2026/10/ai-medical-coding-04-results.webp)

الكلمات مع السياق تتصدر على هذه الملاحظات، والنموذج مع البحث دقيق ولكنه يجد القليل

والجدول التالي يتتبع أربع ملاحظات تمثل حالات الفشل التي طلبناها من المختبر، وماذا أعطت كل طريقة بعد الحاجز:

| الحالة                                                 | الإجابة الصحيحة | كلمات بسيطة | كلمات مع السياق | qwen2.5:7b وحده | qwen2.5:7b مع البحث |
| ------------------------------------------------------ | --------------- | ----------- | --------------- | --------------- | ------------------- |
| النفي، n02: «لا يوجد سكري» مع ضغط                      | I10             | E119 I10    | I10             | I159            | I10                 |
| التاريخ العائلي، n22: «الأم مصابة بالسكري والضغط»      | Z021 Z833 Z8249 | I10 Z021    | I10 Z021        | Z8481           | لا شيء              |
| الاختصارات، n06: «Dx: UTI»                             | N390            | N390        | N390            | لا شيء          | لا شيء              |
| عربي وإنجليزي، n10: «Hypothyroidism على levothyroxine» | E039            | E039 F329   | E039            | E119            | E039                |

لاحظ أن كل طريقة تفشل في مكان مختلف: الكلمات البسيطة تسقط في النفي، والقواعد مع السياق تسقط عندما يتغير تركيب الجملة العربية كما في n22، والنموذج وحده يقرأ الجملة جيداً ثم يكتب رمزاً خاطئاً أو غير موجود، والنموذج مع البحث لا يكتب رمزاً خاطئاً تقريباً ولكنه يسكت كثيراً. وفي n06 رأى النموذج مع البحث «UTI» وأخرج لها الرمز `Z98.81` وهو رمز لا علاقة له، فرفضه الحاجز لأنه ليس من المرشحين، وهذا مثال على أن الحاجز يحميك من الخطأ وأن السكوت أفضل من الخطأ في أداة تقترح. ولا تأخذ هذه الأرقام كحكم على النماذج عموماً، فهي 26 ملاحظة كتبناها نحن ونموذجان صغيران على معالج عادي، والنماذج الأكبر قد تكون أفضل بكثير، والطريقة الصحيحة أن تعيد نفس القياس على عينة من ملاحظاتك أنت بعد إزالة الهوية، وبإجابات يضعها مرمز معتمد.

## تدريب عملي

- أضف إلى `notes.jsonl` عشر ملاحظات جديدة لم يرها القاموس، بصياغات مختلفة، ثم شغل `rules.py context` عليها وحدها، وقارن F1 بالنتيجة أعلاه.
- حسن `llm_retrieve.py` بأمرين: قاموس صغير يحول الاختصارات مثل DM2 وHTN وUTI إلى مصطلحات قبل البحث، وأخذ الدليل من خطوة الاستخراج بدل خطوة الاختيار، ثم قس هل يرتفع الاستدعاء دون أن تنخفض الدقة.
- أضف إلى الحاجز في `llm.py` فحصاً ثالثاً: إذا كانت جملة الدليل تبدأ بكلمة نفي مثل «No» أو «لا يوجد» فارفض الرمز، ثم انظر هل يمنع خطأ n21.

## تنظيف المختبر

```bash
docker rm -f lab-mc-py lab-mc-ollama
docker volume rm lab-mc-ollama
docker network rm lab-mc-net
```

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- الترميز بمساعدة الحاسب يقترح والمرمز يقرر، والمسؤولية أمام المدقق وشركة التأمين على المنشأة والمرمز.
- القواعد سريعة وقابلة للتفسير، وطبقة النفي والشك وتاريخ العائلة وحدها رفعت الدقة في المختبر من 0.57 إلى 0.83، ولكن كل رمز دقيق يحتاج قاعدة.
- النموذج المحلي الصغير فهم الملاحظات ولكنه لم يعرف الرموز، فاخترع رموزاً غير موجودة وكتب رمز السكري لملاحظات لا علاقة لها به، والحاجز الذي يتحقق من ملف الرموز الرسمي رفض قرابة نصف ما اقترحه.
- تقسيم العمل بين النموذج الذي يقرأ والملف الرسمي الذي يعطي الرموز رفع الدقة إلى 0.69، ولكن الاستدعاء بقي 0.20 لأن البحث لم يعرف الاختصارات ولأن النموذج استشهد بوصف الرمز بدل الملاحظة، والسكوت هنا أفضل من الخطأ.
- قس على مستوى الرمز بالدقة والاستدعاء وF1، واذكر المطابقة التامة بجانبها لا وحدها، واقرأ الأخطاء نفسها لأن الرمز الزائد قد يكون ترميزاً أعلى.
- الملاحظة الطبية بيانات حساسة في نظام حماية البيانات الشخصية، فشغل النموذج داخل شبكتك وسجل كل اقتراح وقرار.

[![](https://arabroot.io/content/images/2026/10/medical-coding-cover-cover.webp)دليلالترميز الطبي: من ملف المريض إلى المطالبةكيف يحول المرمز توثيق الطبيب إلى رموز تشخيص وإجراءات ومجموعات DRG، وما الذي يلزم به مجلس الضمان الصحي في السعودية.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A-clinical-coding/)[![](https://arabroot.io/content/images/2026/10/health-interop-cover-cover.webp)دليلتبادل البيانات الصحية بين الأنظمة: HL7 وFHIRكيف تنتقل نتائج المختبر وبيانات المريض بين أنظمة المستشفى برسائل HL7 وواجهات FHIR، وكيف تبني ذلك وتختبره بنفسك.](https://arabroot.io/articles/%D8%AA%D8%A8%D8%A7%D8%AF%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AD%D9%8A%D8%A9-hl7-%D9%88-fhir/)[![](https://arabroot.io/content/images/2026/10/medical-ai-startup-cover-cover.webp)دليلكيف تعمل شركة ناشئة في الذكاء الاصطناعي الطبي؟ما الذي تحتاجه شركة ذكاء اصطناعي للأشعة غير النموذج: البيانات والتحقق والموافقة والتكامل مع المستشفى، بحسابات حقيقية.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%B4%D8%B1%D9%83%D8%A9-%D9%86%D8%A7%D8%B4%D8%A6%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D8%A7%D9%84%D8%B7%D8%A8%D9%8A/)[![](https://arabroot.io/content/images/2026/10/local-llm-hardware-cover-cover.webp)دليلتشغيل نماذج اللغة على جهازك: كم ذاكرة وأي GPU تحتاج؟كيف تحسب الذاكرة التي يحتاجها النموذج، وماذا تتوقع من جهازك الحالي قبل أن تشتري بطاقة رسومية GPU.](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-%D8%B9%D9%84%D9%89-%D8%AC%D9%87%D8%A7%D8%B2%D9%83-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/llm-top10-cover-cover.webp)دليلOWASP Top 10 لتطبيقات LLM: المخاطر وكيف تحمي تطبيقكأخطر عشر مشكلات في التطبيقات المبنية على نماذج اللغة، من حقن التعليمات Prompt Injection إلى تسرب البيانات، وكيف تمنع كلاً منها.](https://arabroot.io/articles/owasp-top-10-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-llm/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ المختبر على Ollama 0.40.3 مع qwen2.5:7b وqwen2.5:3b بتكميم Q4\_K\_M على المعالج فقط، وpython:3.13.16-slim، وملف ICD-10-CM للسنة المالية 2027 من CMS، على Docker Engine 29.8.0.