الأبحاث · جولةُ البرمجة الأولى

CB-2-01 — تتبّع خطأ وإصلاحه · Python · عالٍ

جولةُ البرمجة الأولى · 2026-10-09 · GAIBRA v2.30.03 · واجبُ التشغيل

نتيجةُ الجولة

الترتيبالمتنافسالرمز أمام المحكّمالكلماتالاختباراتOpusGrokالمجموعالصدق (Opus · Grok)
1Claude Sonnetد1,57712/1295.0100.097.512 · 12
2GAIBRAأ1,58012/1289.094.091.57 · 11
3ChatGPTب70612/1287.083.085.010 · 9
4Geminiج55612/1271.073.072.04 · 7
5DeepSeekهـ73710/1254.271.262.72 · 8

نصُّ السيناريو كما كتبه المولّد

اشتغل على نظام فوترة لمطاعم في الكويت، والعملة بالدينار الكويتي بثلاث خانات عشرية (1000 فلس = دينار واحد). سياسة العميل ثابتة ولا تقبل النقاش: كل سطر في الفاتورة يُقرَّب إلى أقرب فلس بنظام «النصف إلى الأعلى» (half-up). المشكلة: على بعض الفواتير، مجموع الأسطر بعد التقريب لا يساوي إجمالي الفاتورة بفلس واحد، وتكاملنا مع برنامج المحاسبة عند العميل يرفض المستند بسبب عدم التطابق. الكود الحالي (ملف invoicing.py كاملاً): Python from dataclasses import dataclass, field TAX\_RATE = 0.15 @dataclass class Line:   name: str   qty: int   unit\_price: float @dataclass class Invoice:   lines: list = field(default\_factory=list)   discount: float = 0.0 # total discount in KWD def round\_kwd(amount):   return round(amount, 3) def line\_total(line):   return round\_kwd(line.qty \* line.unit\_price) def compute\_invoice(inv):   subtotal = sum(line\_total(l) for l in inv.lines)   subtotal = round\_kwd(subtotal - inv.discount)   tax = round\_kwd(subtotal \* TAX\_RATE)   total = round\_kwd(subtotal + tax)   return {"subtotal": subtotal, "tax": tax, "total": total} الاختبار الذي يفشل عندنا في CI: plain \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ test\_invoice\_matches\_erp \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_   inv = Invoice(lines=\[Line("برجر", 3, 2.885), Line("مشروب", 2, 1.475)], discount=0.0)   result = compute\_invoice(inv) > assert result\["total"] == sum(line\_total(l) for l in inv.lines) + result\["tax"] E AssertionError: assert 11.771 == 11.772 جرّبت تغيير الترتيب (خصم قبل/بعد الضريبة) والنتيجة نفسها على فواتير أخرى. المطلوب: تحديد السبب الجذري للمشكلة وتقديم ملف واحد مستقل معدَّل بحيث يكون إجمالي الفاتورة دائماً مساوياً لمجموع الأسطر المعروضة للعميل + الضريبة المحسوبة على ذلك الأساس، مع بقاء سياسة التقريب half-up لكل سطر على حدة. القيود: Python 3.11، المكتبة القياسية فقط، سلوك حتمي، الحل لا يتجاوز 300 سطراً.

الردودُ الخمسة كما وصلت المحكّمين

الرمزُ هو ما رآه المحكّمان؛ والاسمُ كُشف بعد التحكيم.

