المشروع النهائي: إدارة درجات الطلاب (الجزء 2)

في الدرس 34، أكملنا طبقة البيانات — عمليات CRUD للطلاب والدرجات. في هذا الدرس، نبني طبقة المنطق فوق طبقة البيانات: الحسابات الإحصائية، الترتيب، تحليل توزيع الدرجات، ومنطق الأعمال الأساسي الآخر. الدرس 36 سيضيف طبقة العرض (واجهة القائمة) لإكمال النظام.


تنفيذ طبقة المنطق

هذه الطبقة تعتمد على دوال طبقة البيانات من الدرس 34 (load_data, get_all_students، إلخ.).

مثال: طبقة منطق إحصائيات الدرجات

PYTHON
# ====== طبقة المنطق: إحصائيات وتحليل الدرجات ======

import statistics
from grade_project_part1 import load_data, get_all_students


def calculate_student_average(student_id):
    """احسب متوسط درجة طالب واحد"""
    data = load_data()
    student = data["students"].get(student_id)
    if not student:
        return None

    scores = student["scores"].values()
    if not scores:
        return None

    return sum(scores) / len(scores)


def get_student_report(student_id):
    """أنشئ بطاقة تقرير فردية"""
    data = load_data()
    student = data["students"].get(student_id)
    if not student:
        return None

    report = {
        "name": student["name"],
        "class_name": student["class_name"],
        "scores": dict(student["scores"]),
    }

    scores = list(student["scores"].values())
    if scores:
        report["average"] = round(sum(scores) / len(scores), 1)
        report["max_score"] = max(scores)
        report["min_score"] = min(scores)
        report["total"] = sum(scores)
    else:
        report["average"] = None
        report["max_score"] = None
        report["min_score"] = None
        report["total"] = 0

    return report


def get_class_ranking(class_name=None):
    """احصل على ترتيب الفصل (حسب المجموع الإجمالي تنازلياً)"""
    data = load_data()
    students = data["students"]

    results = []
    for sid, info in students.items():
        if class_name and info["class_name"] != class_name:
            continue

        scores = list(info["scores"].values())
        total = sum(scores) if scores else 0
        avg = round(total / len(scores), 1) if scores else 0

        results.append({
            "student_id": sid,
            "name": info["name"],
            "class_name": info["class_name"],
            "total": total,
            "average": avg,
            "score_count": len(scores)
        })

    results.sort(key=lambda x: x["total"], reverse=True)

    for i, r in enumerate(results, 1):
        r["rank"] = i

    return results


def get_subject_averages():
    """احسب متوسطات درجات كل مادة"""
    data = load_data()
    subjects = data["subjects"]
    students = data["students"]

    result = {}
    for subject in subjects:
        scores = []
        for info in students.values():
            if subject in info["scores"]:
                scores.append(info["scores"][subject])

        if scores:
            result[subject] = {
                "average": round(sum(scores) / len(scores), 1),
                "max": max(scores),
                "min": min(scores),
                "count": len(scores)
            }
        else:
            result[subject] = None

    return result


def get_score_distribution(subject=None):
    """احسب توزيع الدرجات عبر النطاقات"""
    data = load_data()
    students = data["students"]

    ranges = [
        ("90-100", 90, 101),
        ("80-89", 80, 90),
        ("70-79", 70, 80),
        ("60-69", 60, 70),
        ("0-59", 0, 60),
    ]

    if subject:
        result = {label: 0 for label, _, _ in ranges}
        for info in students.values():
            if subject in info["scores"]:
                score = info["scores"][subject]
                for label, low, high in ranges:
                    if low <= score < high:
                        result[label] += 1
                        break
        return result
    else:
        result = {label: 0 for label, _, _ in ranges}
        for info in students.values():
            for score in info["scores"].values():
                for label, low, high in ranges:
                    if low <= score < high:
                        result[label] += 1
                        break
        return result


def get_students_by_class():
    """جمّع الطلاب حسب الفصل"""
    data = load_data()
    students = data["students"]

    classes = {}
    for sid, info in students.items():
        cls = info["class_name"]
        if cls not in classes:
            classes[cls] = []
        classes[cls].append({
            "student_id": sid,
            "name": info["name"],
            "score_count": len(info["scores"])
        })

    return classes
