Make source-link listings reachable + skip uncontactable applicants
CI/CD / CI · dotnet build (push) Successful in 1m33s
CI/CD / Deploy · hamkadr (push) Has been cancelled

(1) The contact modal only offered a click-through link for Divar sources, so medboom/
iranestekhdam/channel listings with no inline phone looked uncontactable. Offer the source link
for ANY source («مشاهده آگهی در منبع»), for talent, shifts, and jobs alike — rescuing the dead
applicant cards that actually have a source URL.
(2) At publish, skip an applicant («آماده به کار») that has NO contact path at all — no phone, no
contact channel, and no source URL. Such a card cannot reach anyone. Existing ones drop out when
the talent reprocess button rebuilds the board.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-21 20:28:04 +03:30
parent 98fc01be8e
commit 0334cac3dc
2 changed files with 13 additions and 6 deletions
+6 -6
View File
@@ -426,8 +426,8 @@ app.MapGet("/contact", async (string? type, int id, AppDbContext db, InterestSer
if (!string.IsNullOrWhiteSpace(s.Facility!.BaleId)) items.Add(Item(ContactType.Bale, s.Facility.BaleId!));
}
if (items.Count == 0 && !string.IsNullOrWhiteSpace(s.SourceUrl)
&& Uri.TryCreate(s.SourceUrl, UriKind.Absolute, out var ss) && ss.Host.Contains("divar"))
{ fallbackUrl = s.SourceUrl; fallbackLabel = "مشاهده شماره در دیوار ↗"; }
&& Uri.TryCreate(s.SourceUrl, UriKind.Absolute, out var ss))
{ fallbackUrl = s.SourceUrl; fallbackLabel = ss.Host.Contains("divar") ? "مشاهده شماره در دیوار ↗" : "مشاهده آگهی در منبع ↗"; }
await interest.LogAsync(InterestEventType.Apply, id);
break;
}
@@ -444,8 +444,8 @@ app.MapGet("/contact", async (string? type, int id, AppDbContext db, InterestSer
if (!string.IsNullOrWhiteSpace(j.Facility!.BaleId)) items.Add(Item(ContactType.Bale, j.Facility.BaleId!));
}
if (items.Count == 0 && !string.IsNullOrWhiteSpace(j.SourceUrl)
&& Uri.TryCreate(j.SourceUrl, UriKind.Absolute, out var js) && js.Host.Contains("divar"))
{ fallbackUrl = j.SourceUrl; fallbackLabel = "مشاهده شماره در دیوار ↗"; }
&& Uri.TryCreate(j.SourceUrl, UriKind.Absolute, out var js))
{ fallbackUrl = j.SourceUrl; fallbackLabel = js.Host.Contains("divar") ? "مشاهده شماره در دیوار ↗" : "مشاهده آگهی در منبع ↗"; }
await interest.LogJobAsync(InterestEventType.Apply, id);
break;
}
@@ -458,8 +458,8 @@ app.MapGet("/contact", async (string? type, int id, AppDbContext db, InterestSer
items.AddRange(t.Contacts.OrderBy(c => c.SortOrder).Select(c => Item(c.Type, c.Value)));
if (items.Count == 0 && !string.IsNullOrWhiteSpace(t.Phone)) items.Add(Item(ContactType.Mobile, t.Phone!));
if (items.Count == 0 && !string.IsNullOrWhiteSpace(t.SourceUrl)
&& Uri.TryCreate(t.SourceUrl, UriKind.Absolute, out var su) && su.Host.Contains("divar"))
{ fallbackUrl = t.SourceUrl; fallbackLabel = "مشاهده شماره در دیوار ↗"; }
&& Uri.TryCreate(t.SourceUrl, UriKind.Absolute, out var su))
{ fallbackUrl = t.SourceUrl; fallbackLabel = su.Host.Contains("divar") ? "مشاهده شماره در دیوار ↗" : "مشاهده آگهی در منبع ↗"; }
break;
}
default: return Results.BadRequest();
@@ -677,6 +677,13 @@ public class IngestionService
// «آماده به کار» — a worker offering themselves. No facility involved.
if (parsed.Kind == ListingKind.Talent || kindStr.Contains("talent") || kindStr.Contains("آماده"))
{
// Skip uncontactable applicants: no phone, no other contact channel, AND no source link to
// click through to — such a card is useless to an employer. (A source URL is enough, since
// the detail page now offers a «مشاهده آگهی در منبع» link for any source.)
var reachable = !string.IsNullOrWhiteSpace(d?.Phone) || !string.IsNullOrWhiteSpace(parsed.Phone)
|| parsed.Contacts.Count > 0 || !string.IsNullOrWhiteSpace(raw.SourceUrl);
if (!reachable) { raw.Status = RawListingStatus.Discarded; return; }
// ONE person = ONE listing. Do NOT fan out across roles: an applicant has a single
// profession, and «پرستار» + «پرستار کودک» from the same ad were producing duplicate
// cards. Use the primary (AI) role; any secondary role names become searchable tags.