From cf5e0011c4debcaa4dbf3cb68010b43551c8aac3 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Tue, 9 Jun 2026 19:04:24 +0330 Subject: [PATCH] AI ingestion: dynamic role/category creation + tags, hardcoded read-only prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unknown roles from the AI are now resolved-or-CREATED (Persian-normalized dedupe) instead of dropped/fallback; new role gets the AI's category, assigned to the applicant. - AI output gains category + tags; AI-detected skills/requirements (ICU, MMT, پروانه‌دار…) now fold into the applicant's searchable Tags. - System prompt is hardcoded in AppSetting.DefaultPrompt and used directly by the auditor; admin sees it read-only (cannot edit/break it). Co-Authored-By: Claude Opus 4.8 --- src/JobsMedical.Web/Models/AppSetting.cs | 52 +++++++++----- .../Pages/Admin/Settings.cshtml | 7 +- .../Pages/Admin/Settings.cshtml.cs | 5 +- .../Services/Scraping/AiAuditor.cs | 29 ++++++-- .../Services/Scraping/IngestionService.cs | 70 +++++++++++++++---- .../Services/Scraping/SettingsService.cs | 3 +- 6 files changed, 123 insertions(+), 43 deletions(-) diff --git a/src/JobsMedical.Web/Models/AppSetting.cs b/src/JobsMedical.Web/Models/AppSetting.cs index e8dce06..8933248 100644 --- a/src/JobsMedical.Web/Models/AppSetting.cs +++ b/src/JobsMedical.Web/Models/AppSetting.cs @@ -138,23 +138,41 @@ public class AppSetting : s.Split(new[] { '\n', '\r', ',', '،' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToList(); + /// The fixed, code-owned system prompt the AI follows. It is hardcoded (shown read-only + /// in admin) so it can't drift or be broken by an edit. The authoritative output-key schema is + /// appended automatically by OpenAiCompatibleAuditor, so this text stays behavioral. public const string DefaultPrompt = """ - تو دستیار بررسی آگهی‌های کاری حوزه درمان برای پلتفرم «همکادر» هستی. - هر آگهی خام را بخوان و تصمیم بگیر: - - approve: آگهی واقعی و مرتبط با کادر درمان است و اطلاعات کافی دارد. - - reject: تبلیغ، اسپم، نامرتبط، یا فاقد اطلاعات حداقلی است. - - review: مرتبط است اما ناقص/مبهم و نیاز به بررسی انسانی دارد. - سه نوع آگهی داریم: - - shift: مرکز درمانی برای یک شیفت نیرو می‌خواهد. - - job: مرکز درمانی برای استخدام دائم نیرو می‌خواهد. - - talent: خودِ کادر درمان اعلام «آماده به کار / آماده همکاری» کرده است. - نقش، شهر/محله، نوع شیفت/همکاری، مبلغ یا درصد سهم، عنوان، نام مرکز، و شماره تماس را در صورت وجود استخراج کن. - برای talent: نام فرد، سال سابقه و پروانه‌دار بودن را هم استخراج کن. - فقط با یک شیء JSON پاسخ بده با کلیدهای: - decision (approve|reject|review)، confidence (0-100)، reason (فارسی کوتاه)، - kind (shift|job|talent)، role، city، district، shiftType (day|evening|night|oncall)، - employmentType (fulltime|parttime|contract|plan)، payAmount (عدد تومان یا null)، - sharePercent (0-100 یا null)، title، facilityName، phone، - personName، yearsExperience (عدد یا null)، isLicensed (true|false). + تو دستیار دسته‌بندی آگهی‌های کادر درمان تهران برای پلتفرم «همکادر» هستی. هر ورودی یک متن خام از + کانال‌های تلگرام/بله/دیوار است. وظیفه: (۱) تشخیص نوع، (۲) استخراج دقیق فیلدها و دسته‌بندی فرد، + (۳) تصمیم تأیید/رد/بررسی. فقط یک شیء JSON معتبر برگردان؛ هیچ متن اضافه‌ای ننویس. + + نوع (kind): + • shift = مرکز درمانی برای بازهٔ زمانی مشخص نیرو می‌خواهد. + • job = مرکز درمانی استخدام دائم/قراردادی دارد. + • talent= فردی از کادر درمان خودش را «آماده به کار / آماده همکاری» معرفی می‌کند + (سمت عرضه؛ مرکز ندارد و شماره تماسِ خودِ فرد مهم‌ترین فیلد است). + + نقش (role) و گروه (category): + اول سعی کن نقش را با یکی از نقش‌های رایج تطبیق دهی: پزشک عمومی، پزشک متخصص، پرستار، + پرستار سالمندان، ماما، تکنسین اتاق عمل، تکنسین فوریت‌های پزشکی، کارشناس آزمایشگاه، دندانپزشک. + اگر تخصص دقیقاً در این فهرست نبود، عنوانِ دقیق و استانداردِ همان نقش را بنویس + (مثل «پرستار ICU»، «کارشناس رادیولوژی»، «متخصص بیهوشی») — سیستم آن را به‌عنوان نقش جدید + ثبت و به همین فرد نسبت می‌دهد. عنوان را کوتاه و رسمی بنویس، نه جمله. + category را گروهِ آن نقش بگذار (پزشک | پرستار | ماما | تکنسین | دندانپزشک)؛ + اگر هیچ‌کدام مناسب نبود، یک گروهِ کوتاهِ مناسب پیشنهاد بده. + + مهارت‌ها/الزامات (tags): هر مهارت، گواهی یا شرطِ کلیدی را به‌صورت آرایه‌ای از کلیدواژه‌های + کوتاه برگردان (مثل "ICU"، "MMT"، "CPR"، "دیالیز"، "پروانه‌دار"، "خانم"، "آقا"). اگر نبود []. + + شهر (city): فقط نام شهر (مثل «تهران»). محله/منطقه را در district بگذار. + + تصمیم (decision): + • approve = آگهیِ واقعیِ مرتبط با کادر درمان تهران با اطلاعات کافی. + • reject = اسپم/تبلیغ/نامرتبط/خارج از کادر درمانِ تهران. + • review = مرتبط ولی مبهم/ناقص. + confidence را ۰ تا ۱۰۰ بده و reason را کوتاه و فارسی بنویس. + + برای talent: personName، yearsExperience، isLicensed (پروانه‌دار) و phone (ارقام لاتین) + را در صورت ذکر پر کن. هر فیلدِ نامشخص = null. """; } diff --git a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml index 5ee0128..d1ea742 100644 --- a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml +++ b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml @@ -71,9 +71,10 @@
- - -

به مدل بگو چطور تأیید/رد کند و چه فیلدهایی را استخراج کند. خروجی باید JSON باشد.

+ + +

این دستور در کد ثابت شده و قابل ویرایش نیست تا دسته‌بندی و استخراج همیشه درست بماند. یک «اسکیمای خروجی JSON» هم به‌صورت خودکار به انتهای آن افزوده می‌شود.