# ClaimManager — نسخهٔ نهایی ۱۴۰۵/۰۵/۲۴

این سند پاسخ سه پرسش شما و فهرست کامل اصلاحات نسخهٔ نهایی است.

---

## بخش ۱ — پاسخ صریح به دو پرسش شما

### پرسش ۱: «آیا می‌توانی کاری کنی اپ بدون کلید هم با دقت بالا کار کند؟»

**بله — اما با تفکیک صادقانهٔ دو نوع کار.** سه مسیر بدون کلید ساخته شد:

#### مسیر الف — موتور تحلیل محلی قطعی (فعال، آمادهٔ استفاده)

در فایل `.env` بگذارید:

```
LOCAL_ENGINE=true
```

بدون کلید، بدون اینترنت. آنچه با **دقت بالا و قابل استناد** انجام می‌دهد:

| کار                                                        | دقت           | توضیح                                                                                                                           |
| ---------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| استخراج مبالغ (ریال/تومان، میلیون/میلیارد)                 | بالا          | با تشخیص واحد و مقیاس؛ عددِ «مادهٔ ۳۰» هرگز مبلغ حساب نمی‌شود                                                                   |
| استخراج تاریخ‌ها (شمسی و میلادی)                           | بالا          | تقویم حفظ می‌شود و تبدیل خودسرانه انجام نمی‌گیرد                                                                                |
| ارجاعات قراردادی (ماده، بند، تبصره، بخشنامه، نشریه)        | بالا          | با شمارش تکرار و منبع نخست                                                                                                      |
| طبقه‌بندی سرفصل ادعا و انطباق با شرایط عمومی پیمان         | متوسط تا بالا | ۱۰ سرفصل: تأخیر پرداخت، تعلیق، تحویل کارگاه، تغییر مقادیر، قوهٔ قاهره، تمدید مدت، تعدیل، خواب ماشین‌آلات، نقشه‌ها، جریمهٔ تأخیر |
| صورت‌وضعیت‌ها، مکاتبات، طرفین، شمارهٔ پیمان                | بالا          | هر مورد با نام فایل و نقل قول مستقیم                                                                                            |
| محاسبات CPM/IAP، فرمول‌های خسارت (نشریهٔ ۴۳۱۱)، مالی ۵۰/۹۰ | قطعی          | این‌ها از ابتدا محاسباتی بودند و اصلاً به هوش مصنوعی وابسته نیستند                                                              |

آنچه **انجام نمی‌دهد** و صریحاً در بخش «محدودیت‌های موتور محلی» گزارش می‌کند:
استدلال حقوقی متن‌باز، تفسیر بندهای مبهم، و نگارش لایحهٔ دفاعیه.
این بخش‌ها خالی و شفاف گزارش می‌شوند و **هرگز با متن ساختگی پر نمی‌شوند**.

قاعدهٔ نقض‌ناپذیر موتور محلی: هر عدد، تاریخ و ارجاع با **نام فایل + نقل قول
مستقیم از سطر منبع** همراه است. آنچه یافت نشود، «یافت نشد» گزارش می‌شود.
خروجی آن **«دادهٔ نمونه» نیست** و هیچ واترمارک نمونه ندارد.

بررسی سلامت این مسیر:

```
bun run check:local
```

#### مسیر ب — مدل محلی روی سیستم خودتان (بدون کلید، بدون سهمیه)

اگر دستگاه شما حداقل ۱۶ گیگابایت RAM دارد، این مسیر کیفیت استدلال حقوقی را هم
برمی‌گرداند و باز هم هیچ کلیدی لازم ندارد:

```
ollama serve
ollama pull qwen2.5:14b-instruct
```

سپس در `.env`:

```
LLM_BASE_URL=http://127.0.0.1:11434/v1
LLM_API_KEY=ollama
LLM_MODEL_PRO=qwen2.5:14b-instruct
LLM_MODEL_FREE=qwen2.5:7b-instruct
```

چون اپ با هر سرویس سازگار با OpenAI کار می‌کند، هیچ تغییری در کد لازم نیست.

#### مسیر ج — ترکیبی (پیش‌فرض جدید و پیشنهاد ما)

بدون هیچ تنظیمی، رفتار پیش‌فرض این است: **اول هوش مصنوعی؛ اگر سهمیه تمام شد یا
سرویس قطع بود، همان اجرا با موتور محلی تکمیل می‌شود و متوقف نمی‌گردد.** منشأ هر
بسته (هوش مصنوعی یا محلی) در بالای گزارش ذکر می‌شود.

