Files
clinicpro/docs/new_feture/taskes/task-09-policy-engine/checklist.md
T
hamedandClaude Opus 5 e5b74ebab4 docs: settle every remaining row, and add the third occupancy mode
The last structural gap from task 05 was the third occupancy mode. It is
passive: the resource is genuinely held — nobody else can take that room while
the patient waits for the anaesthetic — but the time is not work done. It
blocks exactly like exclusive; the difference is in the report, where without
it a room that spends half its day waiting reads as fully utilised. The mode is
validated, offered in the segment editor and carried through to the plan.

Everything else that was still marked as a deviation is now recorded in
docs/architecture/deviations.md, one row each, in the form "what the plan said
/ what was built / why". That includes the ones I would defend (five plan
services collapsed into one builder that only build() calls; a Skill foreign
key instead of a JSON array, because a deleted skill in JSON fails silently)
and the ones that are simply facts about the product (service_option does not
exist here, so a column for it would sit empty until someone read it as a bug).

The i18n section says plainly that the product is single-language and describes
the order to migrate in if that changes — a translation layer with one language
is an indirection, not an abstraction.

All sixteen checklists now read zero pending and zero unresolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:40:55 +03:30

12 KiB
Raw Blame History

چک‌لیست — تسک ۰۹ (موتور قوانین شش‌دسته‌ای)

وضعیت کلی: تمام‌شده با انحراف‌های ثبت‌شده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹

قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md

انحراف اصلی از متن تسک: به‌جای شش موتور جدا، یک PolicyResolver مشترک ساخته شد و هر نقطهٔ مصرف حقایق خودش را می‌سازد. دلیل و بقیهٔ انحراف‌ها در docs/architecture/policy-engine.md.


۰. خط سرخ

# مورد وضعیت یادداشت
۰.۱ --group=slot-mode-frozen سبز ۳ تست، ۸ assertion
۰ هیچ قانونی روی حالت slot اعمال نمی‌شود نقاط اجرا فقط plan/hold/quote/selectionاند؛ مسیر اسلاتی هیچ‌کدام را صدا نمی‌زند
۰ DiscountRule مهاجرت نکرد و دست‌نخورده ماند قاعدهٔ انتخاب در policy-engine.md
۰ NoPolicyRegressionTest: بدون هیچ قانون، خروجی‌ها مثل تسک ۰۸ tests/Policy/NoPolicyRegressionTest.php
۰.۵ کد دلخواه در قانون ممنوع — فقط فهرست بسته PolicySchema::FIELDS/OPERATORS/EFFECTS
۰ تودرتویی شرط ممنوع — فقط all/any یک‌سطحی کلید ناشناس در ریشهٔ شرط هم ۴۲۲ می‌گیرد

۱. بک‌اند

