# نکات پیاده‌سازی — تسک ۱۰

## ۱. 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` یک بخش «چرا آزمایش اجباری است»
اضافه کن با ارجاع به ریسک دوم مستند بند ۱۷.
