fix scout stats applicant counts
This commit is contained in:
@@ -9,9 +9,11 @@ Honest sources (SCOUT_UI_SPEC):
|
||||
|
||||
Anything without a real source is omitted — the UI cuts or locks it, never fakes it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import math
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -30,7 +32,9 @@ def _match_scores(opps: list[dict]) -> list[int]:
|
||||
return out
|
||||
|
||||
|
||||
def _histogram(scores: list[int], lo: int = 60, hi: int = 100, bins: int = 11) -> list[int]:
|
||||
def _histogram(
|
||||
scores: list[int], lo: int = 60, hi: int = 100, bins: int = 11
|
||||
) -> list[int]:
|
||||
h = [0] * bins
|
||||
span = (hi - lo) / bins
|
||||
for s in scores:
|
||||
@@ -57,9 +61,14 @@ def _apply_window(opps: list[dict]) -> dict:
|
||||
return {"median_age_days": None, "fresh_share": None, "urgency": None}
|
||||
ages.sort()
|
||||
median = ages[len(ages) // 2]
|
||||
fresh = sum(1 for a in ages if a <= 3) / len(ages) # posted within 3 days
|
||||
fresh = sum(1 for a in ages if a <= 3) / len(ages) # posted within 3 days
|
||||
urgency = "high" if median <= 3 else "medium" if median <= 7 else "low"
|
||||
return {"median_age_days": median, "fresh_share": round(fresh, 2), "urgency": urgency, "dated": len(ages)}
|
||||
return {
|
||||
"median_age_days": median,
|
||||
"fresh_share": round(fresh, 2),
|
||||
"urgency": urgency,
|
||||
"dated": len(ages),
|
||||
}
|
||||
|
||||
|
||||
async def build_scout_stats(user_id: str, *, user_uuid: str | None = None) -> dict:
|
||||
@@ -78,7 +87,17 @@ async def build_scout_stats(user_id: str, *, user_uuid: str | None = None) -> di
|
||||
opps = feed.get("opportunities") or []
|
||||
|
||||
scores = _match_scores(opps)
|
||||
apps = [j.get("applicants") for j in opps if j.get("applicants")]
|
||||
apps = []
|
||||
for opportunity in opps:
|
||||
raw_applicants = opportunity.get("applicants")
|
||||
if isinstance(raw_applicants, bool):
|
||||
continue
|
||||
try:
|
||||
applicants = float(raw_applicants)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if math.isfinite(applicants) and applicants >= 0:
|
||||
apps.append(applicants)
|
||||
matches = len(opps)
|
||||
|
||||
return {
|
||||
@@ -93,22 +112,34 @@ async def build_scout_stats(user_id: str, *, user_uuid: str | None = None) -> di
|
||||
# salary band = average of each deck's peak salary, accumulated across decks (₹L)
|
||||
"salaryMaxL": salary_band,
|
||||
"avgApplicants": round(sum(apps) / len(apps)) if apps else None,
|
||||
"activeWindow": active if active.get("samples") else None, # WHEN the user works their search
|
||||
"activeWindow": active
|
||||
if active.get("samples")
|
||||
else None, # WHEN the user works their search
|
||||
# ── activity-derived (real, this DB) — funnel is all-time so the stages are consistent ──
|
||||
"funnel": [
|
||||
{"label": "Matches", "value": max(act["matches"], matches), "tracked": True},
|
||||
{
|
||||
"label": "Matches",
|
||||
"value": max(act["matches"], matches),
|
||||
"tracked": True,
|
||||
},
|
||||
{"label": "Viewed", "value": act["viewed"], "tracked": True},
|
||||
{"label": "Shortlisted", "value": act["saved"], "tracked": True},
|
||||
{"label": "Applied", "value": act["applied"], "tracked": True},
|
||||
],
|
||||
"tailoredResumes": act["tailored"],
|
||||
"searches": act["searches"],
|
||||
"percentile": act["percentile"], # cohort engagement percentile (top 100-percentile %)
|
||||
"percentile": act[
|
||||
"percentile"
|
||||
], # cohort engagement percentile (top 100-percentile %)
|
||||
"cohortSize": act["cohort_size"],
|
||||
"searchStage": "Applying" if act["applied"] else "Reviewing" if act["saved"] else "Scanning",
|
||||
"searchStage": "Applying"
|
||||
if act["applied"]
|
||||
else "Reviewing"
|
||||
if act["saved"]
|
||||
else "Scanning",
|
||||
# ── cross-service (real; None → the UI omits the card, never fakes it) ──
|
||||
"qxNow": (qx or {}).get("qx"),
|
||||
"quotients": (qx or {}).get("quotients"),
|
||||
"qxTrend": qx_trend, # RQ Score series (qscore-service) → Readiness-trend sparkline
|
||||
"qxTrend": qx_trend, # RQ Score series (qscore-service) → Readiness-trend sparkline
|
||||
"dayStreak": streak,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user