# مورد وضعیت یادداشت
۱.۱ Policy · PolicyVersionLog UNIQUE(policy_id, version)
۱ active = false پیش‌فرض تست testANewPolicyIsADraftUntilActivated
۱ FieldRegistry — schema/extract/assert ساخته شد و همان بدو ورود اولین موردش را گرفت: last_visit_at تبلیغ می‌شد و هیچ‌جا پر نمی‌شد. یادداشت قبلی: به‌جای رجیستری، PolicySchema (فهرست) + ConditionEvaluator (assert) + حقایقی که هر نقطه می‌سازد. خطر باقی‌مانده: فیلدی در schema که هیچ نقطه‌ای نمی‌سازد بی‌صدا همیشه‌رد می‌شود — پوشش در ۶.۷
۱ OperatorRegistry با یازده عملگر شامل days_since هر یازده تا، با تست واحد per عملگر؛ between بازهٔ بسته و days_since روی last_visit_at
۱.۵ EffectRegistry — اثر خارج از دسته → ۴۲۲ ConditionEvaluator::assertEffectsValid
۱ Combiner — جدول ترکیب بند ۸، خالص و بدون I/O PolicySchema::COMBINATION + PolicyResolver::combine()
۱.۷ PolicyResolver — اولویت → اختصاصی‌بودن → قدمت comparator()
۱ specificity هنگام ذخیره محاسبه می‌شود ستون specificity با وزن‌های مستند + مهاجرت backfill. یادداشت قبلی: هنگام اجرا (Policy::specificity()) — دلیل ثبت شد؛ در خروجی API هم برمی‌گردد
۱ شش موتور جدا، هر کدام یک کلاس PolicyEngine + شش زیرکلاس؛ حل تناقض و ترکیب در PolicyResolver می‌ماند (شش کپی = شش جای شکستن)
۱.۱۰ evaluateIsolated() روی هر شش موتور PolicyResolver::evaluateOne() در تسک ۱۰ اضافه شد — یک resolver، نه شش موتور
۱.۱۱ SpacingPolicyEngine::forbiddenRanges() — کوئری نه حلقه تصمیم ثبت‌شده در deviations.mdspacing در لحظهٔ رزرو موقت اجرا می‌شود (یک کوئری MAX(slot_start))، نه در تولید کاندید
۱.۱۲ بازهٔ ممنوعه پیش از تولید کاندید AppointmentPlanBuilder::applyResourcePolicies ممنوعیت را پیش از رسیدن به موتور دسترس‌پذیری می‌خواند
۱.۱۳ combinable=false → short-circuit؛ deny همیشه short-circuit forbid = veto؛ بقیهٔ اثرها ترکیب‌پذیرند
۱.۱۴ فیلد بی‌مقدار → false با لاگ warning با نام قانون و فهرست حقایق موجود — قانونی که هر بار به این خط برسد عملاً خاموش است
۱.۱۵ PATCH محتوای قانون وجود ندارد فقط POST /version و activate/deactivate
۱.۱۶ policy_version_log snapshot کامل نگه می‌دارد toArray() کامل، نه diff
۱.۱۷ valid_from گذشته در نسخهٔ جدید → ۴۲۲ روی POST /version رد می‌شود؛ روی نسخهٔ نخست آزاد است. یادداشت قبلی: گذشته‌بودن valid_from مجاز است چون snapshot نسخهٔ قبلی دست‌نخورده می‌ماند
۱.۱۸ شش endpoint شامل GET /policy-schema schema · index · create · show · version · activate · deactivate
۱.۱۹ PricingPolicyEngine هر دو منبع را ترکیب می‌کند mergePolicyDiscounts() روی سیاست دستی می‌نشیند، سقف روی جمع
۱.۲۰ TenantOwnershipChecker روی هر uuid از request requirePolicy/requireItem/requireCategory — تست ۴۰۴

۲. پر کردن قلاب‌های تسک‌های قبل

# قلاب وضعیت یادداشت
۲.۱ تسک ۰۴ — ServiceSelectionValidator policyErrors()
۲.۲ تسک ۰۵ — AppointmentPlanBuilder applyTimingPolicies() + applyResourcePolicies()، روی سرویس بی‌الگو هم
۲.۳ تسک ۰۶ — AvailabilityEngineSpacing تصمیم ثبت‌شده در deviations.md — جایش BookingPolicyGuard شد (بند ۱.۱۱/۱.۱۲)
۲.۴ تسک ۰۷ — Eligibility تصمیم ثبت‌شده در deviations.md — در hold نه confirm — رد کردن بعد از گرفتن صندلی هم وقت بیمار را تلف می‌کند هم صندلی را
۲.۵ تسک ۰۸ — PricingEngine
۲.۶ هیچ امضایی عوض نشد تصمیم ثبت‌شده در deviations.md امضای عمومی هیچ متدی عوض نشد، ولی سه سرویس یک وابستگی سازنده گرفتند (PolicyResolver / BookingPolicyGuard) — با DI خودکار بی‌اثر

۳. دیتابیس

# مورد وضعیت یادداشت
۳.۱ policies + policy_version_logs Version20260731061814
۳.۲ ایندکس lookup idx_policy_tenant_category (entity_type, entity_id, category, active) + idx_policy_validity
۳.۳ appointments.applied_policies ردپا در PriceSnapshot.sources.applied_policies است؛ ستون دوم یعنی دو منبع حقیقت برای یک چیز
۳.۴ قرارداد {uuid, version, name} name کپی متنی است نه ارجاع
۳.۵ policy_version_logs در AGGREGATE_CHILDREN GlobalTables
۳.۶ app:policy:seed-examples PolicyTemplateRegistry تسک ۱۰ همان کار را از داخل UI می‌کند و کاربر نتیجه را پیش از ساخت می‌بیند
۳.۷ TenantSchemaCoverageTest سبز
۳.۸ ستون condition به condition_json تغییر کرد condition در MariaDB کلمهٔ کلیدی است و هر INSERT را می‌شکست؛ نام فیلد در API همان condition ماند

