BSC fiscal cycles, task delivery, and month locks
Status: Shipped (Phases A–D, June 2026)
Code: api/app/services/bsc_cycle.py, scorecard_config_store.py, task_delivery_*.py, scorecard_month_lock.py, bsc_month_report_store.py
UI: Scorecard → Configurations, Task Delivery, BSC dashboard sync actions
This document is the source of truth for BSC 2025 / 2026 behavior, per-cycle configuration, task delivery, and finalized-month editing rules. Related formulas: User-month calculation, BSC metrics. Dashboard sync: BigQuery dashboard. Mid-month weekly locks for SBS / Non-SBS / ATW: Scorecard § Weekly locking.
1. Fiscal cycle model
| Concept | Rule |
|---|---|
| Cycle label | BSC {start_year} — e.g. BSC 2025 = July 2025 through June 2026 |
| Cycle id | String start year: "2025", "2026" |
| Start month | July (start_month = 7), stored in bsc-cycle-settings for future flexibility |
| Resolver | Calendar month maps to cycle id: Jul–Dec → that year; Jan–Jun → previous year |
Examples:
| Calendar month | Fiscal cycle id |
|---|---|
| May 2026 | 2025 (still closing BSC 2025) |
| June 2026 | 2025 |
| July 2026 | 2026 |
Registry: Mongo tbl_scorecard_config, document _id: "bsc-cycle-settings" — list of BscCycleDefinition + start_month.
API: GET /v1/scorecard/bsc-cycles — each cycle includes status (upcoming | active | ended) and editable (config edits allowed only while upcoming).
UI: Scorecard → Configurations — cycle dropdown, Add cycle (upcoming cycles only). Extra upcoming cycles beyond the next one may be deleted.
2. Per-cycle configuration (versioned)
Legacy single documents were migrated to per-cycle ids on first access. Each cycle has its own bundle:
| Config | Mongo _id pattern |
|---|---|
| Evaluation count requirements | evaluation_count_requirement-{cycle_id} |
| Balance scorecard metrics (KPM/KPI tree) | balance-score-card-metrics-{cycle_id} |
| BSC total score goals | bsc-total-score-goals-{cycle_id} |
| SBS checklist rubric | sbs-checklist-rubric-{cycle_id} |
Resolve config for a calendar month:
cycle_id = resolve_cycle_id(year, month)— fiscal cycle for the month.metrics_cycle_id = resolve_metrics_cycle_id(year, month, cycle_id)— same as fiscalcycle_id(metrics follow the fiscal cycle).- Load KPIs, quotas, goals, and rubric from
*-{cycle_id}.
API: GET / PATCH /v1/scorecard/configurations?cycle_id=2026 (cycle required on write).
Cycle config lock: Once a cycle’s start month has begun (status active or ended), that cycle’s configuration bundle is read-only in the UI (editable: false on the cycle).
3. BSC 2025 vs BSC 2026 metrics
Both cycles use the same four Productivity KPI keys and weights (20 / 25 / 25 / 30). Formulas differ from BSC 2026 onward (July 2026+). BSC 2025 rules apply through June 2026 inclusive.
BSC 2025 Productivity (Jul 2025 – Jun 2026)
| KPI | Weight | Formula |
|---|---|---|
quantity_of_output | 20% | Mean SBS + Non-SBS quantity_score; eval ratio when month finalized |
integrity | 25% | Mean Non-SBS only quantity_score; eval ratio on Non-SBS |
accuracy | 25% | Mean SBS + Non-SBS auditors_score (N/A excluded); eval ratio |
compliance | 30% | Mean SBS checklist score (timeliness − integrity) − 1% per productivity issue |
Non-SBS entries use auditor score only (no checklist).
BSC 2026+ Productivity (Jul 2026 onward)
| KPI | Weight | Formula |
|---|---|---|
quantity_of_output | 20% | Task delivery: daily finished ÷ task count on scheduled workdays through today, month average. No eval ratio. |
integrity | 25% | Mean SBS + Non-SBS quantity_score; combined eval ratio |
accuracy | 25% | Mean SBS + Non-SBS checklist score (timeliness − integrity); combined eval ratio |
compliance | 30% | 100% − 1% per productivity issue |
Non-SBS entries use the same Yes/No checklist and per-cycle rubric as SBS (no auditor score). Task delivery data is entered on the Task Delivery scorecard page.
Config: quantity_of_output in the BSC 2026 metrics document uses source task_delivery (same KPI key, different compute path).
4. SBS checklist rubric (per cycle)
SBS Yes/No scoring weights, red flags, and integrity divisor are no longer hardcoded only in Python/TS constants.
- Stored per cycle in
sbs-checklist-rubric-{cycle_id}. - Loaded at runtime by task-evaluated date:
GET /v1/scorecard/sbs-checklist-rubric?year=&month=. - Server recalculates on create/update via
sbs_rubric_runtime.py+sbs_entry_calculated.py. - Web SBS form, view modal, and calculated panel use
useSbsRubricForDate+sbsRubricRuntime.ts.
BSC 2025 and 2026 were seeded with the same rubric as the former hardcoded defaults.
5. Task delivery
Purpose
Daily finished tasks vs task count on scheduled workdays for BSC-enabled employees. Contributes the task_delivery KPI when present in the active metrics bundle.
Data
| Item | Detail |
|---|---|
| Collection | tbl_task_delivery_entries |
| Grain | One document per employee_id + work_date (US M/D/YYYY in API) |
| Fields | finished_task, task_count, submitted_by_id |
Workdays
Only days whose schedule day code has is_task_delivery_day: true in the catalog (configured at Scorecard → Configurations → Schedule Day Codes). This is separate from is_working_day (attendance working_days): e.g. VL and SL default to task-delivery eligible but not attendance working days. No Mon–Fri fallback for task delivery (unlike attendance KPI fallback).
Off days and non-TD codes appear in the UI with their abbreviation (OFF, H, VL, etc.) and are not editable for task delivery.
Scoring (monthly KPI)
For each scheduled workday through today (Eastern):
- Daily % =
min(100, finished_task / task_count × 100);task_count = 0→ day excluded from average. - Past workday with no entry → 0% for that day (counts in the average).
- Today with no entry → pending — shown in the grid but excluded from the month average until saved.
- Month KPI = average of included day scores (
round_bsc_up).
Future workdays may be entered in advance; they are excluded from the month average until that calendar day.
KPI breakdown (task_delivery_breakdown) tags each day as scored, missing, or pending; workdays_pending_today counts today's pending days.
Code: task_delivery/kpis.py, task_delivery/workdays.py, task_delivery/store.py; BSC compute passes task_delivery_today via task_delivery_scoring_reference_date.
API
| Method | Path | Notes |
|---|---|---|
GET | /v1/scorecard/task-delivery/month | Month grid; query: year, month, org filters |
PUT | /v1/scorecard/task-delivery/day | Upsert one day; requires scorecard create permission + workday |
UI
Scorecard → Task Delivery (/scorecard/task-delivery) — month grid, employee detail, day edit modal. Filters match other scorecard pages (cluster / department / employee / month). BSC-only employees.
Grid highlights (July 2026):
| Cell state | Color | Average impact |
|---|---|---|
| Missing (past workday, no entry) | Red | Counts as 0% |
| Pending (today, no entry yet) | Amber | Excluded until saved |
| Future workday | Neutral | Excluded until the day occurs |
Avg (to date) column — hover the header or a cell for a breakdown (scored / missing / pending / N/A counts). Dashboard BSC KPI hover uses the same pending vs missing semantics.
6. Finalized calendar months (dashboard)
Dashboard BSC uses calendar months (Eastern), not fiscal cycles, for sync and finalize.
Mongo: tbl_bsc_month_reports
Document _id: "YYYY-MM" (e.g. "2026-05").
| Field | Meaning |
|---|---|
status | draft or final |
finalized_at | When final report was generated |
finalized_by_employee_id | Who finalized |
last_rolling_sync_at | Last rolling or manual sync (draft) |
reopened_at | Set when a final month is reopened (optional audit) |
Workflow
text
draft → (past month) Generate Final Report → final
final → (level 5) Reopen Month → draft → sync → Re-generate Final Report → final| Action | Who | Effect |
|---|---|---|
| Refresh dashboard data (sync) | Access level 5 | Recompute draft month → BigQuery report_status=draft |
| Generate Final Report | Access level 5 | Past month only; full eval ratios; BQ final; Mongo status=final |
| Re-generate Final Report | Access level 5 | Past final month; overwrite BQ final snapshot |
| Reopen Month | Access level 5 | Past final month → status=draft; allows scorecard edits and re-sync |
Rolling sync (if enabled) runs for the current month only and skips final months.
API:
GET /v1/dashboard/bsc/month-report?year=&month=— status +can_sync,can_finalize,can_regenerate_final,can_reopen,can_edit_entriesPOST /v1/dashboard/bsc/syncPOST /v1/dashboard/bsc/finalize?regenerate=false|truePOST /v1/dashboard/bsc/reopen
UI: BSC dashboard — sync / finalize / re-gen / reopen controls (BscSyncActions.tsx).
7. Scorecard entry locks (finalized months)
When a calendar month is final, scorecard mutations for entries in that month are blocked for most users.
Rules
| User | Can edit entries in a finalized month? |
|---|---|
| Access level 1–4 | No (API 409, UI disabled with tooltip) |
| Access level 5 | Yes (super-admin override; should re-sync and re-generate final report after corrections) |
Lock is keyed on the entry date (calendar month of):
| Entry type | Date field |
|---|---|
| SBS / Non-SBS | task_evaluated_date |
| Productivity / Attendance / ATW | date_of_occurrence |
| Task delivery | work_date |
Moving an entry to another month checks both old and new months.
Code: scorecard_month_lock.py, wired in scorecard_access.py on all create/update/delete routes.
UI
- Banner on scorecard entry pages when viewing finalized month(s) (
ScorecardMonthLockBanner). - Context menu: Edit / Delete / Resync shown but disabled with full lock message on hover.
- Task delivery: grid cells and detail Edit disabled with same message.
- Add entry / Upload: Enabled when the current calendar month or any filter-selected month is editable — so users can add rows for an open month while the table filter still shows a finalized month. Per-row locks for historical dates are unchanged.
- Bulk upload: Preview rejects rows whose entry dates fall in finalized months (levels 1–4).
7b. Weekly locks (SBS / Non-SBS / ATW)
Separate from month finalize: while a month is still open, SBS / Non-SBS / ATW can auto-lock by entry-date week. Full rules (schedule, manual unlock, upload, bonus cadence): Scorecard § Weekly locking and User-month calculation § Bonus qualification.
8. Quick reference — files
| Area | Path |
|---|---|
| Cycle resolver + June override | api/app/services/bsc_cycle.py |
| Per-cycle config + migration | api/app/services/scorecard_config_store.py |
| Month report + permissions | api/app/services/bsc_month_report_store.py |
| Finalize / sync / reopen | api/app/services/bsc_sync_service.py |
| Entry month lock | api/app/services/scorecard_month_lock.py |
| Task delivery | api/app/domains/scorecard/task_delivery/ (store.py, workdays.py, kpis.py) |
| BSC compute | api/app/domains/bsc/employee_compute.py, kpi_breakdown.py |
| Schedule day codes | api/app/domains/scorecard/schedules/day_codes.py |
| Config UI | web/src/pages/scorecard/configurations/ |
| Task delivery UI | web/src/pages/scorecard/ScorecardTaskDeliveryPage.tsx, task_delivery_components/ |
| Month lock UI | web/src/pages/scorecard/shared/useScorecardMonthEditAccess.ts |
9. Operator checklist
- Before July — Confirm BSC 2026 cycle exists in Configurations; review metrics split and SBS rubric.
- June 2026 close — Finalize May when ready; finalize June when leadership signs off (BSC 2025 metrics through June).
- After finalize — No routine edits for that month; reopen only if corrections needed, then sync + re-generate final report.
- New fiscal year — Add upcoming cycle in Configurations while status is
upcoming; adjust metrics/goals before July 1.
