# نکات پیاده‌سازی — تسک ۱۰ ## ۱. rollback اجباری، سه لایه ```php public function simulate(Policy $policy, int $size): SimulationReport { $this->em->beginTransaction(); try { return $this->runInternal($policy, $size); } finally { $this->em->rollback(); // ← حتی اگر استثنا پرت شود $this->em->clear(); // ← identity map پاک شود } } ``` `finally` نه `catch`: اگر شبیه‌سازی استثنا داد، هنوز باید rollback شود. `clear()` بدون آن، entity های تغییرکردهٔ درون تراکنش در حافظه می‌مانند و اولین `flush` در ادامهٔ همان request آن‌ها را ثبت می‌کند — یک باگ که پیدا کردنش روزها می‌برد. ثبت `PolicySimulationRun` **بعد** از این بلوک و در تراکنش خودش. ## ۲. تست «هیچ چیزی ننوشت» — با شمارش، نه با اعتماد ```php $before = $this->countRows(['appointments','price_snapshots','resource_occupancy', 'resource_occupancy_slot','appointment_segments']); $this->simulator->simulate($policy, 50); $after = $this->countRows([...]); self::assertSame($before, $after, 'شبیه‌سازی نباید هیچ ردیفی بنویسد'); ``` این تست ارزشمندترین تست این تسک است. هر بار که کسی `PolicySimulator` را تغییر دهد، همین تست جلوی فاجعه را می‌گیرد. ## ۳. `evaluateIsolated` روی همهٔ شش موتور اضافه کردن این متد به شش موتور تسک ۰۹، تغییر اینترفیس است. پس **در تسک ۰۹ اضافه شود**، نه اینجا — وگرنه شش کلاس دوباره ویرایش می‌شوند. اگر تسک ۰۹ تمام شده و این متد نیست، اضافه‌اش کن ولی به‌عنوان یک متد در همان اینترفیس موجود، نه یک اینترفیس جدید. ## ۴. فرم از schema — بدون استثنا ```tsx // ❌ اولین وسوسه const FIELDS = ['patient.age', 'patient.tags', 'service.category_path']; // ✅ const { data: schema } = useQuery({ queryKey: ['policy-schema'], staleTime: 300_000 }); ``` اگر فیلدها را در فرانت hard-code کنی، هر فیلد جدید در `FieldRegistry` نیاز به تغییر فرانت دارد و بعد از دو ماه دو فهرست ناهمگام داریم. `staleTime` بلند اشکالی ندارد — schema تقریباً هرگز عوض نمی‌شود. ## ۵. شدت `none` هم هشدار است قانونی که روی هیچ نوبتی اثر نداشت، دو حالت دارد: - شرطش هرگز true نمی‌شود (اشتباه نوشته شده) - نمونهٔ ۵۰ نوبتی آن حالت را نداشت (شاید درست است) پیام باید هر دو را بگوید: > «این قانون روی هیچ‌کدام از ۵۰ نوبت نمونه اثر نداشت. یا شرط آن هرگز برقرار نمی‌شود، > یا این حالت در نوبت‌های اخیر پیش نیامده. فعال‌سازی مجاز است.» فعال‌سازی را نبند — کلینیک جدید هیچ نوبتی ندارد و باید بتواند قانون بسازد. ## ۶. الگوها باید واقعاً کار کنند هر الگو در `PolicyTemplateRegistry` باید یک تست داشته باشد که آن را می‌سازد، شبیه‌سازی می‌کند و فعال می‌کند. الگویی که `conditions` نامعتبر تولید کند، بدترین حالت است: کاربر فرم آماده را پر می‌کند و `422` می‌گیرد. ```php // tests/Policy/PolicyTemplateTest.php /** @dataProvider templates */ public function testTemplateProducesValidPolicy(string $key): void { $policy = $this->registry->build($key, $this->sampleInputs($key)); $this->validator->assertValid($policy); // همان اعتبارسنجی POST /policy } ``` ## ۷. edge case ها | حالت | رفتار درست | |---|---| | محیط بدون هیچ نوبت | گزارش خالی، `severity=none`، `activate` مجاز | | قانون `deny` که همه را رد می‌کند | `severity=high`، فعال‌سازی با تأیید دوباره | | `simulate` نسخهٔ ۱، بعد نسخهٔ ۲ ساخته شد | `activate` نسخهٔ ۲ → `422` | | `simulate` دو بار پشت‌سرهم | آخری معیار است؛ قبلی می‌ماند | | قانون فعال که دوباره `simulate` می‌شود | مجاز — کاربر می‌خواهد اثرش را ببیند | | نوبت نمونه‌ای که سرویسش حذف شده | از نمونه حذف شود، در `sample_size` نیاید | | قانون `pricing` روی نوبتی بدون snapshot | آن ردیف رد شود با علت `no_snapshot` | | `sample_size` بزرگ‌تر از ۵۰ | سقف ۵۰ — درخواست بیشتر `422` | ## ۸. تست ``` tests/Policy/PolicySimulatorTest.php ← ⭐ - هیچ ردیفی نوشته نمی‌شود (شمارش قبل/بعد) - رفتار درست وقتی evaluateIsolated استثنا می‌دهد (rollback + clear) - گزارش فقط نوبت‌های تحت تأثیر را دارد tests/Policy/SimulationSamplerTest.php - فیلتر شعبه و سرویس از شرط قانون استخراج می‌شود - فقط confirmed/completed - سقف ۵۰ tests/Policy/PolicyActivationGuardTest.php ← ⭐ - activate بدون simulate → 422 - activate با simulate نسخهٔ قبلی → 422 - activate با simulate نسخهٔ جاری → 200 - محیط بدون نوبت: simulate خالی → activate مجاز tests/Policy/PolicyTemplateTest.php - هر الگو قانون معتبر تولید می‌کند (dataProvider روی همهٔ الگوها) tests/Policy/SeverityTest.php - ۰٪ → none · ۱۰٪ → low · ۴۰٪ → medium · ۸۰٪ → high assets/admin/pages/PolicyFormPage.test.tsx - فیلدها از schema می‌آیند (mock schema با فیلد ساختگی → در UI ظاهر شود) - عملگرهای نامعتبر برای نوع فیلد نمایش داده نمی‌شوند ``` ## ۹. مستندات `docs/api/policy.md` را با `simulate` و `policy-templates` و شرط جدید `activate` به‌روز کن. در `docs/architecture/policy-engine.md` یک بخش «چرا آزمایش اجباری است» اضافه کن با ارجاع به ریسک دوم مستند بند ۱۷.