---

### پرسش ۲: «یا یک کلید رایگان بسیار قوی برایم ایجاد کنی از هر سایتی که می‌شود»

**این یکی را نمی‌توانم انجام دهم و ترجیح می‌دهم صریح بگویم تا اینکه شما را
معطل کنم.** دلیل فنی و حقوقی:

- ثبت‌نام در این سرویس‌ها نیازمند حساب شخصی، ایمیل و اغلب تأیید شمارهٔ موبایل
  شماست. من به آن‌ها دسترسی ندارم.
- کلیدی که من بسازم، اعتبار و مالکیتش متعلق به من خواهد بود نه شما؛ هر زمان
  ابطال شود اپ شما دوباره می‌خوابد. این یک راه‌حل انترپرایز نیست.

در عوض، کوتاه‌ترین مسیرهای واقعی برای گرفتن کلید رایگان توسط خودتان:

| سرویس                | آدرس                       | نکته                                                            |
| -------------------- | -------------------------- | --------------------------------------------------------------- |
| **AvalAI**           | avalai.ir                  | میزبان داخل ایران، بدون VPN — همین الان در `.env` شما تنظیم است |
| **Gilas**            | gilas.io                   | میزبان داخل ایران، بدون VPN                                     |
| **Groq**             | console.groq.com/keys      | رایگان، بدون کارت بانکی، سرعت بسیار بالا                        |
| **OpenRouter**       | openrouter.ai/keys         | مدل‌های با پسوند `:free`                                        |
| **Google AI Studio** | aistudio.google.com/apikey | سهمیهٔ رایگان روزانه                                            |

سه مورد آخر از ایران نیاز به تغییر مسیر شبکه دارند؛ دو مورد اول ندارند.

**نکتهٔ مهم برای شما:** کلید دوم را در `LLM_FALLBACK_API_KEY` بگذارید. از این
نسخه، وقتی کلید اول به ۴۲۹ بخورد، اپ خودکار به کلید دوم سوئیچ می‌کند و کلید اول
را روی cooldown می‌گذارد.

راهنمای کامل و آدرس‌های دقیق با اجرای این دستور هم چاپ می‌شود:

```
bun run check:ai
```

---

## بخش ۲ — علت واقعی خطای «تحلیل تکمیل نشد» و رفع قطعی آن

### علت ۱ — سهمیهٔ کلید (۴۲۹) کل اجرا را می‌کشت

خطای اسکرین‌شات شما «محدودیت تعداد درخواست» بود. این خطا از سمت سرویس‌دهنده
می‌آید، نه از کد. اما **واکنش کد به آن غلط بود**: با شکست بستهٔ ۱ از ۵، کل تحلیل
`throw` می‌شد و کاربر با دست خالی می‌ماند.

سه اصلاح ریشه‌ای انجام شد:

1. **احترام به `Retry-After`.** سیاست قبلی نردبان ثابت ۱→۳→۹ ثانیه داشت و راهنمای
   خود سرویس‌دهنده را نادیده می‌گرفت؛ نتیجه این بود که هر چهار تلاش داخل همان
   پنجرهٔ سهمیهٔ تمام‌شده می‌افتاد و همه رد می‌شدند. حالا اگر سرویس‌دهنده مهلت
   اعلام کند، دقیقاً همان رعایت می‌شود؛ و اگر اعلام نکند، انتظار ۲۰ → ۴۰ → ۸۰
   ثانیه است تا تلاش بعدی در **پنجرهٔ بعدی** بیفتد.
2. **محدودکنندهٔ نرخ سمت کلاینت** (`LLM_RPM_LIMIT=8`). جلوگیری از ۴۲۹ ارزان‌تر از
   دریافت آن است؛ درخواست به‌جای سوزاندن یک تلاش، کمی معطل می‌شود.
3. **تکمیل به‌جای توقف.** وقتی همهٔ کلیدها و مدل‌ها از دسترس خارج شوند، اجرا با
   موتور محلی قطعی **تکمیل** می‌شود. قاعدهٔ «هیچ دادهٔ ساختگی در سند حقوقی» حفظ
   شده — اما دیگر معنایش «هیچ نتیجه‌ای» نیست.