أGAIBRA91.5 · 1,580 كلمة
الخلاصة الجذر هو حساب المال بـ float ثمّ تقريبه بـ round(). القيمة 2.885 تُخزَّن أصغر قليلاً من حقيقتها، فيصير سطر البرجر 8.654 بدل 8.655، ويخسر الإجمالي فلساً واحداً. لكنّ الرسالة 11.771 == 11.772 لا يمكن أن تصدر عن هذا الكود بهذه البيانات. ما يفشل في CI إمّا مُدخَلٌ آخر وإمّا نسخةٌ أخرى من الملف. الإصلاح: Decimal مع ROUND\_HALF\_UP من حدود الإدخال إلى المُخرَج، وفحوصٌ تؤكّد قيمك داخل الملف. تتبّعتُ الكود الأصليّ يدويّاً على فاتورتك نفسها، ولم أشغّل أيّ شيء. القيمة 2.885 لا تُمثَّل في binary تمثيلاً دقيقاً. أقرب double إليها يقع تحتها بفارقٍ ضئيل. ضربُها في 3 يُعطي الـ double الأقرب إلى 8.655، وهو أيضاً تحت 8.655. الدالّة round(x, 3) في Python تقرّب القيمة الثنائيّة الفعليّة لا الرقم العشريّ الذي كتبتَه، فتُنتج 8.654. سطر المشروب لا يتأثّر: 1.475 مخزَّنة فوق حقيقتها قليلاً، فيخرج السطر 2.95. البند الكود الأصليّ (تتبّع يدويّ) ما تفرضه سياسة half-up برجر 3 × 2.885 8.654 8.655 مشروب 2 × 1.475 2.95 2.950 مجموع الأسطر 11.604 11.605 الضريبة 1.741 1.741 (من 1.74075) الإجمالي 13.345 13.346 الكود إذن يُخرج على هذه البيانات إجماليّاً يقارب 13.345، لا 11.771. الطرفان في اختبارك لا يختلفان إلّا بضجيج float، لأنّ الخصم صفر. الفرق الحقيقيّ في هذه الفاتورة يظهر في مكانٍ آخر. أنت تعرض للعميل سطراً قيمته 8.655 عشريّاً، وتحسب الإجمالي على 8.654. وبرنامج المحاسبة عند العميل يحسب بالعشريّ، فيرى فرق الفلس ويرفض المستند. أمّا الرقمان 11.771 و11.772 فلا أستطيع ربطهما بسببٍ محدّد من دون الفاتورة الفعليّة التي فشلت في CI. أقرب الاحتمالات ثلاثة: فاتورة أخرى غير التي في نصّ الاختبار، أو نسخة من invoicing.py بقيمة TAX\_RATE مختلفة، أو فاتورة فيها خصم. مهمّ اختبارك نفسه لا يتضمّن الخصم: يقارن total بمجموع الأسطر + الضريبة. أيّ فاتورة بخصمٍ قدره 0.001 تُنتج بالضبط فجوة فلسٍ واحد مثل التي رأيتها، والكود سليمٌ في هذه الجزئيّة. الثابت الصحيح هو: الإجمالي = مجموع الأسطر − الخصم + الضريبة. الإصلاح \# invoicing.py """Kuwaiti-dinar invoicing with per-line half-up rounding to the fils. All money is Decimal. Floats are accepted only at the input boundary and are read through their shortest repr ("2.885" stays 2.885), never through their binary value. Run `python invoicing.py` to execute the self-checks. """ from \_\_future\_\_ import annotations import unittest from dataclasses import dataclass, field from decimal import ( &#x20; ROUND\_DOWN, &#x20; ROUND\_HALF\_UP, &#x20; Context, &#x20; Decimal, &#x20; DivisionByZero, &#x20; InvalidOperation, &#x20; Overflow, &#x20; localcontext, ) from typing import Union Number = Union\[Decimal, int, str, float] FILS = Decimal("0.001") ZERO\_KWD = Decimal("0.000") TAX\_RATE = Decimal("0.15") \_MONEY\_CONTEXT = Context( &#x20; prec=28, &#x20; rounding=ROUND\_HALF\_UP, &#x20; traps=\[InvalidOperation, DivisionByZero, Overflow], ) class InvoiceError(ValueError): &#x20; """Raised when an invoice, a line or an amount violates input rules.""" def to\_decimal(value: Number, field\_name: str) -> Decimal: &#x20; """Convert an input amount to a finite Decimal without binary noise. &#x20; Raises InvoiceError for bool, unsupported types, unparsable text, NaN, infinity. &#x20; """ &#x20; if isinstance(value, bool): &#x20; raise InvoiceError(f"{field\_name}: bool is not a number") &#x20; with localcontext(\_MONEY\_CONTEXT): &#x20; try: &#x20; if isinstance(value, Decimal): &#x20; result = value &#x20; elif isinstance(value, int): &#x20; result = Decimal(value) &#x20; elif isinstance(value, float): &#x20; result = Decimal(repr(value)) &#x20; elif isinstance(value, str): &#x20; result = Decimal(value.strip()) &#x20; else: &#x20; raise InvoiceError( &#x20; f"{field\_name}: unsupported type {type(value).\_\_name\_\_}" &#x20; ) &#x20; except InvalidOperation as exc: &#x20; raise InvoiceError(f"{field\_name}: not a number: {value!r}") from exc &#x20; if not result.is\_finite(): &#x20; raise InvoiceError(f"{field\_name}: must be finite, got {value!r}") &#x20; return result def round\_kwd(amount: Number) -> Decimal: &#x20; """Round an amount to the nearest fils; exact ties go up (half-up).""" &#x20; with localcontext(\_MONEY\_CONTEXT): &#x20; return to\_decimal(amount, "amount").quantize(FILS, rounding=ROUND\_HALF\_UP) def \_validate\_line\_fields(name: object, qty: object) -> None: &#x20; """Reject empty names and non-positive or non-integer quantities.""" &#x20; if not isinstance(name, str) or not name.strip(): &#x20; raise InvoiceError("line name must be a non-empty string") &#x20; if isinstance(qty, bool) or not isinstance(qty, int) or qty < 1: &#x20; raise InvoiceError(f"line qty must be a positive integer, got {qty!r}") def \_validated\_unit\_price(value: Number) -> Decimal: &#x20; """Return the unit price as a non-negative finite Decimal.""" &#x20; price = to\_decimal(value, "unit\_price") &#x20; if price < 0: &#x20; raise InvoiceError(f"unit\_price must not be negative, got {value!r}") &#x20; return price def \_validated\_discount(value: Number) -> Decimal: &#x20; """Return the discount as a non-negative Decimal rounded half-up to the fils.""" &#x20; discount = to\_decimal(value, "discount") &#x20; if discount < 0: &#x20; raise InvoiceError(f"discount must not be negative, got {value!r}") &#x20; return round\_kwd(discount) @dataclass class Line: &#x20; """One invoice line; unit\_price is normalised to Decimal on construction.""" &#x20; name: str &#x20; qty: int &#x20; unit\_price: Number &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; \_validate\_line\_fields(self.name, self.qty) &#x20; self.unit\_price = \_validated\_unit\_price(self.unit\_price) @dataclass class Invoice: &#x20; """An invoice; discount is a total amount in KWD, normalised to Decimal.""" &#x20; lines: list\[Line] = field(default\_factory=list) &#x20; discount: Number = ZERO\_KWD &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; if not isinstance(self.lines, list) or not all( &#x20; isinstance(item, Line) for item in self.lines &#x20; ): &#x20; raise InvoiceError("lines must be a list of Line") &#x20; self.discount = \_validated\_discount(self.discount) def line\_total(line: Line) -> Decimal: &#x20; """Return qty x unit\_price rounded half-up to the fils (the displayed amount).""" &#x20; if not isinstance(line, Line): &#x20; raise InvoiceError(f"expected Line, got {type(line).\_\_name\_\_}") &#x20; \_validate\_line\_fields(line.name, line.qty) &#x20; price = \_validated\_unit\_price(line.unit\_price) &#x20; with localcontext(\_MONEY\_CONTEXT): &#x20; return round\_kwd(price \* line.qty) def compute\_invoice(inv: Invoice) -> dict\[str, object]: &#x20; """Compute an invoice whose total equals displayed lines - discount + tax. &#x20; Returns Decimal amounts: subtotal (after discount), tax, total, plus &#x20; line\_totals, lines\_total and the applied discount for the ERP payload. &#x20; Raises InvoiceError if the discount exceeds the sum of the lines. &#x20; """ &#x20; if not isinstance(inv, Invoice): &#x20; raise InvoiceError(f"expected Invoice, got {type(inv).\_\_name\_\_}") &#x20; line\_totals = \[line\_total(line) for line in inv.lines] &#x20; with localcontext(\_MONEY\_CONTEXT): &#x20; lines\_total = sum(line\_totals, ZERO\_KWD) &#x20; discount = \_validated\_discount(inv.discount) &#x20; if discount > lines\_total: &#x20; raise InvoiceError( &#x20; f"discount {discount} exceeds lines total {lines\_total}" &#x20; ) &#x20; subtotal = lines\_total - discount &#x20; tax = round\_kwd(subtotal \* TAX\_RATE) &#x20; total = subtotal + tax &#x20; return { &#x20; "subtotal": subtotal, &#x20; "tax": tax, &#x20; "total": total, &#x20; "line\_totals": line\_totals, &#x20; "lines\_total": lines\_total, &#x20; "discount": discount, &#x20; } def \_user\_invoice(discount: Number = 0.0) -> Invoice: &#x20; return Invoice( &#x20; lines=\[Line("برجر", 3, 2.885), Line("مشروب", 2, 1.475)], discount=discount &#x20; ) class InvoiceSelfCheck(unittest.TestCase): &#x20; """Executable assertions of the values traced by hand in the reply.""" &#x20; def test\_invoice\_matches\_erp(self) -> None: &#x20; inv = \_user\_invoice() &#x20; result = compute\_invoice(inv) &#x20; self.assertEqual( &#x20; result\["total"], sum(line\_total(l) for l in inv.lines) + result\["tax"] &#x20; ) &#x20; def test\_user\_invoice\_line\_totals\_are\_half\_up(self) -> None: &#x20; result = compute\_invoice(\_user\_invoice()) &#x20; self.assertEqual(result\["line\_totals"], \[Decimal("8.655"), Decimal("2.950")]) &#x20; def test\_user\_invoice\_totals(self) -> None: &#x20; result = compute\_invoice(\_user\_invoice()) &#x20; self.assertEqual(result\["subtotal"], Decimal("11.605")) &#x20; self.assertEqual(result\["tax"], Decimal("1.741")) &#x20; self.assertEqual(result\["total"], Decimal("13.346")) &#x20; def test\_discount\_keeps\_total\_balanced(self) -> None: &#x20; result = compute\_invoice(\_user\_invoice(discount="0.5")) &#x20; self.assertEqual(result\["subtotal"], Decimal("11.105")) &#x20; self.assertEqual(result\["tax"], Decimal("1.666")) &#x20; self.assertEqual(result\["total"], Decimal("12.771")) &#x20; self.assertEqual( &#x20; result\["total"], &#x20; sum(result\["line\_totals"], ZERO\_KWD) - result\["discount"] + result\["tax"], &#x20; ) &#x20; def test\_exact\_tie\_rounds\_up\_not\_to\_even(self) -> None: &#x20; self.assertEqual(line\_total(Line("x", 1, "1.0005")), Decimal("1.001")) &#x20; def test\_result\_independent\_of\_caller\_decimal\_context(self) -> None: &#x20; with localcontext() as ctx: &#x20; ctx.prec = 3 &#x20; ctx.rounding = ROUND\_DOWN &#x20; result = compute\_invoice(\_user\_invoice()) &#x20; self.assertEqual(result\["total"], Decimal("13.346")) &#x20; def test\_rejects\_invalid\_inputs(self) -> None: &#x20; with self.assertRaises(InvoiceError): &#x20; Line("x", 0, "1.000") &#x20; with self.assertRaises(InvoiceError): &#x20; Line("x", 1, "-1") &#x20; with self.assertRaises(InvoiceError): &#x20; Line("x", 1, float("nan")) &#x20; with self.assertRaises(InvoiceError): &#x20; compute\_invoice(\_user\_invoice(discount="20")) if \_\_name\_\_ == "\_\_main\_\_": &#x20; unittest.main() التحقّق يتمّ بأمرٍ واحد: python invoicing.py. القيم المثبّتة في الاختبارات هي نتائج تتبّعي اليدويّ، والاختبار الذي يفشل عندك في CI موجودٌ فيها باسمه. هذه كلّ فروق السلوك عن الأصل: نوع القيم المُعادة: صارت كلّها Decimal بدل float. مفاتيح جديدة في الناتج: line\_totals وlines\_total وdiscount. نتيجة فاتورتك نفسها: تتغيّر من 13.345 إلى 13.346، ومن 8.654 إلى 8.655 في سطر البرجر. التقريب: الأنصاف الدقيقة تُقرَّب إلى الأعلى دائماً، لا إلى الزوجيّ. التحويل: unit\_price وdiscount يتحوّلان إلى Decimal عند إنشاء الكائن، وأيّ float يُقرأ عبر repr. الخصم: يُقرَّب half-up إلى الفلس قبل طرحه. أمّا الأصل فكان يقرّب ناتج الطرح بـ round(). مدخلات صارت مرفوضة: الكمّيّة صفرٌ أو سالبةٌ أو bool، والسعر السالب، وNaN واللانهاية، والاسم الفارغ، والنصوص غير الرقميّة، والخصم السالب، والخصم الأكبر من مجموع الأسطر. كلّها ترمي InvoiceError، وهي فرعٌ من ValueError. تشغيل الملف مباشرةً: صار ينفّذ الاختبارات. شرح السبب العيب ليس في ترتيب الخصم والضريبة، ولذلك لم يُفِد تبديلُه. العيب في نوع البيانات. float يحمل كسراً ثنائيّاً، والسياسة عشريّة. كلّ قيمةٍ تنتهي عند الحدّ الفاصل، مثل الفلس الأخير في 8.655، يُحسم تقريبُها بحسب موقعها الثنائيّ، وهذا الموقع قد يقع تحت الحدّ أو فوقه. هذا هو التقريب الأوّل الذي يخون السياسة. وهناك خيانةٌ ثانية حتّى حين تكون القيمة دقيقة: round() تتّبع قاعدة half-to-even، وهي ليست half-up. لهذا العيب طابعٌ نظاميّ. كلّ موضعٍ آخر في النظام يحسب المال بـ float يحمل الخطر نفسه: التقارير، والمرتجعات، والتصدير إلى برنامج المحاسبة. الإصلاح آمنٌ تحت التزامن. localcontext محلّيٌّ لكلّ thread، والكائنات لا تتشارك حالةً قابلةً للتعديل. والناتج لا يتأثّر بأيّ context عشريّ يضبطه المستدعي، وهذا ما يغطّيه أحد الاختبارات. الوقاية امنع float في أيّ حقلٍ ماليّ عند حدود النظام. مرّر المبالغ نصوصاً أو Decimal من قاعدة البيانات ومن واجهة الـ POS. صحّح كذلك ثابت اختبارك ليتضمّن الخصم، وأضِف اختبار خصائص بمولّد random ذي بذرةٍ ثابتة. هذا الاختبار يولّد فواتير كثيرة ويؤكّد عليها جميعاً أنّ الإجمالي يساوي مجموع الأسطر − الخصم + الضريبة. وأيّ عودةٍ إلى float ستكسر هذا الثابت عند أوّل قيمةٍ على الحدّ. قراري أن تعتمد هذا الملف الآن. ولكي أحسم مصدر 11.771 تحديداً: ما الأسطر والخصم وقيمة TAX\_RATE في الفاتورة التي فشلت فعلاً في CI؟ إن أرسلتها هنا تتبّعتُها سطراً سطراً، وتبيّن إن كانت فجوة الفلس من الخصم أم من نسخةٍ أخرى من الملف. 🏃 لم يُشغَّل — السبب: Docker غيرُ متاح: docker: An error occurred trying to start process 'docker' with working directory 'D:\\Sites\\gaibra.ai'. The system cannot find the file specified. ما لا أعرفه: تصنيفُ السبب الجذريّ لهذا الخطأ بعينه
دClaude Sonnet97.5 · 1,577 كلمة
Working through a floating-point rounding puzzle. سأعيد إنتاج الفشل أولاً لأحدد السبب بدقة، ثم أكتب الملف المعدَّل وأختبره. Created a file, shared a file, ran 3 commands السبب الجذري ترتيب الخصم والضريبة ليس سبب المشكلة. السبب هو تمثيل المال بـ float واستخدام round()، وهذا ينتج ثلاثة أخطاء متراكبة: round() في بايثون ليس half-up. هو تقريب «النصف إلى الزوجي» (banker’s rounding). مثلاً round(2.5) يعطي 2. الـ float لا يمثّل الفلس بدقة. القيمة 2.885 مخزَّنة فعلياً كـ 2.884999999…، و1.475 مخزَّنة كـ 1.475000…09. لذلك تقع القيم المنتهية بـ 5 أحياناً تحت نقطة المنتصف وأحياناً فوقها بشكل غير متوقع. جرّبت كل حالات المنتصف من 0.0005 إلى 9.9995، وكان round\_kwd مخالفاً لسياسة half-up في نصفها تماماً. مثال: 0.0045 تصبح 0.004 بدلاً من 0.005. الجمع والمقارنة بـ float. المجموع sum(...) + tax لا يطابق بالضرورة بتاً ببت نتيجة round(subtotal + tax, 3). على 20,000 فاتورة عشوائية بالكود الحالي، فشل شرط الاختبار في 5,343 منها. ملاحظة مهمة: المدخلات المكتوبة في الاختبار (3 × 2.885 و2 × 1.475) تنجح فعلياً عندي بالكود القديم، والإجمالي 13.346. أما القيم 11.771 و11.772 في السجل فلا تنتج عن هذه الأسطر. على الأرجح السجل منسوخ من تشغيل لفاتورة أخرى، فيستحق أن تراجع أي فاتورة فشلت فعلاً في CI. الإصلاح المدخلات تُحوَّل إلى Decimal من نصّها، فيصبح 2.885 هو 2.885 بالضبط. الـ float مقبول ويُحوَّل عبر repr. كل سطر يُقرَّب منفرداً بـ ROUND\_HALF\_UP ويُخزَّن كعدد صحيح من الفلوس. ما بعد ذلك كله جمع وطرح أعداد صحيحة، فلا انحراف ممكن: subtotal = مجموع الأسطر − الخصم tax = half\_up(subtotal × 0.15) مرة واحدة total = subtotal + tax التطابق مضمون بالبناء نفسه، وعليه assert داخلي احتياطاً. للتصدير إلى برنامج المحاسبة استخدم as\_dict(). هي تُرجع نصوصاً بثلاث خانات بالضبط ("13.346")، فلا يدخل float في الـ JSON أبداً. result\["total"] ما زال يعمل للتوافق مع الكود القديم، لكنه يُرجع Decimal الآن. اختبرت الملف على حالة الـ CI، وعلى حالات half-up الحدّية، وعلى 50,000 فاتورة عشوائية فيها خصومات، ونجحت كلها. اختبار CI يحتاج تعديلاً صغيراً إذا كانت الفاتورة فيها خصم، لأنه لا يطرح الخصم حالياً: result.total == sum(line\_total(l) for l in inv.lines) - result.discount + result.tax Invoicing · PY """ Invoicing for Kuwaiti restaurants (KWD, 3 decimals: 1 KWD = 1000 fils). &#x20; Policy: every invoice line is rounded to the nearest fils, half-up, independently. Invoice total is ALWAYS exactly: &#x20; &#x20; sum(displayed line totals) - discount + tax &#x20; where tax is computed (and rounded half-up once) on that same basis. &#x20; Root cause of the old mismatches: &#x20; 1. Money was stored as binary float. 2.885 is really 2.88499999999999978..., &#x20; so "x.xxx5" values sit just below or above the midpoint unpredictably. &#x20; 2. Python's round() is round-half-EVEN (banker's rounding), not half-up, &#x20; and it operates on that inexact binary value. &#x20; 3. Totals were compared/added as floats, so 11.605 + 1.741 may not equal &#x20; the float produced by round(11.605 + 1.741, 3) bit for bit. &#x20; Fix: parse inputs as Decimal from their text form, round with ROUND\_HALF\_UP, and keep every amount as an integer number of fils from that point on. Integer addition is exact, so the invariant holds by construction. &#x20; Python 3.11, standard library only, deterministic. """ from \_\_future\_\_ import annotations &#x20; from dataclasses import dataclass, field from decimal import ROUND\_HALF\_UP, Decimal, InvalidOperation, localcontext &#x20; TAX\_RATE = Decimal("0.15") FILS\_PER\_KWD = 1000 \_FILS = Decimal("0.001") &#x20; Number = Decimal | int | str | float &#x20; &#x20; def to\_decimal(value: Number) -> Decimal: &#x20; """Convert input to an exact Decimal. &#x20; &#x20; Floats go through repr() so 2.885 becomes Decimal('2.885') (what the user &#x20; typed), not Decimal(2.88499999999999978...). Prefer passing str/Decimal. &#x20; """ &#x20; if isinstance(value, bool): &#x20; raise TypeError("bool is not a money amount") &#x20; if isinstance(value, Decimal): &#x20; d = value &#x20; elif isinstance(value, int): &#x20; d = Decimal(value) &#x20; elif isinstance(value, float): &#x20; d = Decimal(repr(value)) &#x20; elif isinstance(value, str): &#x20; try: &#x20; d = Decimal(value.strip()) &#x20; except InvalidOperation as exc: &#x20; raise ValueError(f"invalid amount: {value!r}") from exc &#x20; else: &#x20; raise TypeError(f"unsupported amount type: {type(value).\_\_name\_\_}") &#x20; if not d.is\_finite(): &#x20; raise ValueError(f"amount must be finite: {value!r}") &#x20; return d &#x20; &#x20; def round\_half\_up\_fils(amount: Number) -> int: &#x20; """Round an amount in KWD to the nearest fils (half-up), return int fils. &#x20; &#x20; Half-up here means ties go away from zero (Decimal.ROUND\_HALF\_UP), so &#x20; 0.0125 -> 13 fils and -0.0125 -> -13 fils. &#x20; """ &#x20; with localcontext() as ctx: &#x20; ctx.prec = 50 # wide enough that multiplication never loses digits &#x20; q = to\_decimal(amount).quantize(\_FILS, rounding=ROUND\_HALF\_UP) &#x20; return int(q \* FILS\_PER\_KWD) &#x20; &#x20; def fils\_to\_kwd(fils: int) -> Decimal: &#x20; """Integer fils -> Decimal KWD with exactly 3 decimals (e.g. 13346 -> 13.346).""" &#x20; return (Decimal(fils) / FILS\_PER\_KWD).quantize(\_FILS) &#x20; &#x20; def kwd\_str(fils: int) -> str: &#x20; return f"{fils\_to\_kwd(fils):.3f}" &#x20; &#x20; @dataclass class Line: &#x20; name: str &#x20; qty: int &#x20; unit\_price: Number # KWD; str or Decimal recommended, float accepted &#x20; &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; if isinstance(self.qty, bool) or not isinstance(self.qty, int): &#x20; raise TypeError("qty must be an int") &#x20; if self.qty < 0: &#x20; raise ValueError("qty must be >= 0") &#x20; self.unit\_price = to\_decimal(self.unit\_price) &#x20; if self.unit\_price < 0: &#x20; raise ValueError("unit\_price must be >= 0") &#x20; &#x20; @dataclass class Invoice: &#x20; lines: list\[Line] = field(default\_factory=list) &#x20; discount: Number = Decimal("0") # total discount in KWD &#x20; &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; self.discount = to\_decimal(self.discount) &#x20; if self.discount < 0: &#x20; raise ValueError("discount must be >= 0") &#x20; &#x20; def line\_total\_fils(line: Line) -> int: &#x20; """Line total in fils, rounded half-up per line (client policy).""" &#x20; with localcontext() as ctx: &#x20; ctx.prec = 50 &#x20; return round\_half\_up\_fils(line.unit\_price \* line.qty) &#x20; &#x20; def line\_total(line: Line) -> Decimal: &#x20; """Line total as displayed to the customer (Decimal KWD, 3 places).""" &#x20; return fils\_to\_kwd(line\_total\_fils(line)) &#x20; &#x20; @dataclass(frozen=True) class InvoiceResult: &#x20; line\_totals\_fils: tuple\[int, ...] &#x20; lines\_sum\_fils: int &#x20; discount\_fils: int &#x20; subtotal\_fils: int # lines\_sum - discount; the tax basis &#x20; tax\_fils: int &#x20; total\_fils: int &#x20; &#x20; # Decimal views for display / ERP export &#x20; @property &#x20; def subtotal(self) -> Decimal: &#x20; return fils\_to\_kwd(self.subtotal\_fils) &#x20; &#x20; @property &#x20; def tax(self) -> Decimal: &#x20; return fils\_to\_kwd(self.tax\_fils) &#x20; &#x20; @property &#x20; def total(self) -> Decimal: &#x20; return fils\_to\_kwd(self.total\_fils) &#x20; &#x20; @property &#x20; def discount(self) -> Decimal: &#x20; return fils\_to\_kwd(self.discount\_fils) &#x20; &#x20; @property &#x20; def lines\_sum(self) -> Decimal: &#x20; return fils\_to\_kwd(self.lines\_sum\_fils) &#x20; &#x20; def \_\_getitem\_\_(self, key: str) -> Decimal: &#x20; """Backward compatibility: result\["total"], result\["tax"], ...""" &#x20; if key in ("subtotal", "tax", "total", "discount", "lines\_sum"): &#x20; return getattr(self, key) &#x20; raise KeyError(key) &#x20; &#x20; def as\_dict(self) -> dict\[str, str]: &#x20; """String amounts with exactly 3 decimals - safe to serialize to the ERP.""" &#x20; return { &#x20; "lines": \[kwd\_str(f) for f in self.line\_totals\_fils], &#x20; "lines\_sum": kwd\_str(self.lines\_sum\_fils), &#x20; "discount": kwd\_str(self.discount\_fils), &#x20; "subtotal": kwd\_str(self.subtotal\_fils), &#x20; "tax": kwd\_str(self.tax\_fils), &#x20; "total": kwd\_str(self.total\_fils), &#x20; } &#x20; &#x20; def compute\_invoice(inv: Invoice, tax\_rate: Number = TAX\_RATE) -> InvoiceResult: &#x20; rate = to\_decimal(tax\_rate) &#x20; if rate < 0: &#x20; raise ValueError("tax\_rate must be >= 0") &#x20; &#x20; # 1. Each line rounded half-up on its own -> these are the displayed values. &#x20; line\_fils = tuple(line\_total\_fils(l) for l in inv.lines) &#x20; &#x20; # 2. Everything below is integer fils arithmetic: exact, no drift. &#x20; lines\_sum = sum(line\_fils) &#x20; discount = round\_half\_up\_fils(inv.discount) &#x20; if discount > lines\_sum: &#x20; raise ValueError("discount exceeds invoice lines total") &#x20; subtotal = lines\_sum - discount &#x20; &#x20; # 3. Tax on the displayed basis, rounded half-up once. &#x20; with localcontext() as ctx: &#x20; ctx.prec = 50 &#x20; tax = round\_half\_up\_fils(fils\_to\_kwd(subtotal) \* rate) &#x20; &#x20; total = subtotal + tax &#x20; &#x20; result = InvoiceResult(line\_fils, lines\_sum, discount, subtotal, tax, total) &#x20; # Invariant the ERP checks - holds by construction, asserted for safety. &#x20; assert result.total\_fils == sum(line\_fils) - discount + tax &#x20; return result &#x20; &#x20; \# -------------------------------------------------------------------------- \# Self-test: python invoicing.py \# -------------------------------------------------------------------------- def \_self\_test() -> None: &#x20; import random &#x20; &#x20; # The CI case. &#x20; inv = Invoice(lines=\[Line("برجر", 3, "2.885"), Line("مشروب", 2, "1.475")]) &#x20; r = compute\_invoice(inv) &#x20; assert r.total == sum(line\_total(l) for l in inv.lines) + r.tax &#x20; assert r.as\_dict() == { &#x20; "lines": \["8.655", "2.950"], "lines\_sum": "11.605", "discount": "0.000", &#x20; "subtotal": "11.605", "tax": "1.741", "total": "13.346", &#x20; }, r.as\_dict() &#x20; &#x20; # Floats are accepted and give the same answer as strings. &#x20; inv\_f = Invoice(lines=\[Line("برجر", 3, 2.885), Line("مشروب", 2, 1.475)]) &#x20; assert compute\_invoice(inv\_f) == r &#x20; &#x20; # Half-up, not half-even (round() gave the wrong answers here). &#x20; assert line\_total\_fils(Line("x", 1, "0.0125")) == 13 &#x20; assert line\_total\_fils(Line("x", 1, "0.0145")) == 15 &#x20; assert line\_total\_fils(Line("x", 1, "1.0005")) == 1001 &#x20; assert line\_total\_fils(Line("x", 1, "2.6745")) == 2675 &#x20; assert line\_total\_fils(Line("x", 3, "0.3335")) == 1001 # 1.0005 -> 1.001 &#x20; &#x20; # Discount: total = lines - discount + tax on (lines - discount). &#x20; inv\_d = Invoice(lines=\[Line("x", 2, "1.2345")], discount="0.500") &#x20; rd = compute\_invoice(inv\_d) &#x20; assert rd.as\_dict()\["lines"] == \["2.469"] &#x20; assert rd.subtotal\_fils == 1969 &#x20; assert rd.tax\_fils == 295 # 1.969 \* 0.15 = 0.29535 -> 0.295 &#x20; assert rd.total\_fils == 2264 &#x20; &#x20; # Randomized invariant check (fixed seed => deterministic). &#x20; rng = random.Random(20261009) &#x20; for \_ in range(50\_000): &#x20; lines = \[ &#x20; Line("x", rng.randint(0, 20), f"{rng.randint(0, 99\_999) / 10\_000:.4f}") &#x20; for \_ in range(rng.randint(1, 6)) &#x20; ] &#x20; ls = sum(line\_total\_fils(l) for l in lines) &#x20; disc = f"{rng.randint(0, ls) / 1000:.3f}" &#x20; res = compute\_invoice(Invoice(lines, disc)) &#x20; assert res.total == ( &#x20; sum(line\_total(l) for l in lines) - res.discount + res.tax &#x20; ) &#x20; assert res.total\_fils == ls - res.discount\_fils + res.tax\_fils &#x20; &#x20; print("all invoicing checks passed") &#x20; &#x20; if \_\_name\_\_ == "\_\_main\_\_": &#x20; \_self\_test() &#x20;
بChatGPT85.0 · 706 كلمة
السبب الجذري ليس ترتيب تطبيق الخصم والضريبة، بل استخدام float مع الدالة round() الافتراضية في Python، التي تستخدم التقريب إلى الزوجي عند التعادل (round half to even)، وليس التقريب المطلوب half-up. كما أن الأعداد العشرية مثل 2.885 لا تُمثَّل بدقة باستخدام float، مما قد يغيّر نتيجة التقريب. هناك نقطة ثانية مهمة: يجب أن تُحسب الضريبة على مجموع الأسطر بعد تقريب كل سطر إلى أقرب فلس، وبعد خصم الخصم وفق سياسة واضحة، ثم يُقرَّب إجمالي الضريبة إلى أقرب فلس. وأخيرًا، يُحسب الإجمالي بجمع المبالغ النهائية المقربة، لا بإعادة حسابه من قيم غير مقربة. الحل الأنسب هو استخدام Decimal من المكتبة القياسية، مع ROUND\_HALF\_UP، وتمثيل الدينار بثلاث خانات عشرية في كل المبالغ النقدية النهائية. الملف المعدّل: invoicing.py كتابة from dataclasses import dataclass, field from decimal import Decimal, ROUND\_HALF\_UP, InvalidOperation from typing import Sequence \# Kuwait: 1 KWD = 1000 fils MONEY\_QUANTUM = Decimal("0.001") TAX\_RATE = Decimal("0.15") ZERO = Decimal("0.000") def to\_decimal(value: Decimal | int | float | str) -> Decimal: &#x20; """Convert a number to Decimal without importing binary float artifacts.""" &#x20; if isinstance(value, bool): &#x20; raise TypeError("Boolean values are not valid monetary amounts") &#x20; if isinstance(value, float): &#x20; value = str(value) &#x20; try: &#x20; result = Decimal(value) &#x20; except (InvalidOperation, ValueError, TypeError) as exc: &#x20; raise ValueError(f"Invalid monetary amount: {value!r}") from exc &#x20; if not result.is\_finite(): &#x20; raise ValueError("Monetary amounts must be finite") &#x20; return result def round\_kwd(amount: Decimal | int | float | str) -> Decimal: &#x20; """Round a KWD amount to the nearest fils, using half-up.""" &#x20; return to\_decimal(amount).quantize( &#x20; MONEY\_QUANTUM, &#x20; rounding=ROUND\_HALF\_UP, &#x20; ) @dataclass(frozen=True) class Line: &#x20; name: str &#x20; qty: int &#x20; unit\_price: Decimal | int | float | str &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; if isinstance(self.qty, bool) or not isinstance(self.qty, int): &#x20; raise TypeError("Quantity must be an integer") &#x20; if self.qty < 0: &#x20; raise ValueError("Quantity cannot be negative") &#x20; # Normalize floats through their decimal string representation. &#x20; object.\_\_setattr\_\_(self, "unit\_price", to\_decimal(self.unit\_price)) &#x20; if self.unit\_price < 0: &#x20; raise ValueError("Unit price cannot be negative") @dataclass class Invoice: &#x20; lines: list\[Line] = field(default\_factory=list) &#x20; discount: Decimal | int | float | str = ZERO &#x20; def \_\_post\_init\_\_(self) -> None: &#x20; self.discount = round\_kwd(self.discount) &#x20; if self.discount < ZERO: &#x20; raise ValueError("Discount cannot be negative") def line\_total(line: Line) -> Decimal: &#x20; """Calculate and round each invoice line independently.""" &#x20; return round\_kwd(Decimal(line.qty) \* line.unit\_price) def compute\_invoice(inv: Invoice) -> dict: &#x20; # These are the exact amounts displayed for individual invoice lines. &#x20; line\_totals = \[line\_total(line) for line in inv.lines] &#x20; gross\_subtotal = sum(line\_totals, ZERO) &#x20; # Preserve the existing behavior: discount is deducted before tax. &#x20; if inv.discount > gross\_subtotal: &#x20; raise ValueError("Discount cannot exceed the line subtotal") &#x20; subtotal = round\_kwd(gross\_subtotal - inv.discount) &#x20; tax = round\_kwd(subtotal \* TAX\_RATE) &#x20; # Add already-rounded monetary amounts; do not recalculate from floats. &#x20; total = subtotal + tax &#x20; # Enforce the accounting invariant explicitly. &#x20; if total != subtotal + tax: &#x20; raise ArithmeticError("Invoice total does not reconcile") &#x20; return { &#x20; "line\_totals": line\_totals, &#x20; "gross\_subtotal": gross\_subtotal, &#x20; "discount": inv.discount, &#x20; "subtotal": subtotal, &#x20; "tax": tax, &#x20; "total": total, &#x20; } اختبار الحالة المذكورة from decimal import Decimal inv = Invoice( &#x20; lines=\[ &#x20; Line("برجر", 3, 2.885), &#x20; Line("مشروب", 2, 1.475), &#x20; ], &#x20; discount=0, ) result = compute\_invoice(inv) assert result\["line\_totals"] == \[ &#x20; Decimal("8.655"), &#x20; Decimal("2.950"), ] assert result\["gross\_subtotal"] == Decimal("11.605") assert result\["subtotal"] == Decimal("11.605") assert result\["tax"] == Decimal("1.741") assert result\["total"] == Decimal("13.346") assert result\["total"] == sum( &#x20; result\["line\_totals"], Decimal("0.000") ) + result\["tax"] print(result) لماذا هذا الحل صحيح؟ تقريب نصف فلس إلى الأعلى: ROUND\_HALF\_UP يحقق سياسة العميل بدل round() الافتراضية. لا توجد أخطاء تمثيل ثنائي: تُحوّل قيم float عبر str()، وتُستخدم Decimal في العمليات المالية. تقريب كل سطر مستقل: يُقرَّب ناتج الكمية × سعر الوحدة إلى ثلاثة منازل عشرية قبل جمع الأسطر. اتساق الإجمالي: يُحسب الإجمالي من المجموع الخاضع للضريبة بعد الخصم، مضافًا إليه مبلغ الضريبة المقرب. تنبيه مهم بشأن الخصم: في وجود خصم، يصبح الإجمالي مساويًا لمجموع الأسطر ناقص الخصم، زائد الضريبة. إذا كان نظام المحاسبة يشترط حرفيًا أن يساوي الإجمالي مجموع الأسطر المعروضة زائد الضريبة، فيجب إظهار الخصم كسطر سالب ضمن الفاتورة بدل خصمه خارج الأسطر. كذلك، الأرقام 11.771 و11.772 في رسالة الاختبار لا تتوافق مع بيانات المثال المعروضة؛ فالحساب وفق سياسة التقريب المذكورة يعطي إجماليًا قدره 13.346 دينار كويتي.
هـDeepSeek62.7 · 737 كلمة
تحليل المشكلة السبب الجذري المشكلة ليست في round\_kwd نفسها، بل في الافتراض الخاطئ بأن جمع الأعداد المُقرَّبة يساوي تقريب المجموع. عندك سطران: السطر الحساب الخام التقريب half-up برجر 3 × 2.885 = 8.655 8.655 (لا تغيير) مشروب 2 × 1.475 = 2.950 2.950 (لا تغيير) المجموع 11.605 11.605 الضريبة على 11.605: 11.605 × 0.15 = 1.74075 round(1.74075, 3) = 1.741 (لأن round في Python يستخدم banker's rounding، لكن هنا الرقم واضح) الإجمالي الحالي: round(11.605 + 1.741) = round(13.346) = 13.346 لكن اختبارك يقول 11.771 مقابل 11.772 — إذن هناك خصم أو أرقام مختلفة عمّا حسبته يدوياً. المهم أن الفارق فلس واحد وهو ناتج حتمي عن: round() في Python تستخدم banker's rounding (round-half-to-even)، وليست half-up كما تظن. مثلاً round(2.5) تعطي 2 وليس 3. الضريبة تُحسب على subtotal بعد تقريبه، ثم تُقرَّب من جديد، ثم total يُقرَّب مرة ثالثة — ثلاث عمليات تقريب متتالية كل واحدة قد تُدخل انحرافاً بمقدار فلس. اختبارك يقارن total مع sum(line\_total) + tax، لكن total محسوب من round(subtotal + tax) وsubtotal نفسه مجموع أسطر مُقرَّبة — فلا يوجد ضمان رياضي أن round(a+b) == round(a) + round(b). الخلاصة: التقريب المتسلسل (line → subtotal → discount → tax → total) يخلق انحرافات متراكمة، وسياسة العميل تفرض أن total == sum(lines) + tax بالضبط. الحل القاعدة الذهبية: احسب الإجمالي من الأسطر المعروضة مباشرة، واعتبر الضريبة مشتقة منه، لا العكس. subtotal = مجموع الأسطر المُقرَّبة (هذا ما يراه العميل). discount يُقرَّب ويُطرح من subtotal بعد التقريب. taxable\_base = subtotal - discount. tax = half\_up(taxable\_base × 0.15). total = taxable\_base + tax (بدون تقريب إضافي — هو أصلاً مجموع أرقام بثلاث خانات). بهذا يتحقق الشرط دائماً: total == (sum(lines) - discount) + tax، والفرق بين الطرفين صفر رياضياً. الملف المعدَّل (مستقل، stdlib فقط، أقل من 300 سطر) python """ invoicing.py — نظام فوترة مطاعم بالدينار الكويتي (3 خانات عشرية). سياسة التقريب: half-up لكل سطر على حدة، وإجمالي الفاتورة يُشتق من الأسطر المعروضة + الضريبة، لضمان تطابق دائم مع برنامج المحاسبة. """ from dataclasses import dataclass, field from decimal import Decimal, ROUND\_HALF\_UP, getcontext \# دقة كافية لتفادي أي فقدان في العمليات الوسيطة getcontext().prec = 28 TAX\_RATE = Decimal("0.15") Q = Decimal("0.001") # 3 خانات عشرية = فلس واحد def half\_up(value) -> Decimal: &#x20; """تقريب half-up إلى 3 خانات عشرية (أقرب فلس، والنصف للأعلى).""" &#x20; d = Decimal(str(value)) &#x20; return d.quantize(Q, rounding=ROUND\_HALF\_UP) @dataclass class Line: &#x20; name: str &#x20; qty: int &#x20; unit\_price: float &#x20; @property &#x20; def total(self) -> Decimal: &#x20; # qty صحيح، unit\_price يُحوَّل عبر str لتفادي أخطاء float الثنائية &#x20; return half\_up(Decimal(self.qty) \* Decimal(str(self.unit\_price))) @dataclass class Invoice: &#x20; lines: list = field(default\_factory=list) &#x20; discount: float = 0.0 # خصم إجمالي بالدينار def line\_total(line: Line) -> Decimal: &#x20; return line.total def compute\_invoice(inv: Invoice) -> dict: &#x20; # 1) مجموع الأسطر بعد تقريب كل سطر على حدة (ما يراه العميل) &#x20; subtotal = sum((l.total for l in inv.lines), Decimal("0.000")) &#x20; # 2) الخصم يُقرَّب half-up ثم يُطرح من المجموع المعروض &#x20; discount = half\_up(inv.discount) &#x20; taxable\_base = half\_up(subtotal - discount) &#x20; # 3) الضريبة على الأساس الخاضع بعد التقريب &#x20; tax = half\_up(taxable\_base \* TAX\_RATE) &#x20; # 4) الإجمالي = الأساس + الضريبة (بدون تقريب إضافي) &#x20; # هذا يضمن رياضياً: total == (subtotal - discount) + tax &#x20; total = half\_up(taxable\_base + tax) &#x20; return { &#x20; "subtotal": float(taxable\_base), &#x20; "discount": float(discount), &#x20; "tax": float(tax), &#x20; "total": float(total), &#x20; # قيم Decimal للاستخدام الداخلي/الترحيل المحاسبي &#x20; "\_subtotal": taxable\_base, &#x20; "\_discount": discount, &#x20; "\_tax": tax, &#x20; "\_total": total, &#x20; } لماذا ينجح هذا مع الاختبار الاختبار: python assert result\["total"] == sum(line\_total(l) for l in inv.lines) + result\["tax"] في الكود الجديد: result\["total"] = half\_up(subtotal - discount + tax) sum(line\_total) + tax = subtotal + tax (بافتراض discount=0) وبما أن subtotal وtax كلاهما مضاعفات للفلس (Decimal بـ 3 خانات)، فإن مجموعهما مضاعف للفلس أيضاً، وعملية quantize لا تغيّره. إذن الطرفان متساويان دائماً، وليس فقط لهذه الحالة. نقاط مهمة في التصميم Decimal بدل float: float لا يمثّل 2.885 أو 1.475 بدقة، وround في Python تستخدم banker's rounding لا half-up. Decimal + ROUND\_HALF\_UP يحلّ الأمرين. تقريب واحد لكل سطر، وتقريب واحد للخصم، وتقريب واحد للضريبة — لا تقريب متسلسل للإجمالي من أرقام خام. الإجمالي مشتق من المكوّنات المعروضة، فيستحيل أن يختلف عنها بفلس. إرجاع float للعرض مع الاحتفاظ بـ Decimal في المفاتيح \_\* لأي ترحيل محاسبي لاحق. حتمي بالكامل: لا اعتماد على ترتيب عمليات عائم، ولا على إعدادات محلية. إذا كان برنامج المحاسبة يستقبل الإجمالي كـ float، فالحل أعلاه كافٍ. أما إذا كان يستقبل Decimal/نص، فاستخدم المفاتيح \_\* مباشرة ومرّرها كنص عبر str(value).
جGemini72.0 · 556 كلمة
سبب المشكلة الجذري يتلخص في نقطتين في الكود الحالي:التعامل مع الأعداد العشرية (float): دالة round() في بايثون تستخدم تقريب المصرفيين (Round to Even) وليس "النصف إلى الأعلى" (Half-Up)، كما أن حسابات float تُدخل أخطاء تمثيل ثنائي (Binary Floating-Point Inaccuracy) تجعل القيم مثل 8.655 تُخزن في الذاكرة كـ 8.654999999999999 فيتم تقريبها للأسفل بطريق الخطأ.تراكم تقريبات الأسطر مقابل المجموع: في اختبارك الحالي:السطر الأول: $3 \\times 2.885 = 8.655 \\xrightarrow{\\text{Half-Up}} 8.655$السطر الثاني: $2 \\times 1.475 = 2.950 \\xrightarrow{\\text{Half-Up}} 2.950$مجموع الأسطر = $11.605$ د.ك.الضريبة: $11.605 \\times 0.15 = 1.74075 \\xrightarrow{\\text{Half-Up}} 1.741$ د.ك.الإجمالي الصحيح رياضياً = $11.605 + 1.741 = 13.346$ د.ك.لكن دالة round() في بايثون مع float قرّبت $8.655$ إلى $8.654$ فظهر الناتج $11.771$ بدلاً من $11.772$ (حيث أُضيفت قيمة قديمة أو غير دقيقة).الحل النهائي (ملف invoicing.py معدل بالكامل)استخدام وحدة decimal من المكتبة القياسية لبايثون مع ضبط وضع التقريب على ROUND\_HALF\_UP يضمن الدقة المسبوقة بثلاث خانات عشرية مطابقة لمتطلبات المحاسبة الكويتية.Pythonfrom dataclasses import dataclass, field from decimal import Decimal, ROUND\_HALF\_UP \# معدل الضريبة كـ Decimal TAX\_RATE = Decimal("0.15") \# القيمة المرجعية للتقريب إلى 3 خانات عشرية (0.001) KWD\_PRECISION = Decimal("0.001") def round\_kwd(amount: Decimal) -> Decimal: &#x20; """ &#x20; تقريب القيمة إلى أقرب فلس (3 خانات عشرية) باستخدام نظام Half-Up. &#x20; """ &#x20; return amount.quantize(KWD\_PRECISION, rounding=ROUND\_HALF\_UP) @dataclass class Line: &#x20; name: str &#x20; qty: int &#x20; unit\_price: Decimal &#x20; def \_\_post\_init\_\_(self): &#x20; # تحويل السعر تلقائياً إلى Decimal إذا أُمرِر كـ float أو int أو str &#x20; if not isinstance(self.unit\_price, Decimal): &#x20; self.unit\_price = Decimal(str(self.unit\_price)) @dataclass class Invoice: &#x20; lines: list\[Line] = field(default\_factory=list) &#x20; discount: Decimal = Decimal("0.0") # إجمالي الخصم بالدينار الكويتي &#x20; def \_\_post\_init\_\_(self): &#x20; if not isinstance(self.discount, Decimal): &#x20; self.discount = Decimal(str(self.discount)) def line\_total(line: Line) -> Decimal: &#x20; """ &#x20; حساب إجمالي السطر مع تقريب كل سطر على حدة وفق سياسة العميل. &#x20; """ &#x20; raw\_total = Decimal(line.qty) \* line.unit\_price &#x20; return round\_kwd(raw\_total) def compute\_invoice(inv: Invoice) -> dict\[str, Decimal]: &#x20; """ &#x20; حساب إجمالي الفاتورة بحيث يكون الإجمالي دائماً مساوياً لمجموع الأسطر &#x20; بعد خصم Discount مضافاً إليها الضريبة المحسوبة على ذلك الأساس. &#x20; """ &#x20; # 1. مجموع الأسطر المقربة &#x20; lines\_sum = sum((line\_total(l) for l in inv.lines), Decimal("0.000")) &#x20; # 2. المجموع الفرعي بعد الخصم &#x20; subtotal = round\_kwd(lines\_sum - inv.discount) &#x20; # 3. حساب الضريبة على المجموع الفرعي الصافي وتقريبها لـ Half-Up &#x20; tax = round\_kwd(subtotal \* TAX\_RATE) &#x20; # 4. الإجمالي النهائي هو الجمع المباشر للمجموع الفرعي المقرب + الضريبة المقربة &#x20; total = subtotal + tax &#x20; return { &#x20; "subtotal": subtotal, &#x20; "tax": tax, &#x20; "total": total, &#x20; } \# ---------------------------------------------------------------------- \# التحقق والاختبار التشغيلي \# ---------------------------------------------------------------------- if \_\_name\_\_ == "\_\_main\_\_": &#x20; inv = Invoice( &#x20; lines=\[ &#x20; Line("برجر", 3, 2.885), &#x20; Line("مشروب", 2, 1.475), &#x20; ], &#x20; discount=0.0, &#x20; ) &#x20; result = compute\_invoice(inv) &#x20; # طباعة التفاصيل &#x20; print("المجموع الفرعي (Subtotal):", result\["subtotal"]) &#x20; print("الضريبة (Tax): ", result\["tax"]) &#x20; print("الإجمالي (Total): ", result\["total"]) &#x20; # التحقق المباشر المطلوبة في الـ CI &#x20; expected\_total = sum(line\_total(l) for l in inv.lines) + result\["tax"] &#x20; assert result\["total"] == expected\_total, f"Discrepancy: {result\['total']} != {expected\_total}" &#x20; print("\\n✅ تم مجتاز اختبار المطابقة مع نظام ERP بنجاح!") أبرز ما تم تعديله:استبدال float بـ Decimal: يضمن التخلص التام من أخطاء النقطة العائمة الثنائية.استخدام ROUND\_HALF\_UP: يُنفذ سياسة العميل بدقة (مثلاً $0.0005$ تُقرَّب إلى $0.001$).تطابق المعادلة مع الـ CI: تم ضمان شرط المطابقة حيث أصبح total = subtotal + tax كـ Decimal بدقة 100% دون خروج أي فلس زائد أو ناقص.