AI qualify round 2: strip gender/seniority from roles, aide synonyms, more tag noise
CI/CD / CI · dotnet build (push) Successful in 1m17s
CI/CD / Deploy · hamkadr (push) Successful in 1m39s

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 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-20 15:41:06 +03:30
parent 993c34758f
commit 85a5191c45
2 changed files with 23 additions and 2 deletions
+3 -2
View File
@@ -155,9 +155,10 @@ public class AppSetting
نقش (role) و گروه (category): نقش (role) و گروه (category):
اول سعی کن نقش را با یکی از نقشهای رایج تطبیق دهی: پزشک عمومی، پزشک متخصص، پرستار، اول سعی کن نقش را با یکی از نقشهای رایج تطبیق دهی: پزشک عمومی، پزشک متخصص، پرستار،
پرستار سالمندان، ماما، تکنسین اتاق عمل، تکنسین فوریتهای پزشکی، کارشناس آزمایشگاه، دندانپزشک. پرستار سالمندان، ماما، تکنسین اتاق عمل، تکنسین فوریتهای پزشکی، کارشناس آزمایشگاه، دندانپزشک.
نقش را به «حرفهٔ پایه» بنویس، نه با پیشوند/پسوندِ توصیفی. گروهِ سنی، بخش، یا سطح را در نقش نقش را به «حرفهٔ پایه» بنویس، نه با پیشوند/پسوندِ توصیفی. گروهِ سنی، بخش، سطح، یا جنسیت را در
نیاور و بهجایش در tags بگذار: نقش نیاور و بهجایش در tags (و جنسیت را در فیلد gender) بگذار:
«پرستار کودک» نقش «پرستار» + تگ «کودک» «پرستار کودک» نقش «پرستار» + تگ «کودک»
«پرستار آقا» نقش «پرستار» + جنسیت «آقا»
«پرستار اورژانس» نقش «پرستار» + تگ «اورژانس» «پرستار اورژانس» نقش «پرستار» + تگ «اورژانس»
«کارآموز تکنسین داروخانه» نقش «تکنسین داروخانه» + تگ «کارآموز» «کارآموز تکنسین داروخانه» نقش «تکنسین داروخانه» + تگ «کارآموز»
فقط وقتی نقشِ جدید بساز که یک «حرفهٔ پایهٔ متفاوت» باشد که در فهرست نیست (مثل «تکنسین داروخانه»، فقط وقتی نقشِ جدید بساز که یک «حرفهٔ پایهٔ متفاوت» باشد که در فهرست نیست (مثل «تکنسین داروخانه»،
@@ -438,6 +438,8 @@ public class IngestionService
{ {
"توافقی", "پرداخت", "پرداخت توافقی", "حقوق", "دستمزد", "تماس", "شماره", "شماره تماس", "توافقی", "پرداخت", "پرداخت توافقی", "حقوق", "دستمزد", "تماس", "شماره", "شماره تماس",
"مراقبت از", "مراقبت", "همکاری", "آماده", "آماده به کار", "نیرو", "استخدام", "جذب", "مراقبت از", "مراقبت", "همکاری", "آماده", "آماده به کار", "نیرو", "استخدام", "جذب",
// personality / filler — not clinical skills
"خوش‌اخلاق", "خوش اخلاق", "خوشاخلاق", "دلسوز", "منظم", "مسئولیت‌پذیر", "مسئولیت پذیر", "باتجربه", "مجرب",
}; };
private static bool IsNoiseTag(string tag) private static bool IsNoiseTag(string tag)
@@ -454,6 +456,10 @@ public class IngestionService
/// sub-specialties («پرستار ICU») stay distinct on purpose.</summary> /// sub-specialties («پرستار ICU») stay distinct on purpose.</summary>
private Role ResolveOrCreateRole(List<Role> roles, string name, string? category) private Role ResolveOrCreateRole(List<Role> 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); var norm = NormalizeFa(name);
// (1) Already a known role (same word or spelling variant). // (1) Already a known role (same word or spelling variant).
@@ -504,6 +510,7 @@ public class IngestionService
["تکنسین فوریت‌های پزشکی"] = new[] { "فوریت پزشکی", "تکنسین اورژانس", "پارامدیک", "paramedic", "emt", "اورژانس ۱۱۵" }, ["تکنسین فوریت‌های پزشکی"] = new[] { "فوریت پزشکی", "تکنسین اورژانس", "پارامدیک", "paramedic", "emt", "اورژانس ۱۱۵" },
["کارشناس آزمایشگاه"] = new[] { "علوم آزمایشگاهی", "تکنسین آزمایشگاه", "آزمایشگاهی", "لابراتوار", "lab", "laboratory" }, ["کارشناس آزمایشگاه"] = new[] { "علوم آزمایشگاهی", "تکنسین آزمایشگاه", "آزمایشگاهی", "لابراتوار", "lab", "laboratory" },
["دندانپزشک"] = new[] { "دندان پزشک", "دندون پزشک", "dentist" }, ["دندانپزشک"] = new[] { "دندان پزشک", "دندون پزشک", "dentist" },
["کمک بهیار"] = new[] { "کمک‌یار", "کمکیار", "کمک یار", "کمک‌بهیار", "کمک بیمار" },
}); });
// Synonyms → canonical CATEGORY (the role-group used for filters/chips). // 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(); 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 =
{ "آقا", "خانم", "خانوم", "بانو", "مرد", "زن", "کارآموز", "کارورز", "ارشد", "مبتدی" };
/// <summary>Remove modifier tokens from a role name, keeping the base profession. Never strips to
/// empty (falls back to the original).</summary>
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();
}
/// <summary>Fresh ContactMethod rows for one talent listing (parser contacts + AI phone).</summary> /// <summary>Fresh ContactMethod rows for one talent listing (parser contacts + AI phone).</summary>
private static List<ContactMethod> BuildContacts(AiStructured? d, ParsedListing parsed) private static List<ContactMethod> BuildContacts(AiStructured? d, ParsedListing parsed)
{ {