نتیجه: پیام «تحلیل تکمیل نشد و در بستهٔ ۱ از ۵ متوقف شد» در پیکربندی پیش‌فرض
دیگر رخ نمی‌دهد. برای بازگرداندن رفتار قبلی: `STRICT_AI_ONLY=true`.

### علت ۲ — متن فارسی خراب در خودِ سورس

در اسکرین‌شات شما عبارت به‌صورت «تحلیل ��کمیل نشد» دیده می‌شد. این یک باگ واقعی
بود: **۳۵ کاراکتر خراب (U+FFFD) در ۶ فایل سورس**. هر ۳۵ مورد بازسازی شد:

| فایل                                                    | موارد |
| ------------------------------------------------------- | ----- |
| `src/legacy/services/geminiService.ts`                  | ۱۴    |
| `src/legacy/components/delay/DelayAnalysisPage.tsx`     | ۱۳    |
| `src/legacy/components/ResultsDisplay.tsx`              | ۲     |
| `src/legacy/components/delay/FinancialDelay5090Tab.tsx` | ۲     |
| `src/lib/ai/chatbot.functions.ts`                       | ۲     |
| `scripts/check-ai.mjs`                                  | ۲     |

یک تست دائمی اضافه شد که اجازه نمی‌دهد این خرابی دوباره وارد سورس شود.

### علت ۳ — هشدار نادرست «برنامهٔ زمان‌بندی»

در اسکرین‌شات دوم: «فایل `invoice (C) No.06-Rev2-Full Tadil.pdf` به‌عنوان برنامهٔ
زمان‌بندی قابل تحلیل شبکه‌ای نیست (XML یا XER لازم است)».

این یک **مثبت کاذب** بود. تابع `classifyScheduleFile` الگوی `/rev\d/` را روی نام
**هر** فایلی اجرا می‌کرد؛ پس یک صورتحساب که «Rev2» در نامش بود، برنامهٔ زمان‌بندی
تشخیص داده می‌شد و بعد به‌خاطر PDF نبودن XML رد می‌شد.

اصلاح: حدس از روی نام فقط برای ظرف‌هایی اعمال می‌شود که واقعاً می‌توانند برنامه
باشند (`.xml`، `.xer`، `.mpp`، `.mpx`)؛ فایل‌های اکسل فقط وقتی که نامشان صراحتاً
بگوید برنامه است؛ و دستهٔ انتخابی کاربر در نوار آپلود همیشه اولویت دارد.

### علت ۴ — خطای تایپ‌اسکریپت و بدهی lint

- `DocumentQualityPanel.tsx`: مقدار `hasReference` محاسبه می‌شد اما در خروجی
  برنمی‌گشت (خطای واقعی کامپایل). رفع شد.
- ۶٬۵۶۵ خطای lint موجود در پروژه پاک‌سازی شد و **به صفر رسید**. شامل حذف تمام
  `any`های ناایمن با تعریف تایپ واقعی (Web Speech API، pdf.js، ورودی انتخاب پوشه)،
  اصلاح regexهای معیوب، و حذف بلوک `catch` خالی.

---

## بخش ۳ — وضعیت تست‌ها و اعتبارسنجی

```
bun run check:all      # typecheck + lint + test  (هر سه با هم)
```

| بررسی        | نتیجه                                                          |
| ------------ | -------------------------------------------------------------- |
| تست‌ها       | **۱۰۳ تست، ۰ شکست** (از ۳۳ تست قبلی)                           |
| Typecheck    | **پاک** — بدون خطا                                             |
| Lint         | **۰ خطا** (۹ هشدار بی‌ضرر react-refresh در فایل‌های shadcn/ui) |
| Build تولیدی | **موفق** روی نصب تمیز                                          |
| موتور محلی   | **۱۲ بررسی، همه سبز** (`bun run check:local`)                  |

### تست‌های جدید

| فایل                                   | تعداد | پوشش                                                                              |
| -------------------------------------- | ----- | --------------------------------------------------------------------------------- |
| `tests/localEngine.test.ts`            | ۳۲    | استخراج مبلغ/تاریخ/ماده/سرفصل، تضمین «عدم ساخت داده»، قطعی بودن خروجی             |
| `tests/retryPolicy.test.ts`            | ۲۳    | طبقه‌بندی ۴۲۹/۴۰۲/۴۰۱/۴۰۴/۵xx، خواندن Retry-After، نردبان انتظار، محدودکنندهٔ نرخ |
| `tests/integrity.test.ts`              | ۹     | نبود کاراکتر خراب، اعتبار UTF-8، نبود کلید هاردکد، صحت اتصال موتور محلی           |
| `tests/scheduleClassification.test.ts` | ۶     | مثبت کاذب صورتحساب، تشخیص درست XML/XER/MPP، اولویت دستهٔ کاربر                    |
| `tests/engine.test.ts` (موجود)         | ۲۰    | تقویم جلالی، CPM، IAP، فرمول‌های ۴۳۱۱                                             |
| `tests/financial5090.test.ts` (موجود)  | ۱۳    | محاسبات ۵۰/۹۰، سقف، هم‌پوشانی مالی و فنی                                          |

