Unify recommendations across shifts AND jobs
Recommendations only scored open shifts, but almost all roles — doctors especially — exist as استخدام (jobs), not dated shifts, and the only shifts are a handful of nurse shifts. So a visitor who prefers «پزشک» got nurse-shift recommendations (scored by city/freshness) because there were no doctor shifts to surface. Now the engine scores BOTH shifts and job openings: role/city/facility/pay/freshness apply to each, behavioral affinities are derived from shift AND job interest events, and the merged top-N is returned. Recommendation can now carry a Shift or a JobOpening; the card renders either (job → /Jobs/Details with employment type + salary; shift → unchanged with hour-bar). Cold start interleaves the freshest of both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,32 +1,60 @@
|
||||
@model JobsMedical.Web.Services.Recommendation
|
||||
@{
|
||||
var s = Model.Shift;
|
||||
var (badgeClass, typeLabel) = s.ShiftType switch
|
||||
var isJob = Model.IsJob;
|
||||
var role = isJob ? Model.Job!.Role?.Name : Model.Shift!.Role?.Name;
|
||||
var fac = isJob ? Model.Job!.Facility : Model.Shift!.Facility;
|
||||
var gender = isJob ? Model.Job!.GenderRequirement : Model.Shift!.GenderRequirement;
|
||||
var url = isJob ? $"/Jobs/Details/{Model.Job!.Id}" : $"/Shifts/Details/{Model.Shift!.Id}";
|
||||
string empLabel(JobsMedical.Web.Models.EmploymentType t) => t switch
|
||||
{
|
||||
ShiftType.Day => ("badge-day", "صبح"),
|
||||
ShiftType.Evening => ("badge-evening", "عصر"),
|
||||
ShiftType.Night => ("badge-night", "شب"),
|
||||
_ => ("badge-oncall", "آنکال"),
|
||||
JobsMedical.Web.Models.EmploymentType.PartTime => "پارهوقت",
|
||||
JobsMedical.Web.Models.EmploymentType.Contract => "قراردادی",
|
||||
JobsMedical.Web.Models.EmploymentType.Plan => "طرح",
|
||||
_ => "تماموقت",
|
||||
};
|
||||
}
|
||||
<a class="card card-pad shift-card" asp-page="/Shifts/Details" asp-route-id="@s.Id">
|
||||
<a class="card card-pad shift-card" href="@url">
|
||||
<div class="row" style="justify-content: space-between;">
|
||||
<span class="facility">@(s.Role?.Name ?? "شیفت")</span>
|
||||
<span class="badge @badgeClass">@typeLabel</span>
|
||||
<span class="facility">@(role ?? (isJob ? "استخدام" : "شیفت"))</span>
|
||||
@if (isJob)
|
||||
{
|
||||
<span class="badge badge-job">استخدام</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = Model.Shift!;
|
||||
var (badgeClass, typeLabel) = s.ShiftType switch
|
||||
{
|
||||
ShiftType.Day => ("badge-day", "صبح"),
|
||||
ShiftType.Evening => ("badge-evening", "عصر"),
|
||||
ShiftType.Night => ("badge-night", "شب"),
|
||||
_ => ("badge-oncall", "آنکال"),
|
||||
};
|
||||
<span class="badge @badgeClass">@typeLabel</span>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
@if (s.GenderRequirement != Gender.Any)
|
||||
@if (gender != Gender.Any)
|
||||
{
|
||||
<span class="badge badge-gender">@JalaliDate.GenderLabel(s.GenderRequirement)</span>
|
||||
<span class="badge badge-gender">@JalaliDate.GenderLabel(gender)</span>
|
||||
}
|
||||
@if (JobsMedical.Web.Services.SeoJsonLd.HasRealEmployer(s.Facility))
|
||||
@if (JobsMedical.Web.Services.SeoJsonLd.HasRealEmployer(fac))
|
||||
{
|
||||
<span>🏥 @s.Facility?.Name</span>
|
||||
<span>🏥 @fac?.Name</span>
|
||||
}
|
||||
<span>📍 @s.Facility?.City?.Name</span>
|
||||
<span>📍 @fac?.City?.Name</span>
|
||||
</div>
|
||||
<div class="row">📅 @JalaliDate.WeekDayName(s.Date)، @JalaliDate.ToLongDate(s.Date) — 🕐 @JalaliDate.Time(s.StartTime)</div>
|
||||
<partial name="_HourBar" model="s" />
|
||||
|
||||
@if (isJob)
|
||||
{
|
||||
<div class="row">💼 @empLabel(Model.Job!.EmploymentType)</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = Model.Shift!;
|
||||
<div class="row">📅 @JalaliDate.WeekDayName(s.Date)، @JalaliDate.ToLongDate(s.Date) — 🕐 @JalaliDate.Time(s.StartTime)</div>
|
||||
<partial name="_HourBar" model="s" />
|
||||
}
|
||||
|
||||
@* The "why" — what makes a pattern engine trustworthy: every pick is explained. *@
|
||||
<div class="rec-reasons">
|
||||
@@ -37,7 +65,17 @@
|
||||
</div>
|
||||
|
||||
<div class="foot">
|
||||
<span class="pay">@JalaliDate.PayLabel(s.PayType, s.PayAmount, s.SharePercent)</span>
|
||||
<span class="pay">
|
||||
@if (isJob)
|
||||
{
|
||||
@(Model.Job!.SalaryMin is long m ? JalaliDate.ToPersianDigits(m.ToString("#,0")) + " تومان" : "توافقی")
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = Model.Shift!;
|
||||
@JalaliDate.PayLabel(s.PayType, s.PayAmount, s.SharePercent)
|
||||
}
|
||||
</span>
|
||||
<span class="btn btn-outline" style="padding: 6px 14px;">جزئیات</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user