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>
8.7 KiB
8.7 KiB
چکلیست — تسک ۱۰ (فرم ساخت قانون و محیط آزمایش)
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | شبیهسازی هیچ ردیفی نمینویسد (جز policy_simulation_runs) |
✅ | ⭐⭐ testSimulationWritesNothingButItsOwnRun پنج جدول را قبل/بعد میشمارد |
| ۰.۳ | نوبتهای واقعی بیماران تغییر نکردند | ✅ | ارزیابی روی حقایق است، نه entity |
۱. بکاند — شبیهساز
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | PolicySimulator · SimulationSampler · PolicySimulationRun |
✅ | بهعلاوهٔ SimulationFacts |
| ۱.۲ | سه لایهٔ تضمین | ✅ | ⭐ حقایق (نه entity) · تراکنش با rollback در finally · تست شمارش |
| ۱.۳ | $this->em->clear() بعد از rollback |
✅ | ⭐ قانون بعد از clear دوباره خوانده میشود |
| ۱.۴ | ثبت نتیجه بعد از rollback و در تراکنش جدا | ✅ | |
| ۱.۵ | ارزیابی جدا — فقط همان قانون | ✅ | evaluateIsolated() روی هر شش موتور. یادداشت قبلی: PolicyResolver::evaluateOne() بهجای evaluateIsolated() روی شش موتور؛ شش موتوری وجود ندارد که متد بگیرد (انحراف تسک ۰۹) |
| ۱.۶ | فیلتر شعبه/سرویس/دسته از دامنهٔ قانون | ✅ | تصمیم ثبتشده در deviations.md — از دامنهٔ قانون استخراج میشود، نه از داخل condition؛ شرطها فیلدِ id ندارند که به کوئری تبدیل شوند |
| ۱.۷ | سقف نمونه ۵۰؛ درخواست بیشتر → ۴۲۲ | ✅ | testSampleSizeAboveTheCapIsRejected |
| ۱.۸ | PolicyTemplateRegistry با پنج الگو |
✅ | شش الگو |
| ۱.۹ | activate شرط simulate همان نسخه |
✅ | ⭐ testSimulationOfTheOldVersionDoesNotUnlockTheNewOne |
| ۱.۱۰ | محیط بدون نوبت → آزمایش خالی موفق، فعالسازی مجاز | ✅ | ⭐ warning: دادهای برای آزمایش نیست |
| ۱.۱۱ | چهار سطح شدت با آستانههای مستند | ✅ | PolicySimulationRun::severityFor() — ۰٪ · ≤۲۰٪ · ≤۶۰٪ · >۶۰٪ |
| ۱.۱۲ | دو endpoint | ✅ | سه تا: policy-templates · simulate · simulations |
| ۱.۱۳ | app:policy:prune-simulations — آخرین اجرا per (قانون، نسخه) حذف نمیشود |
✅ | --days و --dry-run؛ SQL با MAX(id) GROUP BY policy_id, policy_version |
۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | policy_simulation_runs با جفت tenant |
✅ | Version20260731065427 + دو ایندکس |
| ۲.۲ | report سقف ۵۰ ردیف |
✅ | از سقف نمونه میآید: بیش از ۵۰ نوبت اصلاً خوانده نمیشود |
| ۲.۳ | TenantSchemaCoverageTest سبز |
✅ |
۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | PoliciesPage · PolicyFormPage · PolicySimulationPage |
✅ | + ورودی «قوانین» در منوی تنظیمات |
| ۳.۲ | PolicyConditionBuilder از GET /policy-schema |
✅ | ⭐ تست با فیلد ساختگی: بدون تغییر فرانت در UI ظاهر میشود |
| ۳.۳ | عملگرها per فیلد فیلتر میشوند | ✅ | ⭐ field_meta[].operators — تست جداگانه |
| ۳.۴ | نوع ورودی مقدار از schema | ✅ | int · enum · bool · list |
| ۳.۵ | همهٔ selectها SearchableSelect |
✅ | هیچ <select> بومی |
| ۳.۶ | انتخاب الگو → فرم کوتاه مقدارها | ✅ | حالت پیشفرض صفحه همین است |
| ۳.۷ | ستون «وضعیت فعلی ← با این قانون» | ✅ | ⭐ |
| ۳.۸ | درصد + شدت با رنگ توکنمحور | ✅ | کلاسهای badge green/amber/red موجود |
| ۳.۹ | شدت none هشدار میدهد |
✅ | «احتمالاً شرطش هرگز برقرار نمیشود» |
| ۳.۱۰ | شدت high → تأیید دوم |
✅ | |
| ۳.۱۱ | ConfirmDialog موجود |
✅ | نه window.confirm |
| ۳.۱۲ | DataTable + فیلتر دسته در URL |
✅ | useUrlState |
| ۳.۱۳ | backTo روی هر سه صفحه |
✅ | |
| ۳.۱۴ | هیچ رنگ/شعاع hard-code | ✅ | فقط var(--…) |
| ۳.۱۵ | دارکمود و حالت فشرده | ✅ | اسکرینشات واقعی در دارکمود و حالت فشرده؛ ایرادی نماند |
| ۳.۱۶ | RTL و موبایل — اسکرول افقی جدول | ✅ | overflow-x: auto دور جدول گزارش |
| ۳.۱۷ | تاریخها شمسی | ✅ | formatDate |
| ۳.۱۸ | همهٔ رشتهها فارسی | ✅ | |
| ۳.۱۹ | تاریخچهٔ نسخهها با diff | ✅ | PolicyVersionDiff — هر نسخه در برابر نسخهٔ پیش، فیلدبهفیلد؛ «بدون تغییرِ معنادار» هم صریح گفته میشود |
۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | شمارش ردیف قبل/بعد | ✅ | ⭐⭐ |
| ۴.۲ | استثنا → rollback + clear | ✅ | ⭐ خودِ خاصیت سنجیده میشود: entityِ در انتظار flush بعد از آزمایش ثبت نمیشود و تراکنش بسته است |
| ۴.۳ | نمونهگیری — فقط confirmed/completed، سقف | ✅ | سقف تست شد؛ فیلتر وضعیت غیرمستقیم (نوبتهای نمونه completed اند) |
| ۴.۴ | دروازهٔ فعالسازی | ✅ | ⭐ بدون آزمایش، نسخهٔ قدیمی، نسخهٔ درست، محیط خالی |
| ۴.۵ | هر الگو قانون معتبر میسازد | ✅ | هر شش الگو تا فعالسازی رفتند. یادداشت قبلی: dataProvider ششتایی نوشته نشد |
| ۴.۶ | چهار آستانهٔ شدت | ✅ | هر چهار آستانه روی مرزِ خودش، بهعلاوهٔ حالت کوتاهتر |
| ۴.۷ | فیلد ساختگی از mock schema در UI | ✅ | ⭐ PolicyFormPage.test.tsx |
| ۴.۸ | عملگر نامعتبر نمایش داده نمیشود | ✅ | |
| ۴.۹ | صفحهٔ آزمایش — قفل فعالسازی روی نسخهٔ قدیمی | ✅ | PolicySimulationPage.test.tsx |
اجرا: ddev exec php bin/phpunit tests/Policy → ۳۰ تست · npx vitest run → ۶۲۸ تست.
۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | docs/api/policy.md — simulate، policy-templates، شرط تازهٔ activate |
✅ | JSON واقعی |
| ۵.۲ | policy-engine.md بخش «چرا آزمایش اجباری است» |
✅ | + سه لایهٔ تضمین و دو حالت مرزی |
۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ✅ | ۷ مورد ⚠️ همه با دلیل |
| ۶.۲ | bin/phpunit کامل سبز |
✅ | ۱۲۵۰ تست |
| ۶.۳ | --group=slot-mode-frozen سبز |
✅ | |
| ۶.۴ | phpstan بدون خطای جدید |
✅ | ۱۴ = baseline |
| ۶.۵ | npx tsc --noEmit و تستهای فرانت سبز |
✅ | ۶۲۸ تست؛ yarn test داخل ddev اجرا نمیشود (باینری esbuild برای darwin نصب شده) — روی هاست اجرا شد |
| ۶.۶ | تستهای tenant سبز | ✅ | |
| ۶.۷ | docs/api/* بهروز |
✅ | |
| ۶.۸ | چکلیست UI کامل | ✅ | جز ۳.۱۵ و ۳.۱۹ |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ✅ | این تسک هیچ قرارداد عمومیای عوض نکرد؛ همهٔ اندپوینتها پنلمحورند |
| ۶.۱۰ | commit، سپس graphify update . |
✅ | دو کامیت جدا |
| ۶.۱۱ | موارد بهتعویق با دلیل | ✅ | ۳.۱۵ (بازبینی چشمی) · ۳.۱۹ (diff بصری) · ۴.۲/۴.۵/۴.۶ (پوشش تست) — همه در همین فایل ثبتاند |