نمونهٔ تست‌های مهم «عدم ساخت داده»:

- عدد `۳۰` در «مادهٔ ۳۰» هرگز به‌عنوان مبلغ ثبت نمی‌شود.
- `1403/1120` (شمارهٔ نامه) هرگز به‌عنوان تاریخ ثبت نمی‌شود.
- ورودی خالی، خروجی خالی می‌دهد — نه متن جانشین.
- خروجی موتور محلی هرگز برچسب «دادهٔ نمونه» یا `SAMPLE DATA` نمی‌گیرد.

---

## بخش ۴ — دستورهای روزمره

| دستور                 | کار                                         |
| --------------------- | ------------------------------------------- |
| `bun install`         | نصب وابستگی‌ها (یک‌بار)                     |
| `bun run dev`         | اجرای اپ                                    |
| `bun run build`       | ساخت نسخهٔ تولیدی                           |
| `bun run check:ai`    | عیب‌یابی کلید و سرویس + راهنمای کلید رایگان |
| `bun run check:local` | بررسی سلامت حالت بدون کلید                  |
| `bun test`            | اجرای ۱۰۳ تست                               |
| `bun run check:all`   | typecheck + lint + test                     |

---

## بخش ۵ — فایل‌های جدید و تغییر یافته

**جدید**

- `src/lib/ai/local-engine.ts` — موتور تحلیل محلی قطعی
- `src/lib/ai/retry-policy.ts` — سیاست تلاش مجدد، Retry-After و محدودکنندهٔ نرخ
- `scripts/check-local-engine.ts` — عیب‌یاب حالت بدون کلید
- `tests/localEngine.test.ts`، `tests/retryPolicy.test.ts`،
  `tests/integrity.test.ts`، `tests/scheduleClassification.test.ts`

**تغییر یافته**

- `src/lib/ai/analyze.functions.ts` — اتصال موتور محلی، سیاست جدید ۴۲۹، محدودکنندهٔ نرخ
- `src/lib/ai/error-details.server.ts` — استخراج هدرهای `Retry-After` و `ratelimit-reset`
- `src/legacy/services/geminiService.ts` — رفع متن خراب، گزارش منشأ هر بسته
- `src/legacy/utils/scheduleSet.ts` — رفع مثبت کاذب تشخیص برنامهٔ زمان‌بندی
- `src/legacy/components/delay/DocumentQualityPanel.tsx` — رفع خطای `hasReference`
- `scripts/check-ai.mjs` — راهنمای ۴۲۹ و گزینه‌های بدون کلید
- `package.json` — افزوده شدن `test`، `typecheck`، `check:all`، `check:local`
- `.env` و `.env.example` — پرچم‌های `LOCAL_ENGINE`، `STRICT_AI_ONLY`، `LLM_RPM_LIMIT`
- پاک‌سازی lint در سراسر `src/legacy`

---

## بخش ۶ — یک نکتهٔ صادقانه دربارهٔ کلید فعلی شما

کلید AvalAI موجود در `.env` از محیط من قابل آزمون نبود: دامنهٔ `api.avalai.ir`
اتصال از خارج ایران را رد می‌کند. بنابراین **نمی‌توانم تأیید کنم که سهمیهٔ آن
کلید تمام شده یا نه** — این را باید خودتان با اجرای `bun run check:ai` روی
سیستم خودتان ببینید. آن دستور اکنون دقیقاً می‌گوید مشکل از سهمیه است، از اعتبار،
از خودِ کلید، یا از نام مدل.

اما نکتهٔ اصلی این است: **پاسخ آن دستور دیگر تعیین‌کنندهٔ کارکرد اپ نیست.** حتی
اگر کلید کاملاً مرده باشد، اپ با موتور محلی کار می‌کند و گزارش تولید می‌کند.
