From 85a5191c45fd705d1ff5acbebfc82feb06d2b50f Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Sat, 20 Jun 2026 15:41:06 +0330 Subject: [PATCH] AI qualify round 2: strip gender/seniority from roles, aide synonyms, more tag noise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-checked live data and found cases the first pass missed: - Gender baked into roles («پرستار آقا», «کمک بهیار آقا») → StripRoleModifiers removes آقا/خانم/مرد/زن/کارآموز/ارشد… from role names (none of the real roles contain these), collapsing the sprawl; gender still lives on the Gender field. - «کمک‌یار» vs «کمک بهیار» forking → alias maps them to one role. - Personality words («خوش‌اخلاق», «دلسوز», «منظم»…) added to the tag stop-list. - Prompt: gender goes to the gender field, not the role. Co-Authored-By: Claude Opus 4.8 --- src/JobsMedical.Web/Models/AppSetting.cs | 5 +++-- .../Services/Scraping/IngestionService.cs | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/JobsMedical.Web/Models/AppSetting.cs b/src/JobsMedical.Web/Models/AppSetting.cs index e1f9b2b..036ba82 100644 --- a/src/JobsMedical.Web/Models/AppSetting.cs +++ b/src/JobsMedical.Web/Models/AppSetting.cs @@ -155,9 +155,10 @@ public class AppSetting نقش (role) و گروه (category): اول سعی کن نقش را با یکی از نقش‌های رایج تطبیق دهی: پزشک عمومی، پزشک متخصص، پرستار، پرستار سالمندان، ماما، تکنسین اتاق عمل، تکنسین فوریت‌های پزشکی، کارشناس آزمایشگاه، دندانپزشک. - نقش را به «حرفهٔ پایه» بنویس، نه با پیشوند/پسوندِ توصیفی. گروهِ سنی، بخش، یا سطح را در نقش - نیاور و به‌جایش در tags بگذار: + نقش را به «حرفهٔ پایه» بنویس، نه با پیشوند/پسوندِ توصیفی. گروهِ سنی، بخش، سطح، یا جنسیت را در + نقش نیاور و به‌جایش در tags (و جنسیت را در فیلد gender) بگذار: «پرستار کودک» → نقش «پرستار» + تگ «کودک» + «پرستار آقا» → نقش «پرستار» + جنسیت «آقا» «پرستار اورژانس» → نقش «پرستار» + تگ «اورژانس» «کارآموز تکنسین داروخانه» → نقش «تکنسین داروخانه» + تگ «کارآموز» فقط وقتی نقشِ جدید بساز که یک «حرفهٔ پایهٔ متفاوت» باشد که در فهرست نیست (مثل «تکنسین داروخانه»، diff --git a/src/JobsMedical.Web/Services/Scraping/IngestionService.cs b/src/JobsMedical.Web/Services/Scraping/IngestionService.cs index 98b1a68..7e5a7f5 100644 --- a/src/JobsMedical.Web/Services/Scraping/IngestionService.cs +++ b/src/JobsMedical.Web/Services/Scraping/IngestionService.cs @@ -438,6 +438,8 @@ public class IngestionService { "توافقی", "پرداخت", "پرداخت توافقی", "حقوق", "دستمزد", "تماس", "شماره", "شماره تماس", "مراقبت از", "مراقبت", "همکاری", "آماده", "آماده به کار", "نیرو", "استخدام", "جذب", + // personality / filler — not clinical skills + "خوش‌اخلاق", "خوش اخلاق", "خوشاخلاق", "دلسوز", "منظم", "مسئولیت‌پذیر", "مسئولیت پذیر", "باتجربه", "مجرب", }; private static bool IsNoiseTag(string tag) @@ -454,6 +456,10 @@ public class IngestionService /// sub-specialties («پرستار ICU») stay distinct on purpose. private Role ResolveOrCreateRole(List roles, string name, string? category) { + // Drop gender/seniority modifiers baked into the role («پرستار آقا»→«پرستار», + // «کارآموز تکنسین داروخانه»→«تکنسین داروخانه»). None of the real roles contain these tokens, + // so it only collapses sprawl — the modifier still lives on as a tag / the Gender field. + name = StripRoleModifiers(name); var norm = NormalizeFa(name); // (1) Already a known role (same word or spelling variant). @@ -504,6 +510,7 @@ public class IngestionService ["تکنسین فوریت‌های پزشکی"] = new[] { "فوریت پزشکی", "تکنسین اورژانس", "پارامدیک", "paramedic", "emt", "اورژانس ۱۱۵" }, ["کارشناس آزمایشگاه"] = new[] { "علوم آزمایشگاهی", "تکنسین آزمایشگاه", "آزمایشگاهی", "لابراتوار", "lab", "laboratory" }, ["دندانپزشک"] = new[] { "دندان پزشک", "دندون پزشک", "dentist" }, + ["کمک بهیار"] = new[] { "کمک‌یار", "کمکیار", "کمک یار", "کمک‌بهیار", "کمک بیمار" }, }); // Synonyms → canonical CATEGORY (the role-group used for filters/chips). @@ -537,6 +544,19 @@ public class IngestionService private static string Clamp(string s, int max) => s.Length <= max ? s : s[..max].Trim(); + // Gender/seniority tokens that don't belong in a role name (they go to tags / the Gender field). + private static readonly string[] RoleModifierWords = + { "آقا", "خانم", "خانوم", "بانو", "مرد", "زن", "کارآموز", "کارورز", "ارشد", "مبتدی" }; + + /// Remove modifier tokens from a role name, keeping the base profession. Never strips to + /// empty (falls back to the original). + private static string StripRoleModifiers(string name) + { + var kept = NormalizeFa(name).Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Where(t => !RoleModifierWords.Any(m => NormalizeFa(m) == t)).ToList(); + return kept.Count > 0 ? string.Join(" ", kept) : name.Trim(); + } + /// Fresh ContactMethod rows for one talent listing (parser contacts + AI phone). private static List BuildContacts(AiStructured? d, ParsedListing parsed) {