▶ جرّب الكود

اختبار طبقة المنطق

مثال: اختبار وظيفي لطبقة المنطق

PYTHON
if __name__ == "__main__":
    print("=== Individual Report Card ===")
    report = get_student_report("2024001")
    if report:
        print(f"Name: {report['name']}")
        print(f"Class: {report['class_name']}")
        for subject, score in report["scores"].items():
            print(f"  {subject}: {score}")
        print(f"Total: {report['total']}")
        print(f"Average: {report['average']}")
        print(f"Max: {report['max_score']}")
        print(f"Min: {report['min_score']}")

    print("\n=== Class Ranking ===")
    ranking = get_class_ranking()
    for r in ranking[:5]:
        print(f"#{r['rank']}: {r['name']} ({r['class_name']}) Total {r['total']}")

    print("\n=== Subject Averages ===")
    averages = get_subject_averages()
    for subject, info in averages.items():
        if info:
            print(f"{subject}: Avg {info['average']}, Max {info['max']}, Min {info['min']}")

    print("\n=== Score Distribution ===")
    dist = get_score_distribution()
    for label, count in dist.items():
        bar = "#" * count
        print(f"{label}: {bar} {count} students")
▶ جرّب الكود

تصميم معالجة الاستثناءات

طبقة المنطق تتعامل مع الحالات الاستثنائية التالية:

السيناريو القيمة المُرجعة الوصف
الطالب غير موجود None أو نتيجة فارغة المتصل يفحص القيمة المُرجعة
لا توجد بيانات درجة قيمة الإحصاء None المتصل يعرض "لا توجد درجات بعد"
بيانات فارغة قائمة فارغة/قاموس فارغ لن ينهار، يعرض رسالة ودية
بيانات غير صالحة استثناء مُلتقط محمي بواسطة try-except

❓ أسئلة شائعة

س كيف أفهم key=lambda في الترتيب؟
ج معامل key يُحدد معيار الترتيب. lambda item: item[1] تعني "خذ العنصر الثاني من كل عنصر كمفتاح ترتيب." هذا يعادل def get_score(item): return item[1] — lambda مجرد اختصار. س:أليس إرجاع None بدلاً من 0 يجعل الأمور أصعب للمتصل؟ ج:إرجاع None يميِّز بين "لا توجد درجة" و "الدرجة هي 0 فعلياً" — فرق ذو معنى. يمكن للمتصل استخدام if result is not None: للتحقق، وهو أكثر أماناً من معاملة "لا توجد بيانات" خطأً على أنها "0 نقطة."

📖 ملخص

  • طبقة المنطق تقع فوق طبقة البيانات، تركز على "الحساب" بدلاً من "التخزين"
  • بطاقة التقرير الفردية: تعرض درجات كل مادة، المجموع، المتوسط، الأعلى/الأدنى
  • ترتيب الفصل: مرتب حسب المجموع الإجمالي تنازلياً، مع أرقام ترتيب مُنشأة تلقائياً
  • متوسطات المواد: تكرار درجات جميع الطلاب، تجميع حسب المادة
  • توزيع الدرجات: عد الطلاب في كل نطاق درجات
  • معالجة الاستثناءات: إرجاع None بدلاً من التسبب في خطأ عندما لا يكون الطالب/الدرجة موجوداً

📝 تمارين

  1. مبتدئ (الصعوبة ⭐): استدعِ get_student_report() لعرض بطاقة تقرير كاملة لطالب واحد وتحقق من صحة المخرجات.

  2. متوسط (الصعوبة ⭐⭐): أضف دالة get_top_n(n) إلى طبقة المنطق تُرجع أفضل N طالباً حسب المجموع الإجمالي في المدرسة بأكملها.

  3. متقدم (الصعوبة ⭐⭐⭐): أضف دالة get_subject_pass_rate(subject, pass_score=60) إلى طبقة المنطق تحسب نسبة النجاح لمادة معينة (عدد الطلاب بدرجة >= pass_score / إجمالي الطلاب). ثم احسب وقارن نسب النجاح عبر جميع المواد.

100%