فاحص الأعراض
فحوصات الأعراض لها سمعة سيئة لأسباب حقيقية: تمزج قواعد تصفية حقيقية بصفحة رئيسية مليئة بالنتائج الأسوأ. الإصدار الذي تبنيه هنا يتجنّب الدراما بالقيام بالجزء الذي يمكن لمحرك أن يفعله بصدق ، مطابقة الأعراض بالأمراض بتراكب مرجّح، وتحديد نطاق استعجال من الشدة والمدة، وتحويل ذلك النطاق إلى خطوات تالية بسيطة اللغة. إنه محرك قواعد فوق قاعدة معرفية صغيرة ومُختارة، ويقول ذلك بوضوح: لا ذكاء اصطناعي، لا تشخيص، وتنبيه يقف عند كل مخرج.
يُفترض أساسيات بايثون مع قواميس ومجموعات أساسية ، لا شيء يتجاوز ذلك، ولا حزم خارجية. هذا اختياري وغير مُقيَّم؛ راجع المشاريع الواقعية للقائمة الكاملة المتنامية.
للأغراض التعليمية فقط. مخرجات هذا المشروع ليست نصيحة طبية، ولا يمكنها التشخيص، ويجب دائمًا أن تشير إلى طبيب حقيقي. يُعلّم البناء نمذجة المجال والقواعد المرقّمة ، الادعاءات الطبية تتوقف عند هذا التنبيه.
🎯 ما ستفعله
- شفر قاعدة معرفية مُختارة من الأعراض إلى الأمراض كبيانات، لا كمنطق.
- درّج الأمراض بتراكب الأعراض المرجّح ورتّب التطابقات.
- اجمع الشدة والمدة في درجة استعجال واحدة محدودة من 0 إلى 10.
- اربط درجة الاستعجال بتوصيات رعاية بسيطة اللغة.
- لفّها في CLI تفاعلي مع إدخال أعراض مُوحّد وتنبيه.
أين تُشغّل هذا
محليًا باستخدام uv هو المسار الأساسي. المحرك مكتبة معيارية نقية، فيبدأ uv init فورًا، ووضع cli التفاعلي يحتاج طرفية حقيقية (سكربت تشغّله، لا خلية تنفّذه) لقراءة input().
Google Colab و Kaggle Notebooks و Binder تشغّل محرك التقييم بشكل متماثل ، خطوات التقييم كلها دوال بسيطة على بيانات بسيطة. التنبيه الصادق: التفاعلية المدفوعة بـ input() غير مريحة في الدفتر، فيشغّل تلك المسارات وضع العرض المُخمّل (الافتراضي في الخطوة 5) بدلاً من الأسئلة والأجوبة المباشرة. استخدم الشارات لرؤية المحرك من البداية إلى النهاية، وانتقل إلى uv المحلي للتجربة التفاعلية الكاملة.
الإعداد
أنشئ المشروع. يعتمد الفاحص المكتبة المعيارية فقط.
uv init symptom-checker
cd symptom-checker
uv run python -c "import sys, typing; print('ok')"
يستخدم sys في الخطوة 5 للتبديل بين أوضاع العرض والتفاعل، وtyping يعطيك توقيعات الدوال الأصغر (dict[str, ...]) التي تحافظ على قراءة بيانات المجال مع نمو قاعدة المعرفة عبر الخطوات.
✅ قائمة التحقق
- ✅
uv init symptom-checkerأنشأ مجلدًا بملفpyproject.toml. - ✅
uv run python -c "import sys, typing"طبعok، صفر حزم مُضافة.
الخطوة 1: نمذجة قاعدة معرفية للأعراض والأمراض
كل ما “يعرفه” هذا الفاحص يعيش في قاموس واحد. الحفاظ على الحقائق الطبية ك بيانات بدلاً من عبارات if هو ما يجعل منطق التقييم عامًا ، أضف مرضًا لاحقًا وسيُقيّمه المحرك بدون أي تغييرات في الكود.
1.1 شفر الأمراض وأعراضها المرجّحة
👟 تلميح البداية : مثل كل مرض كقاموس symptom → weight، وامنح كل عرض مفتاحًا آليًا ثابتًا وتصنيفًا مقروءًا يمكن للـ CLI طباعته.
# checker.py
KNOWLEDGE: dict[str, dict[str, int]] = {
"Common cold": {"cough": 3, "runny_nose": 3, "sore_throat": 2, "sneezing": 2, "fatigue": 1},
"Seasonal allergies": {"sneezing": 3, "itchy_eyes": 3, "runny_nose": 3, "headache": 1},
"Flu": {"fever": 3, "body_aches": 3, "fatigue": 3, "cough": 2, "headache": 2},
"Strep throat": {"sore_throat": 3, "fever": 2, "swollen_lymph": 2},
"Food poisoning": {"nausea": 3, "vomiting": 3, "diarrhea": 3, "stomach_pain": 2},
"Migraine": {"headache": 3, "light_sensitivity": 2, "nausea": 2},
"UTI": {"burning_urination": 3, "frequent_urination": 3},
"Dehydration": {"dry_mouth": 3, "dizziness": 2, "fatigue": 2, "headache": 1},
}
SYMPTOM_LABELS = {
"cough": "cough", "runny_nose": "runny nose", "sore_throat": "sore throat",
"sneezing": "sneezing", "fatigue": "fatigue", "itchy_eyes": "itchy eyes",
"headache": "headache", "fever": "fever", "body_aches": "body aches",
"swollen_lymph": "swollen glands", "nausea": "nausea", "vomiting": "vomiting",
"diarrhea": "diarrhea", "stomach_pain": "stomach pain",
"light_sensitivity": "light sensitivity", "burning_urination": "burning urination",
"frequent_urination": "frequent urination", "dry_mouth": "dry mouth",
"dizziness": "dizziness",
}
ALL_SYMPTOMS = {s for weights in KNOWLEDGE.values() for s in weights}
print(f"conditions: {len(KNOWLEDGE)} distinct symptoms: {len(ALL_SYMPTOMS)}")
شكلان من البيانات يقومان بالعمل الفعلي. weight لكل عرض (1–3) يشفر مدى قوة توجيه العرض نحو مرض ، 3 تعني “نموذجي جدًا حتى إنه يكاد يُعرّف المرض”، و1 تعني “يظهر لكنه غير محدد” ، فسعال وحده يدفع الإنفلونزا أقل مما تفعله الحمّى. SYMPTOM_LABELS يحتفظ بمفتاح آلي واحد ثابت ("burning_urination") مرتبطًا بعبارة بشرية واحدة، وهو ما يتيح لـ CLI في الخطوة 5 طباعة الأعراض وقبولها دون مطابقة نصية للكلمات التي قد يكتبها المستخدمون. ALL_SYMPTOMS مُشتق من قاعدة المعرفة نفسها بدلاً من صيانته يدويًا، فلا يمكن أن ينحرف عن البيانات.
🎯 الناتج المتوقع : conditions: 8 distinct symptoms: 19.
🩹 إذا لم يعمل : إذا كان العدد أقل، فقاموس مرض مفقود أو مفتاحا مرضين يتصادمان (مسافة مقابل شرطة سفلية). إذا أخطأ ALL_SYMPTOMS، فقيمة في KNOWLEDGE ليست قاموسًا ، تحقق من نص عالق في أحد الأمراض. إذا كانت العدّات أعلى، فمرض يحتوي على مفتاح عرض ليس في SYMPTOM_LABELS، وسيرفض CLI في الخطوة 5 طباعته.
1.2 تحقق من النموذج
✅ قائمة التحقق
- ✅
uv run python checker.pyيطبعconditions: 8 distinct symptoms: 19. - ✅ كل مفتاح عرض في
KNOWLEDGEيظهر أيضًا كمفتاح فيSYMPTOM_LABELS. - ✅ يمكنك أن تصف بكلماتك ماذا يعني الرقم
3بجانب عرض كقرار نمذجة.
🤔 سؤال (أسئلة) سقراطي(ة)
- عطسة تشير إلى الحساسيات والبرد بالتساوي تقريبًا. كلاهما مُقيَّم بـ 3 أعلاه ، ما التغيير في النمذجة الذي يعبّر عن “يظهر في كلاهما لكنه لا يميّز بينهما”؟
- الأوزان أعداد صحيحة. ماذا تكسب باستخدام 1–3 مقارنة بقائمة أعراض ثنائية بسيطة نعم/لا، وما المشكلة التي يخلقها جدول أوزان خبير مثل هذا لمنتج طبي حقيقي عند وصول أدلة جديدة؟
الخطوة 2: درّج الأمراض بتراكب مرجّح
الآن يقرر المحرك: بالنظر إلى مجموعة صغيرة من الأعراض الحاضرة، إلى أي أمراض تشير الأدلة؟ النتيجة نسبة من أدلة ذلك المرض النموذجية المطابقة، فيرتّب التطابق الجزئي دون التطابق الكامل.
2.1 رتّب الأمراض بالأدلة المطابقة
👟 تلميح البداية : لكل مرض، اجمع أوزان الأعراض التي لدى المستخدم، واقسم على الوزن الكلي للمرض، ورتّب تنازليًا ، عبارة comprehension واحدة، بلا منطق متفرع.
# checker.py (continued)
def score_conditions(present: set[str]) -> list[tuple[str, float]]:
ranked = []
for condition, symptom_weights in KNOWLEDGE.items():
covered = sum(w for s, w in symptom_weights.items() if s in present)
total = sum(symptom_weights.values())
ratio = covered / total if total else 0.0
ranked.append((condition, round(ratio, 2)))
ranked.sort(key=lambda item: item[1], reverse=True)
return ranked
demo = {"fever", "cough", "body_aches", "fatigue"}
for condition, ratio in score_conditions(demo):
print(f"{ratio:>4.2f} {condition}")
النسبة هي الخوارزمية كلها. covered يعدّ أوزان الأعراض المطابقة، وtotal هو البصمة الكاملة للمرض، فيسجّل المستخدم الذي يطابق كل عرض مرجّح لمرض النتيجة 1.0 تمامًا، ويقع التطابق الجزئي بين ذلك. هذا التطبيع هو القرار الجوهري: مرض ذو بصمة كبيرة (الإنفلونزا) يُقيَّم بمدى ظهور أدلته هو، لا بعدد الأعراض الخام ، وإلا كان المرض ذو الأعراض المدرجة الأكثر يربح دائمًا. sorted(... reverse=True) يحوّل الأزواج المقيَّمة إلى قائمة ترتيب يستهلكها باقي خط الأنابيب.
🎯 الناتج المتوقع : Flu أولًا عند 1.00 (الحمّى وآلام الجسم والإرهاق والسعال هي أفضل أربعة أعراض له تمامًا)، وCommon cold ثانيًا، والباقي أدناه.
🩹 إذا لم يعمل : إذا لم تحتل الإنفلونزا المرتبة الأولى لتلك المجموعة بالضبط، فوزن في قاموس الإنفلونزا مكتوب خطأ (مثلاً cough أصبح 1 بالصدفة). إذا كانت كل النتائج 1.00، فمطابقة s in present خاطئة لأن present يحمل التصنيفات بينما مفاتيح KNOWLEDGE آلية ، أبقِ demo بمفاتيح آلية. إذا بدت النتائج صغيرة، فأنت قسمت على المجموع الخاطئ وcovered/total مقلوبان.
2.2 تحقق من التقييم
✅ قائمة التحقق
- ✅ المجموعة التجريبية ترتّب
Fluعند1.00وCommon coldثانيًا. - ✅ مجموعة عرض واحد (
{"headache"}) تسجّل أدنى من 1.0 لكل مرض يدرج الصداع. - ✅ يمكنك تفسير لماذا يهمّ التطبيع (القسمة على المجموع الكلي لكل مرض) أكثر عندما تختلف أحجام البصمات.
🤔 سؤال (أسئلة) سقراطي(ة)
{"runny_nose", "sneezing", "itchy_eyes"}يجب أن تضع الحساسيات فوق البرد، الذي يشارك اثنين من تلك الأعراض. اعمل في حسابات النسبة وقل أين يتباعد المرضان ، ولماذا يمكن لبصمة أقصر أن تتفوق على أطول؟- هذا التقييم يتجاهل مدة الأعراض. أي نوع من الخطأ يرتكبه مقيّم لا يرى المدة، وهل هي مشكلة تقييم أم مشكلة تقييم-زائد-استعجال؟
الخطوة 3: احسب درجة استعجال محدودة
الوصول إلى قائمة ترتيب ليس الوصول إلى قرار فرز طبي. تضيف هذه الخطوة العمودين اللذين يحتاجهما تقييم حقيقي ، مدى شدة كل عرض ومدة بقائه ، وتضغط كل شيء في درجة استعجال واحدة محدودة من 0 إلى 10 يمكن لنطاقات التوصيات في الخطوة 4 الاعتماد عليها.
3.1 ادمج الشدة والمدة في رقم
👟 تلميح البداية : ابدأ من أعلى نسبة، وأضف جزاءات صغيرة للأعراض الشديدة وللأعراض التي تتجاوز أسبوعًا، واقطع النتيجة عند 10 ، أبقِ كل مساهمة صغيرة بما يكفي فلا تتحكم الشدة المرتفعة وحدها في كل شيء.
# checker.py (continued)
WARNING_SYMPTOMS = {"difficulty_breathing", "chest_pain", "confusion", "faintish"}
def urgency_score(present: set[str], severities: dict[str, str],
durations_days: dict[str, float]) -> float:
top_ratio = score_conditions(present)[0][1]
base = top_ratio * 5
severe_bonus = sum(1 for s in present if severities.get(s) == "severe") * 0.5
chronic_bonus = sum(1 for s, d in durations_days.items() if d > 7) * 0.3
warning_bonus = 4 if present & WARNING_SYMPTOMS else 0
return round(min(10, base + severe_bonus + chronic_bonus + warning_bonus), 1)
demo_dur = {"fever": 2, "cough": 3, "body_aches": 1, "fatigue": 10}
demo_sev = {"fever": "high", "body_aches": "severe", "fatigue": "moderate"}
print("urgency:", urgency_score({"fever", "cough", "body_aches", "fatigue"},
demo_sev, demo_dur))
كل حد يكسب وجوده من أزواجه. base يتدرج مع قوة مطابقة الأدلة (نسبة الخطوة 2 × 5، فتطابق مثالي يبدأ من 5)؛ severe_bonus وchronic_bonus يضيفان زيادات صغيرة للأعراض المعلَّمة severe أو التي تتجاوز أسبوعًا ، مقصودة ومتواضعة لتدفع لا لتسيطر؛ وwarning_bonus كبير (4 نقاط) لأن أعراض WARNING_SYMPTOMS الأربعة تترجم إلى “اطلب رعاية عاجلة” بمعزل عن أي تطابق مرضي. قطع min(10, …) هو ما يجعل الناتج درجة محدودة تثق بها النطاقات. لاحظ أن present & WARNING_SYMPTOMS يعيد استخدام تقاطع المجموعات ، لا حاجة لحلقة للسؤال “هل لدينا أي عرض إنذاري؟”
🎯 الناتج المتوقع : درجة واحدة بين 0 و10 ، للتجربة أعلاه، نحو 7.0–8.0، إذ تطابق إنفلونزا مثالي مع علامتين شديدتين/قريبتين من أعلام الإنذار يقع في النطاق المرتفع.
🩹 إذا لم يعمل : إذا تجاوزت الدرجة 10، فقطع min(10, …) مفقود. إذا بقيت صغيرة رغم الأعراض الشديدة، فـ severities.get(s) يبحث عن تصنيفات الأعراض بينما present يحمل مفاتيح آلية. إذا تفوّقت حالة مزمنة لكن خفيفة (عرض واحد لمدة 12 يومًا) على حالة عاجلة، فـ warning_bonus لا يُضاف ، تحقق أن مفاتيح WARNING_SYMPTOMS تطابق مفاتيحًا آلية حقيقية.
3.2 تحقق من درجة الاستعجال
✅ قائمة التحقق
- ✅ التجربة تعيد رقمًا بين 0 و10 تمامًا.
- ✅ إضافة
"chest_pain"إلىpresentترفع درجة الحالة نفسها بمقدار 3 على الأقل. - ✅ نفس الأعراض بمدد أقصر تسجّل درجة أقل من مدد أطول.
🤔 سؤال (أسئلة) سقراطي(ة)
warning_bonusمبلغ ثابت +4 أيًا كان عرض الإنذار. هل وزن كل إنذار (مثلاًdifficulty_breathingأثقل منfaintish) يحسّن صدق النطاقات، وماذا يكلّف من البساطة؟- الدرجة مجموع حدود مصممة باستقلال. ما الدرجة التي سيحصل عليها مستخدم بلا مرض مطابق لكن بعرض إنذار شديد واحد، وهل هي الإجابة التي تريد نطاق فرز طبي أن ينتجها؟
الخطوة 4: اربط الدرجات بتوصيات بسيطة اللغة
درجة بلا رسالة رقم لا يستطيع شخص قلق أن يتصرف بناءً عليه. تحصر هذه الخطوة نطاق 0–10 في أربعة نطاقات، كل منها مربوط بخطوة تالية ملموسة، وتولّد ملخصًا مقروءًا من المرض الأعلى ترتيبًا مع النطاق.
4.1 اكتب نطاقات الرعاية والملخص
👟 تلميح البداية : عرّف النطاقات بالحد الأعلى في قائمة مرتبة واحدة، وامشِ عليها للعثور على النطاق الذي تقع فيه الدرجة، ثم اكتب فقرة ملخص من سطر واحد من المرض الأعلى والدرجة والنطاق.
# checker.py (continued)
BANDS = [
(8.0, "Seek urgent or emergency care now. Call your local emergency line."),
(5.0, "Book an appointment with a doctor within 24 hours."),
(3.0, "Monitor for 24-48 hours. Hydrate and rest; book a visit if it worsens."),
(0.0, "Likely self-care. Rest, hydrate, and re-check if symptoms change."),
]
def recommendation(score: float) -> str:
for cutoff, message in BANDS:
if score >= cutoff:
return message
return BANDS[-1][1]
def summarize(present: set[str], severities: dict[str, str],
durations_days: dict[str, float]) -> str:
ranked = score_conditions(present)
top_condition, _ = ranked[0]
score = urgency_score(present, severities, durations_days)
lines = [
f"Top match: {top_condition}",
f"Urgency score: {score}/10",
"Next step: " + recommendation(score),
"Consult a qualified health professional before acting on this.",
]
return "\n".join(lines)
print(summarize(demo, demo_sev, demo_dur))
النطاقات تُبقي الحذر الطبي في البيانات، لا مبعثرًا في عبارات if. كل نطاق يعلن حدًا أدنى وإجراءً؛ وfor يمشي القائمة تنازليًا فيفوز أول حد تتجاوزه الدرجة ، 9.5 تصل إلى الرعاية العاجلة، و4.2 تصل إلى “خلال 24 ساعة”، و2.5 تهبط في المراقبة/العناية الذاتية. سطر التنبيه الختامي في summarize مقصود وليس زخرفة: كل مخرج من هذا المحرك ، نطاق مرتفع أو منخفض ، يحمله، لأن محرك القواعد الذي رتّب الأمراض لا يملك أي سلطة طبية.
🎯 الناتج المتوقع : كتلة من 4 أسطر تذكر Flu، ودرجة من 10، ورسالة نطاق واحدة مطابقة، وسطر تنبيه الاستشارة.
🩹 إذا لم يعمل : إذا وجّهت درجة 9.9 إلى “العناية الذاتية”، فقائمة BANDS مرتبة تصاعديًا وscore >= cutoff يصل إلى الحد المنخفض أولًا. إذا ظهرت الدرجة لكن الرسالة None، فـ recommendation انتهت دون return ، تحقق أن الحلقة تغطي كل درجة ممكنة مع سطر (0.0, …) الأخير كأرضية. إذا بدا المرض الأعلى خاطئًا، فـ ranked[0] يفكّ قائمة غير مرتبة.
4.2 تحقق من النطاقات
✅ قائمة التحقق
- ✅ الدرجات ≥ 8 إلى رعاية عاجلة، و≥ 5 إلى موعد خلال 24 ساعة، و≥ 3 إلى المراقبة، وأدنها إلى العناية الذاتية.
- ✅ الملخص ينتهي دائمًا بسطر تنبيه الاستشارة.
- ✅ يمكنك تفسير ما تشتريه بنية النطاقات مقارنة بدرجة مجردة.
🤔 سؤال (أسئلة) سقراطي(ة)
- النطاقات حدّية حادة، فيقول 4.9 “احجز موعدًا” وكذلك 5.0 ، لكن 5.0 يحفّز أيضًا نفس النص مثل 7.9. ما المعلومة التي يحتاج المستخدمان فعلاً أن تختلف بينهما، وهل نطاق إضافي واحد يصلحها؟
- أنظمة الفرز الحقيقية تستخدم توليفات
AND/OR(حمّى AND طفح) بدلاً من درجات خالصة. أين في هذا خط الأنابيب تدخل قاعدة تلغي الدرجة، ولماذا يجب أن يعيش المنطق الطبي الأعلى خارج النطاقات الرقمية؟
الخطوة 5: ابنِ CLI التفاعلي
الخطوة الأخيرة تصل كل شيء بشخص: يسرد CLI الكتالوج، ويترك المستخدم يختار الأعراض من قائمة مرقّمة، ويجمع الشدة والمدة لكل منها، ويشغّل خط الأنابيب كله، ويطبع الملخص. افتراضي demo يبقي السكربت قابلًا للتشغيل دون كتابة.
5.1 أضف معالجة الإدخال ووضعًا تجريبيًا افتراضيًا
👟 تلميح البداية : رقّم SYMPTOM_LABELS للقائمة، واقبل أرقامًا مفصولة بفواصل، وترجمها إلى مفاتيح آلية، ثم استدعِ summarize. احمِ المسار التفاعلي خلف وسيطة cli صريحة فيبقى التشغيل الافتراضي غير تفاعلي.
# checker.py (continued)
import sys
def run_cli() -> None:
order = sorted(SYMPTOM_LABELS)
print("Which symptoms? Enter numbers, comma-separated:")
for i, key in enumerate(order, 1):
print(f" {i:>2}. {SYMPTOM_LABELS[key]}")
raw = input("> ")
try:
picks = [int(x.strip()) for x in raw.split(",")]
except ValueError:
print("Please enter numbers like: 1, 3, 7"); return
present = {order[p - 1] for p in picks if 1 <= p <= len(order)}
if not present:
print("No valid symptoms selected. Nothing to score."); return
severities = {}
durations_days = {}
for key in present:
sev = input(f"{SYMPTOM_LABELS[key]} severity (mild/moderate/severe): ").strip().lower()
dur = input(f"{SYMPTOM_LABELS[key]} duration in days: ").strip()
severities[key] = sev if sev in {"mild", "moderate", "severe"} else "moderate"
try:
durations_days[key] = float(dur)
except ValueError:
durations_days[key] = 1.0
print("\n" + summarize(present, severities, durations_days))
def run_demo() -> None:
severity = {"fever": "high", "body_aches": "severe", "fatigue": "moderate", "cough": "moderate"}
duration = {"fever": 2, "cough": 3, "body_aches": 1, "fatigue": 10}
print(summarize(demo, severity, duration))
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "cli":
run_cli()
else:
run_demo()
القائمة تقوم بتعقيم الإدخال في ثلاث نقاط متعمدة: int(x.strip()) يحوّل الأرقام المكتوبة ويتجاهل الفراغات، وif 1 <= p <= len(order) يسقط الاختيارات خارج النطاق بصمت بدلاً من الانهيار، والإجابات الخاطئة على الشدة/المدة تتراجع إلى القيم الافتراضية المسجّلة (moderate، 1 day) بدلاً من إلغاء الجلسة. مسار demo موجود لأن دفترًا أو CI أو قارئًا لأول مرة يحتاج طريقة بلا إدخال لتمرين خط الأنابيب كله ، وضع cli التفاعلي يحتاج إنسانًا حقيقيًا أمام لوحة مفاتيح حقيقية.
🎯 الناتج المتوقع : تشغيل uv run python checker.py يطبع ملخص التجربة (لا إدخال مطلوب). تشغيل uv run python checker.py cli يعرض القائمة المرقّمة، ويجمع إجاباتك، ويطبع ملخصًا للأعراض التي اخترتها.
🩹 إذا لم يعمل : إذا انهار وضع cli على اختيار غير رقمي، فحارس except ValueError حول فهم القائمة مفقود. إذا كانت مفاتيح القائمة لا تطابق الأعراض المقيَّمة، فـ order (من SYMPTOM_LABELS) ومفاتيح KNOWLEDGE الآلية غير متطابقة ، كان يجب أن يلتقطها فحص الخطوة 1. إذا علّق input() إلى الأبد في دفتر، فأنت في المسار التفاعلي بلا لوحة مفاتيح ، التزم بالتجربة بلا وسائط هناك.
5.2 تحقق من CLI من النهاية إلى النهاية
✅ قائمة التحقق
- ✅
uv run python checker.pyيطبع ملخص التجربة دون أي إدخال. - ✅
uv run python checker.py cliيعرض قائمة مرقّمة، ويقبل اختيارات مفصولة بفواصل، ويطبع ملخصًا. - ✅ إدخال قمامة مثل
abcأو99لا ينهار CLI ، بل يحذّر ويكمل. - ✅ تشغيل الوضعين ينتهي بتنويه الاستشارة.
🤔 سؤال (أسئلة) سقراطي(ة)
- سيكتب المستخدمون العرض نفسه “sore throat” و”Sore Throat” و”throat”. القائمة تتجاوز هذا بالأرقام ، ما كلفة هذا التنظيف، وكيف يقدّم مطابق نص ضبابي مخاطر جديدة هنا لا يملكها الأرقام؟
- مسار التجربة افتراضي والمسار التفاعلي اختياري. في أداة قريبة من السلامة، لماذا يعد الافتراضي إلى الأقل تفاعلية والأكثر حتمية خيارًا دفاعيًا ، وما الذي يغريك بقلبه؟
⚠️ المآزق الشائعة
- خلط التصنيفات البشرية بالمفاتيح الآلية. يكتب المستخدمون “itchy eyes”، وتخزّن قاعدة المعرفة
itchy_eyes؛ مطابقةs in presentضد أحدهما وطباعة الآخر تنتج عدم تطابق صامت. الإصلاح: أبقِSYMPTOM_LABELSالترجمة البشرية↔الآلية الوحيدة، ولا تسلّم نص المستخدم الخام للتقييم أبدًا. - درجات تتجاوز 10 أو تنجرف بلا سقف. يجب أن يردّ كل حد في
urgency_scoreبقطعmin(10, …)، وإلا تجاوزت حالة مدة طويلة + شديدة النطاق المصمم وطابقت “12.4” بصمت النطاق العاجل. - ترتيب يتجاهل المدة.
{"headache"}لمدة 9 أيام يُرتّب مثل{"headache"}جديد ، لا تستطيع الدرجة تفسير المزمن. حدchronic_bonusموجود تحديدًا ليُحرك “تجاوز الأسبوع” الدرجة. - حدود نطاق مرتبة خطأ. إذا كانت
BANDSتصاعدية، تصطدم درجة مرتفعة بالنطاق (الأول) الخاطئ. أبقها تنازلية ودع أولscore >= cutoffيفوز، كما في الخطوة 4. - معاملة أعلى نسبة كتشخيص. المحرك يطابق الأعراض بأنماط معروفة؛ التداخل لا يساوي السببية، ويجب أن ينجو سطر التنبيه من كل مسار برمجي. إزالته من أي ملخص تجاوز لما يمكن لمحرك قواعد ادعاءه.
ما بنيته للتو
محرك فرز أعراض عامل بنموذج بيانات حقيقي ، قاعدة معرفية مرجّحة، درجات مطابقة مُطبّعة، درجة استعجال محدودة تمزج الشدة والمدة، أربعة نطاقات رعاية، وCLI تفاعلي معقّم، كل ذلك ببايثون نقية مع تنبيه يقف خلف كل توصية. المهارة القابلة للنقل هي تحويل المعرفة المجالية إلى بنى بيانات مُقيَّمة: نفس نمط التراكب المرجّح والنطاقات يتعمّم على مطابقة الوظائف، وتصحيح الاختبارات، وبوابات الميزات، وأي مكان يجب فيه لمنتج ترتيب خيارات أمام أدلة جزئية.
شغّل نسخة أكمل دون أي إعداد محلي
examples/symptom-checker/ في دورة الكود نسخة أكمل من الكود أعلاه، بكتالوج أعراض أغنى وعرض CLI تجريبي مُشغَّل مسبقًا في الدفتر. استنسخها، أو افتح الدورة الكاملة في GitHub Codespace، وشغّلها من هناك.
إلى أين تذهب من هنا
- أضف علامة تبويب تاريخ الأعراض: تتبّع درجات المستخدم على مدار آخر أسبوع من الإجابات واعرض “يتحسّن / يزداد سوءًا” كنطاق بحد ذاته.
- دع المستخدمين يكتبون موقعًا من الجسم (رأس، حلق، بطن) وصفِّ القائمة إلى أعراض تلك المنطقة ، حقل تصنيف بسيط على كل مرض.
- ثبّت الأمراض في ملف
conditions.jsonمنفصل يحمّله المحرك عند التشغيل، فلا يتطلب إضافة مرض تحرير كود التقييم أبدًا. - اكتب
test_checker.pyصغيرًا يثبت الدرجة لخمس حالات منتقاة (بما فيها المثالان من أسئلة الخطوة 3)، فلا يمكن لإعادة هيكلة مستقبلية تغيير نتائج الفرز بصمت.
شارك مشروعك مع الفصل
بنيت شيئًا تفتخر به؟ examples/student-projects/ معرض لمشاريع قدمها طلاب آخرون ، و README الخاص به يحتوي على دليل كامل ومناسب للمبتدئين لإضافة مشروعك عبر طلب سحب، حتى لو لم تستخدم git من قبل: تفرّع المستودع، وإنشاء فرع، وعمل commit لملفاتك، وفتح طلب السحب، خطوة بخطوة. لا يُفترض خبرة git مسبقة.
أهلاً بكتابة بايثون خارج المتصفح. 🎓
أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.