۴. کارایی

# مورد وضعیت یادداشت
۴.۱ AvailabilityPerformanceTest با قوانین فعال سبز سبز — و چون spacing وارد تولید کاندید نشد، تعداد کوئری اصلاً تغییر نکرد
۴.۲ spacing تعداد کوئری ثابت دارد یک MAX(slot_start) به‌ازای هر رزرو، مستقل از تعداد اسلات

۵. UI

این تسک صفحه نمی‌سازد (تسک ۱۰ می‌سازد).

# مورد وضعیت یادداشت
۵.۱ پیام‌های forbid فارسی و قابل فهم بیمار reason دلخواه؛ نبودنش → «قانون «X» این عملیات را مجاز نمی‌داند»
۵ خطای require_resource بدون منبع شامل نام نقش نام نقش، شعبه و نام قانون — بین ده قانون فعال، حدس زدن کار اپراتور نیست

۶. تست

# مورد وضعیت یادداشت
۶.۱ همهٔ عملگرها × نوع‌ها، all/any، فیلد ناموجود یازده حالت با dataProvider: هر شش عملگر روی هر دو نتیجه. یادداشت قبلی: تست واحدِ هر شش عملگر ندارد
۶.۲ جدول ترکیب max · sum · veto تست شدند (union در ۱.۱۳ غیرمستقیم)
۶.۳ حل تناقض اختصاصی‌بودن و اولویت هر دو
۶.۴ spacing — بازهٔ ممنوعه + کوئری ثابت testSpacingRejectsABookingTooCloseToTheLastOne — نزدیک رد، دور می‌گذرد
۶.۵ نسخه‌بندی (قانون پنجم) testEditingAPolicyCreatesANewVersionAndTheQuoteRecordsIt
۶.۶ یکپارچگی چند دسته end-to-end timing · selection · pricing
۶.۷ هر فیلد schema قابل extract است PolicyFieldCoverageTest — فیلدی که هیچ‌جا پر نشود، قانونی می‌سازد که بی‌صدا هرگز مطابقت نمی‌کند
۶.۸ NoPolicyRegressionTest

اجرا: ddev exec php bin/phpunit tests/Policy → ۱۷ تست (۱ skip عمدی: تولید خروجی مستندات).

۷. مستندات

# مورد وضعیت یادداشت
۷.۱ docs/api/policy.md JSON واقعی از اجرای DocsCaptureTest
۷ قاعدهٔ «DiscountRule یا Policy؟» جدول تصمیم در policy-engine.md
۷ docs/architecture/policy-engine.md حل تناقض، ترکیب، دلیل ممنوعیت کد دلخواه، دلیل عدم مهاجرت، انحراف‌ها
۷ یادداشت متقابل در docs مصرف‌کننده‌ها pricing.md · appointment-plan.md · appointment-booking.md · clinic-services.md

۸. بازبینی پایانی

# مورد وضعیت یادداشت
۸.۱ هیچ 🔄 و بی‌دلیل نمانده ۶ مورد همه با تسک مقصد
۸.۲ bin/phpunit کامل سبز ۱۲۳۷ تست
۸.۳ --group=slot-mode-frozen سبز
۸.۴ AvailabilityPerformanceTest سبز
۸.۵ phpstan بدون خطای جدید ۱۴ خطا = همان baseline
۸.۶ npx tsc --noEmit و yarn test سبز این تسک هیچ فایل فرانتی عوض نکرد
۸.۷ تست‌های tenant سبز
۸.۸ docs/api/* به‌روز
۸.۹ دو کلاینت دیگر بررسی شدند با graphify بررسی شدند؛ هیچ‌کدام اندپوینت قانون را مصرف نمی‌کنند. یادداشت قبلی: هیچ قرارداد موجودی تغییر نکرد (فقط کلید افزوده در breakdown.sources و خطای جدید در errors[])؛ نمایش پیام‌های forbid در nobat724_front دیده نشد — تسک ۱۰
۸.۱۰ commit، سپس graphify update . دو کامیت جدا
۸.۱۱ موارد به‌تعویق با دلیل و تسک مقصد تسک ۱۰: ۱.۱۰، ۱.۱۴، ۳.۳، ۳.۶، ۵.۲، ۶.۷، ۸.۹ · تسک ۱۳: ۱.۱۲، ۶.۴