"""Scheduled-job handler for the leave-health insights trend snapshot. ``hr.leave_insight_snapshot`` — once a day, compute the current leave-health roll-ups (company baseline + every BU / department / lead) and persist them into ``hr_leave_insight_snapshots`` so the "is this team's leave culture improving?" trend has a point per day. The live endpoints always recompute fresh from ``hr_leave_accounts``; this table is *only* the historical series. There is no backfill — SF leave data is current-state, so the series starts the day this cron first runs. Runs AFTER ``hr.leave_account_sync`` (cron 07:10 vs the sync's 06:30) so each day's snapshot reflects that morning's freshly-synced balances. Idempotent: a same-day re-run replaces the day's rows wholesale inside one transaction. """ from __future__ import annotations from typing import Any from sqlalchemy import delete from sqlalchemy.ext.asyncio import AsyncSession from app.scheduled_jobs.registry import register_scheduled_job @register_scheduled_job( key="hr.leave_insight_snapshot", display_name="休假健康洞察 · 每日趋势快照", description=( "每日计算休假健康洞察的全部口径(公司基线 + 各 BU / 团队 / Lead 的年假使用率、" "对比公司中位、临期未休、调休堆积、健康度判级),写入 hr_leave_insight_snapshots," "为「团队休假文化是否在改善」的趋势图逐日累积数据点。无历史回填(SF 假期为当下态)," "序列自上线起累积。排在 hr.leave_account_sync 之后运行。当日重跑整体覆盖,幂等。" ), ) async def run_hr_leave_insight_snapshot( db: AsyncSession, config: dict[str, Any], ) -> dict[str, Any]: from app.domains.hr.leave_insights_models import HrLeaveInsightSnapshot from app.domains.hr.leave_insights_service import ( COMPANY_KEY, DIMENSION_BU, DIMENSION_COMPANY, DIMENSION_DEPARTMENT, DIMENSION_LEAD, LeaveInsightsService, ) svc = LeaveInsightsService(db) ctx = await svc.build_context() today = ctx.as_of # Replace today's rows wholesale (idempotent re-run). await db.execute( delete(HrLeaveInsightSnapshot).where( HrLeaveInsightSnapshot.snapshot_date == today, ) ) written = 0 def _add_group(g) -> None: nonlocal written db.add(HrLeaveInsightSnapshot( snapshot_date=today, dimension=g.dimension, group_key=g.key, group_label=g.label, fiscal_year=ctx.fiscal_year, member_count=g.member_count, covered_count=g.covered_count, annual_entitled_total=round(g.annual_entitled_total, 2), annual_used_total=round(g.annual_used_total, 2), annual_balance_total=round(g.annual_balance_total, 2), forfeit_at_risk_total=round(g.forfeit_at_risk_total, 2), comp_banked_total=round(g.comp_banked_total, 2), utilisation=g.utilisation, median_utilisation=g.median_utilisation, pace_index=g.pace_index, vs_company=g.vs_company, low_utiliser_count=g.low_utiliser_count, health=g.health, )) written += 1 # Company baseline row (so any group line can be drawn against it). covered = sum(1 for m in ctx.metrics if m.has_quota) db.add(HrLeaveInsightSnapshot( snapshot_date=today, dimension=DIMENSION_COMPANY, group_key=COMPANY_KEY, group_label="全公司", fiscal_year=ctx.fiscal_year, member_count=len(ctx.metrics), covered_count=covered, annual_entitled_total=ctx.company_entitled_total, annual_used_total=ctx.company_used_total, annual_balance_total=round( sum(m.annual_balance for m in ctx.metrics), 2, ), forfeit_at_risk_total=round( sum(m.forfeit_at_risk for m in ctx.metrics), 2, ), comp_banked_total=round(sum(m.comp_balance for m in ctx.metrics), 2), utilisation=ctx.company_utilisation, median_utilisation=None, pace_index=ctx.company_pace_index, vs_company=(1.0 if ctx.company_utilisation is not None else None), low_utiliser_count=0, health=None, )) written += 1 counts = {DIMENSION_COMPANY: 1} for dim in (DIMENSION_BU, DIMENSION_DEPARTMENT, DIMENSION_LEAD): groups = svc.group(dim, ctx) for g in groups: _add_group(g) counts[dim] = len(groups) await db.commit() return { "snapshot_date": today.isoformat(), "rows_written": written, "fiscal_year": ctx.fiscal_year, "active_population": len(ctx.metrics), "covered_population": covered, "groups_by_dimension": counts, }