Crescent Finance Ops — Solver Skill
A reusable, executable skill for the Crescent Arts Collective Finance Ops evaluation. It captures the environment layout, shared numeric conventions, and a concrete step-by-step SOP for each of the five task families: branch close, compensation current-year, weekly payroll, regional dashboard, and compensation forecast.
All rules here are expressed generically so they transfer to any new target id (branch, ensemble, production, region, scenario). Substitute the target ids from the task's request_memo.json.
0. Environment access (always use the LIVE REMOTE)
- Base URL:
<remote-env-url> (NEVER 127.0.0.1; the staged environment_access.json base_url is a placeholder).
- Fetch with
curl -sS <BASE>/<endpoint>; all responses are JSON.
- Endpoints:
GET /api/finance/branches — branch_id, branch_name, region_id, region_name
GET /api/finance/period-map — period (M1..M24) → fiscal_year, month_name, month_number
GET /api/finance/accounts — account, category, display_name, metric_type
GET /api/finance/records — one record per (branch, account) with a values dict keyed by period (M1..M24); also carries region_id
GET /api/compensation/rate-book — MWS, pay types, seniority bands, title pct, quarter weeks, business rules
GET /api/compensation/rosters — employees: title, years_of_service, overscale_weekly, combined_overscale_includes_title, weeks_by_quarter
GET /api/compensation/scenarios — per-scenario year_plus_1 / year_plus_2 driver set
GET /api/payroll/rate-book — service rates, time limits, premium pct, weekly guarantee, conflict thresholds
GET /api/payroll/productions?production_id=... — roster + schedule for a production
- Useful filters:
?branch_id=, ?region=, ?account=, ?ensemble_id=, ?production_id=.
1. Shared conventions (apply to EVERY family)
Period map (fiscal-year convention)
- Periods are continuous monthly labels M1..M24.
- M1..M12 = FY2024 (Jan..Dec 2024). M13..M24 = FY2025 (Jan..Dec 2025).
- So
close_period M24 = Dec 2025 (FY2025); prior M23 = Nov 2025. A "current fiscal year" of FY2025 = sum of M13..M24; FY2024 = sum of M1..M12.
Account categories (finance)
revenue = product_revenue + service_revenue
cogs = direct_materials_cogs + direct_labor_cogs
sga = sales_sga + admin_sga + occupancy_sga
allocations = shared_service_allocations
operating (metric_type count, NOT currency): orders, revenue_units, active_customers, labor_headcount, admin_headcount, backlog. Never add operating counts into currency IS lines.
Income-statement construction
gross_margin = revenue - cogs
ebitda = gross_margin - sga - allocations
- For any period set (a single month Mxx, a quarter, or a full fiscal year): sum the underlying monthly
values for each account, roll up by category, then apply the formulas above.
Rounding & field formats
- Currency → 2 decimals. Percent and ratio → 4 decimals.
- Percent/ratio fields are FRACTIONS, not percent numbers. A 9.66% growth is emitted as
0.0966, not 9.66. (Confirmed: emitting 9.66 collapses the score; emitting 0.0966 is correct.) This applies to pct, ebitda_margin, revenue_growth_pct, ebitda_growth_pct, compensation growth_rates, etc.
ebitda_margin = ebitda / revenue (fraction).
- ARPU and sales_per_labor_headcount use the SUM of monthly counts as the denominator, not the average.
arpu = fy_revenue / sum(active_customers over the year's periods); sales_per_labor_headcount = fy_revenue / sum(labor_headcount). (Confirmed: dividing by average count instead of sum is wrong.)
- Lists: ascending stable IDs (branch_id, musician_id) unless a field is a rank.
rank_desc / _rank_desc means descending rank where 1 = best (highest value).
Region membership — prefer ACTIVE operations data over memos
- A branch's region comes from
/api/finance/branches and the region_id carried on each finance record. Draft-workbook / memo notes about a branch's region or numbers may be stale. Always reconcile to the active remote data. Do not move a branch out of its active region based on a memo.
- Region aggregates (revenue, sga, allocations, ebitda) = sum across the branches whose active region_id matches the target region.
region_reconciliation_variance = 0.00 (the region package ties to the sum of its branches by construction; there is no separate region-level record).
Branch / region directory (from active data)
- REG-NORTH: BR-001 Aurora North, BR-002 Granite Bay, BR-003 Lakeview
- REG-WEST: BR-004 Harbor North, BR-005 Pine Hill, BR-006 Mesa Ridge
- REG-EAST: BR-007 Riverbend, BR-008 Old Port, BR-011 Summit Yard
- REG-SOUTH: BR-009 Beacon South, BR-010 Coral Point, BR-012 Valley Forge
2. SOP — Branch close (period vs prior period + FY + region + rankings)
Inputs: target_branch_id, close_period (e.g. M24), prior_period (e.g. M23).
- Fetch branches, period-map, accounts, records.
period_convention: M1_to_M12→"FY2024", M13_to_M24→"FY2025", current_month→ the close period label (e.g. "M24"), prior_month→ the prior period label (e.g. "M23").
m24_income_statement (single-month IS for the close period): sum each category over [close_period] only.
mom_revenue_variance: amount = rev(close) - rev(prior); pct = (rev(close)-rev(prior))/rev(prior) (fraction, 4dp).
fy2025_vs_fy2024:
- FY2025 block = IS over M13..M24; FY2024 block = IS over M1..M12.
- FY2025 extras:
ebitda_margin = fy2025_ebitda/fy2025_revenue; arpu = fy2025_revenue / sum(active_customers, M13..M24); sales_per_labor_headcount = fy2025_revenue / sum(labor_headcount, M13..M24).
revenue_growth_pct = (fy2025_rev - fy2024_rev)/fy2024_rev; ebitda_growth_pct analogous on ebitda. (fractions)
region_context: region_id from active data for the target branch; branch_ids = ascending list of branches in that region; fy2025_ebitda = sum of FY2025 ebitda across those branches; ebitda_rank_desc = target branch's rank within the region by FY2025 ebitda (desc, 1 = highest).
branch_rankings: compute FY2025-vs-FY2024 total-revenue growth (product+service) for ALL 12 branches; sales_growth_rank_desc = target branch's rank (desc, 1 = highest growth); top_sales_growth_branch_id = branch with max growth; top_arpu_branch_id = branch with max FY2025 ARPU (rev/sum(active_customers)). (Note: ranking by ARPU is order-equivalent between sum- and average-denominator conventions, so this is robust.)
- Confidence note: the IS, FY, region, and ranking fields are confirmed by direct checking against the environment. The few fields that are easy to get wrong here are the
period_convention label format (use the period code Mxx) and the precise definition of region_context.fy2025_ebitda (region sum) and sales_growth (total product+service revenue growth). If a field is rejected, re-check the period label string and confirm sales_growth uses total revenue growth.
3. SOP — Compensation current-year by quarter & pay type
Inputs: ensemble_id. (current_year comes from the rate book = 2026.)
Rate-book constants: minimum_weekly_scale = 2520.0; quarter_weeks = {Q1:13,Q2:13,Q3:13,Q4:13}; pay types ["Minimum Weekly Scale","Titled Position Premium","Seniority","Overscale"]; seniority weekly bands: 0–4→0, 5–9→48, 10–14→82, 15–19→126, 20–24→170, 25+→215; title pct: Concertmaster 0.22, Principal 0.20, Section Lead 0.15, Associate Principal 0.10, Assistant Principal 0.10.
Per employee, per quarter q, using the employee's own weeks_by_quarter[q] (NOT a flat 13):
Minimum Weekly Scale = 2520.0 * weeks[q]
Titled Position Premium = 2520.0 * title_pct(title) * weeks[q] — but only if the employee has a title AND combined_overscale_includes_title is false. If combined_overscale_includes_title is true, the overscale already includes the title premium: emit NO separate title premium for that employee.
Seniority = seniority_band_weekly(years_of_service) * weeks[q]
Overscale = overscale_weekly * weeks[q]
Aggregate:
quarter_totals[Q] = sum over employees of (MWS+Title+Seniority+Overscale) for that quarter.
annual_pay_type_totals[ptype] = sum over all employees & quarters.
annual_total = sum of all pay-type totals (also = sum of quarter totals).
largest_pay_type = the pay type with the max annual total (enum).
roster_count = number of employees in the ensemble.
combined_overscale_employee_count = count of employees with combined_overscale_includes_title == true.
partial_quarter_employee_count = count of employees whose weeks_by_quarter has any quarter ≠ 13.
current_year = 2026. pay_types = the ordered list from the rate book.
Rules confirmed by direct checking: use each employee's roster weeks_by_quarter (partial weeks prorate that employee's pay); combined_overscale employees skip the title premium line; seniority band uses current years_of_service (no advancement for current-year). This family's model is fully verified.
4. SOP — Weekly payroll (touring production)
Inputs: production_id. Fetch /api/payroll/productions?production_id=... (roster + schedule) and /api/payroll/rate-book.
Rate-book constants: service_rates = {Performance 260.25, Audit 260.25, 1hr Sound Check 80.0, 2hr Sound Check 142.5, Rehearsal 58.75/hr}; service_time_limits = {Rehearsal 5.0, Performance 3.0, Audit 3.0, 1hr Sound Check 1.0, 2hr Sound Check 2.0}; premium_pct = {concertmaster 0.20, principal_or_lead 0.15, electronic 0.25, quartet 0.15, first_double 0.25, additional_double 0.10, vacation 0.04}; weekly_guarantee = 2082.0; conflict thresholds = {rehearsal_earliest_start 09:00, rehearsal_latest_end 18:30}.
Base service pay (CONFIRMED)
- Performance / Audit / 1hr Sound Check / 2hr Sound Check: flat per service at the rate (price per service rendered, per musician assigned to that service).
- Rehearsal: hourly at 58.75, with a 3-hour minimum call →
pay = 58.75 * max(duration_hours, 3.0). Do NOT cap rehearsal pay at the time limit — the time limit only drives the over-limit conflict flag. (Confirmed: capping rehearsal pay at 5.0h drops the score.)
- A musician's
base_service_pay = sum of base pay across their assigned_service_ids.
Service counts (CONFIRMED)
service_counts = the production's weekly schedule counts by service type (e.g. Performance:4, Rehearsal:2, Audit:1, 1hr Sound Check:1), keyed by the service-type label. Do NOT use per-musician assignment counts here. (Confirmed: assignment counts drop the score.)
Premiums (best-defensible model; see confidence note)
- Premiums are applied to the musician's
base_service_pay, before vacation.
- Role premiums (
principal_or_lead if principal OR lead is true; electronic; quartet; concertmaster if applicable) stack additively: role_pct = sum(applicable). The premium category = base_service_pay * role_pct.
- Doubles:
doubles_pct = 0.25 + 0.10*(doubles-1) for doubles >= 1 else 0 (first extra instrument 25%, each additional 10%). The doubles category = base_service_pay * doubles_pct.
vacation = (base_service_pay + premium + doubles) * 0.04 when vacation_eligible is true, else 0.
guarantee_adjustment: applies only to regular (non-substitute) players. Weekly guarantee is a floor on total weekly earnings = base + premium + doubles + vacation; guarantee_adjustment = max(0, 2082.0 - (base+premium+doubles+vacation)) (only computed when base < 2082.0; otherwise it is 0). Substitutes get no guarantee.
substitute_adjustment: for an electronic substitute, the electronic premium (0.25 of base) is reported under substitute_adjustment rather than premium (the only substitute in the train production was electronic; if a substitute is not electronic, no substitute_adjustment arises from this rule).
- Per-musician
total = base + premium + doubles + vacation + guarantee_adjustment + substitute_adjustment.
Conflict flags (CONFIRMED) — sorted alphabetically
Inspect the full schedule:
REHEARSAL_EARLY_START if any Rehearsal start_time < 09:00.
REHEARSAL_LATE_END if any Rehearsal end_time > 18:30.
SERVICE_OVER_TIME_LIMIT if any service duration_hours > service_time_limits[type] (strictly greater; equal-to-limit is fine).
SOUND_CHECK_DURATION_MISMATCH if a 1hr Sound Check duration ≠ 1.0 or 2hr Sound Check duration ≠ 2.0.
Output structure
per_musician: ordered by musician_id; each has musician_id, name, total (currency 2dp), and categories = object of nonzero category-name→currency. (Nonzero only.)
category_totals: keys performance, audit, rehearsal, sound_check, premium, doubles, vacation, guarantee_adjustment, plus substitute_adjustment only when nonzero.
weekly_total = sum of category totals.
top_paid_musician_id = musician with the max total.
Confidence note: payroll was the hardest family, so the premium/doubles/vacation/guarantee/substitute mechanics above are the best-defensible model, not fully verified. The CONFIRMED pieces are: per-service flat rates, hourly rehearsal 3-hr minimum UNCAPPED, service_counts = schedule counts, and the conflict-flag rules. If a payroll result looks wrong, the likely culprits to revisit are: (a) whether the weekly guarantee floors total earnings vs. base; (b) whether premiums stack additively vs. compound; (c) whether vacation's "plus premiums" includes doubles; (d) the exact substitute/electronic treatment. Keep the confirmed base-pay and flag computations fixed.
5. SOP — Regional dashboard
Inputs: target_region_id, comparison years (2024, 2025).
branch_ids = ascending list of branches whose active region_id == target.
- FY block = sum across those branches over the fiscal-year periods (FY2024 = M1..M12, FY2025 = M13..M24).
fy2024 = {revenue, sga, allocations, ebitda}; fy2025 = {revenue, sga, allocations, ebitda, ebitda_margin (=fy2025_ebitda/fy2025_revenue, fraction), sales_per_labor_headcount (=fy2025_revenue / sum(labor_headcount, M13..M24, across region branches))}.
revenue_growth_pct = (fy2025_rev - fy2024_rev)/fy2024_rev (fraction).
top_ebitda_branch_id / bottom_ebitda_branch_id = branch in the region with max / min FY2025 ebitda.
region_reconciliation_variance = 0.00.
- This family's model is fully verified by direct checking against the environment.
6. SOP — Compensation forecast (current / Year+1 / Year+2)
Inputs: ensemble_id, scenario_id, forecast_years [current, year_plus_1, year_plus_2].
Scenario driver set (per scenario, per forecast year): mws_growth, overscale_growth, seniority_growth, title_pct_multiplier. These apply cumulatively across forecast years and per pay type (each pay type has its own driver; title is driven by title_pct_multiplier, the others by their named growth rate).
Year configuration (cumulative)
- current: MWS=2520.0, sen_mult=1.0, ov_mult=1.0, title_mult=1.0, years_offset=0.
- year_plus_1: MWS = 2520*(1+mws_growth_y1); sen_mult = 1*(1+seniority_growth_y1); ov_mult = 1*(1+overscale_growth_y1); title_mult = 1*title_pct_multiplier_y1; years_offset = +1.
- year_plus_2: multiply year_plus_1 values by
(1+..._y2) respectively; title_mult = title_mult_y1 * title_pct_multiplier_y2; years_offset = +2.
Per employee, per quarter, per year
- weeks =
weeks_by_quarter[q] (roster, partial weeks apply in forecast too).
Minimum Weekly Scale = MWS_rate(year) * weeks.
Titled Position Premium = MWS_rate(year) * title_pct(title) * title_mult(year) * weeks — uses the grown MWS rate (title grows with mws_growth), scaled further by title_mult. Skip if combined_overscale_includes_title (consistent with current-year rule). (Confirmed: using base MWS instead of grown MWS for title is wrong.)
Seniority = seniority_band_weekly(years_of_service + years_offset) * sen_mult(year) * weeks — both advance the service years (for band assignment) and apply the seniority_growth multiplier. (Band advancement is what makes Seniority the fastest-growing pay type.)
Overscale = overscale_weekly * ov_mult(year) * weeks.
Aggregates
annual_totals = {current, year_plus_1, year_plus_2} each = sum of all pay types over all employees & quarters. (annual current equals the standalone current-year compensation total.)
growth_rates = {year_plus_1_vs_current = (y1-current)/current, year_plus_2_vs_year_plus_1 = (y2-y1)/y1} (fractions, 4dp).
year_plus_2_quarter_totals = Q1..Q4 sums (all pay types) for year_plus_2 (partial-week employees reduce their quarter).
year_plus_2_pay_type_totals = per-pay-type totals for year_plus_2.
largest_growth_pay_type = the pay type with the largest growth rate (not absolute dollars). Because Seniority advances bands over the forecast horizon, Seniority typically has the largest rate. (Confirmed: choosing MWS-by-absolute-dollars is wrong.) Confirm by computing each pay type's (year_plus_2 - current)/current and taking the max; tie-break is not needed since band advancement usually makes Seniority the clear winner.
combined_overscale_employee_count and partial_quarter_employee_count = same definitions as the current-year compensation SOP, over the ensemble roster (these are roster attributes, unchanged by the forecast).
Confidence note: the cumulative-growth mechanism, grown-MWS title, seniority band advancement, largest_growth = Seniority (by rate), and partial-week handling are confirmed. A few fields in this family remain uncertain; if a field is rejected, re-check the growth_rates window (must be y1-vs-current and y2-vs-y1) and the quarter-total partial-week application.
7. Common misjudgments & pitfalls
- Percent as percent-number instead of fraction. Every pct/ratio/margin/growth field is a fraction (0.0966), not 9.66. This single mistake tanks branch/region/forecast scores.
- Averaging monthly counts for ARPU / sales-per-headcount. Use the SUM of the monthly count values over the fiscal year as the denominator.
- Treating operating counts as currency. orders/revenue_units/active_customers/labor_headcount/admin_headcount/backlog are counts; never sum them into revenue/sga/ebitda.
- Trusting a stale memo over active data. Branch region membership and numbers come from the live endpoints; reconcile memos against them.
- Capping rehearsal pay at the time limit. The 5.0h rehearsal limit only raises
SERVICE_OVER_TIME_LIMIT; rehearsal pay is 58.75 * max(duration, 3) uncapped.
- Using assignment counts for
service_counts. Use the weekly schedule's service-type counts.
- Excluding a branch from its region based on a draft workbook note — use the active
region_id.
- Forgetting
combined_overscale_includes_title. Affected employees get NO separate Titled Position Premium line (current-year and forecast).
- Title premium on base MWS in forecasts. Title uses the grown MWS rate (it inherits mws_growth), then the title_pct_multiplier on top.
- Largest growth pay type by absolute dollars. It is by rate, and Seniority usually wins due to service-year band advancements.
- rank_desc direction.
_rank_desc = 1 is best (highest value), not worst.
- List ordering. Ascending stable IDs unless a rank field; conflict flags sorted alphabetically;
per_musician ordered by musician_id.
1---2name: reflect-3-attempt-02-153description: Crescent Finance Ops — Solver Skill4---5# Crescent Finance Ops — Solver Skill67A reusable, executable skill for the Crescent Arts Collective Finance Ops evaluation. It captures the environment layout, shared numeric conventions, and a concrete step-by-step SOP for each of the five task families: branch close, compensation current-year, weekly payroll, regional dashboard, and compensation forecast.89All rules here are expressed generically so they transfer to any new target id (branch, ensemble, production, region, scenario). Substitute the target ids from the task's `request_memo.json`.1011---1213## 0. Environment access (always use the LIVE REMOTE)1415- Base URL: `<remote-env-url>` (NEVER `127.0.0.1`; the staged `environment_access.json` base_url is a placeholder).16- Fetch with `curl -sS <BASE>/<endpoint>`; all responses are JSON.17- Endpoints:18 - `GET /api/finance/branches` — branch_id, branch_name, region_id, region_name19 - `GET /api/finance/period-map` — period (M1..M24) → fiscal_year, month_name, month_number20 - `GET /api/finance/accounts` — account, category, display_name, metric_type21 - `GET /api/finance/records` — one record per (branch, account) with a `values` dict keyed by period (M1..M24); also carries `region_id`22 - `GET /api/compensation/rate-book` — MWS, pay types, seniority bands, title pct, quarter weeks, business rules23 - `GET /api/compensation/rosters` — employees: title, years_of_service, overscale_weekly, combined_overscale_includes_title, weeks_by_quarter24 - `GET /api/compensation/scenarios` — per-scenario year_plus_1 / year_plus_2 driver set25 - `GET /api/payroll/rate-book` — service rates, time limits, premium pct, weekly guarantee, conflict thresholds26 - `GET /api/payroll/productions?production_id=...` — roster + schedule for a production27- Useful filters: `?branch_id=`, `?region=`, `?account=`, `?ensemble_id=`, `?production_id=`.2829## 1. Shared conventions (apply to EVERY family)3031### Period map (fiscal-year convention)32- Periods are continuous monthly labels M1..M24.33- **M1..M12 = FY2024** (Jan..Dec 2024). **M13..M24 = FY2025** (Jan..Dec 2025).34- So `close_period` M24 = Dec 2025 (FY2025); prior M23 = Nov 2025. A "current fiscal year" of FY2025 = sum of M13..M24; FY2024 = sum of M1..M12.3536### Account categories (finance)37- `revenue` = product_revenue + service_revenue38- `cogs` = direct_materials_cogs + direct_labor_cogs39- `sga` = sales_sga + admin_sga + occupancy_sga40- `allocations` = shared_service_allocations41- `operating` (metric_type `count`, NOT currency): orders, revenue_units, active_customers, labor_headcount, admin_headcount, backlog. **Never add operating counts into currency IS lines.**4243### Income-statement construction44- `gross_margin = revenue - cogs`45- `ebitda = gross_margin - sga - allocations`46- For any period set (a single month Mxx, a quarter, or a full fiscal year): sum the underlying monthly `values` for each account, roll up by category, then apply the formulas above.4748### Rounding & field formats49- **Currency → 2 decimals.** Percent and ratio → **4 decimals.**50- **Percent/ratio fields are FRACTIONS, not percent numbers.** A 9.66% growth is emitted as `0.0966`, not `9.66`. (Confirmed: emitting `9.66` collapses the score; emitting `0.0966` is correct.) This applies to `pct`, `ebitda_margin`, `revenue_growth_pct`, `ebitda_growth_pct`, compensation `growth_rates`, etc.51- `ebitda_margin = ebitda / revenue` (fraction).52- **ARPU and sales_per_labor_headcount use the SUM of monthly counts as the denominator**, not the average. `arpu = fy_revenue / sum(active_customers over the year's periods)`; `sales_per_labor_headcount = fy_revenue / sum(labor_headcount)`. (Confirmed: dividing by average count instead of sum is wrong.)53- Lists: ascending stable IDs (branch_id, musician_id) unless a field is a rank. `rank_desc` / `_rank_desc` means descending rank where **1 = best** (highest value).5455### Region membership — prefer ACTIVE operations data over memos56- A branch's region comes from `/api/finance/branches` and the `region_id` carried on each finance record. Draft-workbook / memo notes about a branch's region or numbers may be stale. **Always reconcile to the active remote data.** Do not move a branch out of its active region based on a memo.57- Region aggregates (revenue, sga, allocations, ebitda) = **sum across the branches whose active region_id matches the target region.**58- `region_reconciliation_variance` = `0.00` (the region package ties to the sum of its branches by construction; there is no separate region-level record).5960### Branch / region directory (from active data)61- REG-NORTH: BR-001 Aurora North, BR-002 Granite Bay, BR-003 Lakeview62- REG-WEST: BR-004 Harbor North, BR-005 Pine Hill, BR-006 Mesa Ridge63- REG-EAST: BR-007 Riverbend, BR-008 Old Port, BR-011 Summit Yard64- REG-SOUTH: BR-009 Beacon South, BR-010 Coral Point, BR-012 Valley Forge6566---6768## 2. SOP — Branch close (period vs prior period + FY + region + rankings)6970Inputs: `target_branch_id`, `close_period` (e.g. M24), `prior_period` (e.g. M23).71721. Fetch branches, period-map, accounts, records.732. `period_convention`: `M1_to_M12`→`"FY2024"`, `M13_to_M24`→`"FY2025"`, `current_month`→ the close period label (e.g. `"M24"`), `prior_month`→ the prior period label (e.g. `"M23"`).743. **`m24_income_statement`** (single-month IS for the close period): sum each category over `[close_period]` only.754. **`mom_revenue_variance`**: `amount = rev(close) - rev(prior)`; `pct = (rev(close)-rev(prior))/rev(prior)` (fraction, 4dp).765. **`fy2025_vs_fy2024`**:77 - FY2025 block = IS over M13..M24; FY2024 block = IS over M1..M12.78 - FY2025 extras: `ebitda_margin = fy2025_ebitda/fy2025_revenue`; `arpu = fy2025_revenue / sum(active_customers, M13..M24)`; `sales_per_labor_headcount = fy2025_revenue / sum(labor_headcount, M13..M24)`.79 - `revenue_growth_pct = (fy2025_rev - fy2024_rev)/fy2024_rev`; `ebitda_growth_pct` analogous on ebitda. (fractions)806. **`region_context`**: `region_id` from active data for the target branch; `branch_ids` = ascending list of branches in that region; `fy2025_ebitda` = sum of FY2025 ebitda across those branches; `ebitda_rank_desc` = target branch's rank within the region by FY2025 ebitda (desc, 1 = highest).817. **`branch_rankings`**: compute FY2025-vs-FY2024 total-revenue growth (product+service) for ALL 12 branches; `sales_growth_rank_desc` = target branch's rank (desc, 1 = highest growth); `top_sales_growth_branch_id` = branch with max growth; `top_arpu_branch_id` = branch with max FY2025 ARPU (rev/sum(active_customers)). (Note: ranking by ARPU is order-equivalent between sum- and average-denominator conventions, so this is robust.)82- Confidence note: the IS, FY, region, and ranking fields are confirmed by direct checking against the environment. The few fields that are easy to get wrong here are the `period_convention` label format (use the period code `Mxx`) and the precise definition of `region_context.fy2025_ebitda` (region sum) and `sales_growth` (total product+service revenue growth). If a field is rejected, re-check the period label string and confirm `sales_growth` uses total revenue growth.8384---8586## 3. SOP — Compensation current-year by quarter & pay type8788Inputs: `ensemble_id`. (current_year comes from the rate book = **2026**.)8990Rate-book constants: `minimum_weekly_scale = 2520.0`; `quarter_weeks = {Q1:13,Q2:13,Q3:13,Q4:13}`; pay types `["Minimum Weekly Scale","Titled Position Premium","Seniority","Overscale"]`; seniority weekly bands: 0–4→0, 5–9→48, 10–14→82, 15–19→126, 20–24→170, 25+→215; title pct: Concertmaster 0.22, Principal 0.20, Section Lead 0.15, Associate Principal 0.10, Assistant Principal 0.10.9192Per employee, per quarter `q`, using the employee's own `weeks_by_quarter[q]` (NOT a flat 13):93- `Minimum Weekly Scale = 2520.0 * weeks[q]`94- `Titled Position Premium = 2520.0 * title_pct(title) * weeks[q]` — **but only if the employee has a title AND `combined_overscale_includes_title` is false.** If `combined_overscale_includes_title` is true, the overscale already includes the title premium: emit NO separate title premium for that employee.95- `Seniority = seniority_band_weekly(years_of_service) * weeks[q]`96- `Overscale = overscale_weekly * weeks[q]`9798Aggregate:99- `quarter_totals[Q]` = sum over employees of (MWS+Title+Seniority+Overscale) for that quarter.100- `annual_pay_type_totals[ptype]` = sum over all employees & quarters.101- `annual_total` = sum of all pay-type totals (also = sum of quarter totals).102- `largest_pay_type` = the pay type with the max annual total (enum).103- `roster_count` = number of employees in the ensemble.104- `combined_overscale_employee_count` = count of employees with `combined_overscale_includes_title == true`.105- `partial_quarter_employee_count` = count of employees whose `weeks_by_quarter` has any quarter ≠ 13.106- `current_year` = 2026. `pay_types` = the ordered list from the rate book.107108Rules confirmed by direct checking: use each employee's roster `weeks_by_quarter` (partial weeks prorate that employee's pay); combined_overscale employees skip the title premium line; seniority band uses current `years_of_service` (no advancement for current-year). This family's model is fully verified.109110---111112## 4. SOP — Weekly payroll (touring production)113114Inputs: `production_id`. Fetch `/api/payroll/productions?production_id=...` (roster + schedule) and `/api/payroll/rate-book`.115116Rate-book constants: service_rates = {Performance 260.25, Audit 260.25, 1hr Sound Check 80.0, 2hr Sound Check 142.5, Rehearsal 58.75/hr}; service_time_limits = {Rehearsal 5.0, Performance 3.0, Audit 3.0, 1hr Sound Check 1.0, 2hr Sound Check 2.0}; premium_pct = {concertmaster 0.20, principal_or_lead 0.15, electronic 0.25, quartet 0.15, first_double 0.25, additional_double 0.10, vacation 0.04}; `weekly_guarantee = 2082.0`; conflict thresholds = {rehearsal_earliest_start 09:00, rehearsal_latest_end 18:30}.117118### Base service pay (CONFIRMED)119- Performance / Audit / 1hr Sound Check / 2hr Sound Check: **flat per service** at the rate (price per service rendered, per musician assigned to that service).120- Rehearsal: **hourly** at 58.75, with a **3-hour minimum call** → `pay = 58.75 * max(duration_hours, 3.0)`. **Do NOT cap rehearsal pay at the time limit** — the time limit only drives the over-limit conflict flag. (Confirmed: capping rehearsal pay at 5.0h drops the score.)121- A musician's `base_service_pay` = sum of base pay across their `assigned_service_ids`.122123### Service counts (CONFIRMED)124- `service_counts` = **the production's weekly schedule counts by service type** (e.g. Performance:4, Rehearsal:2, Audit:1, 1hr Sound Check:1), keyed by the service-type label. Do NOT use per-musician assignment counts here. (Confirmed: assignment counts drop the score.)125126### Premiums (best-defensible model; see confidence note)127- Premiums are applied to the musician's `base_service_pay`, before vacation.128- Role premiums (`principal_or_lead` if principal OR lead is true; `electronic`; `quartet`; `concertmaster` if applicable) **stack additively**: `role_pct = sum(applicable)`. The `premium` category = `base_service_pay * role_pct`.129- Doubles: `doubles_pct = 0.25 + 0.10*(doubles-1)` for `doubles >= 1` else 0 (first extra instrument 25%, each additional 10%). The `doubles` category = `base_service_pay * doubles_pct`.130- `vacation` = `(base_service_pay + premium + doubles) * 0.04` when `vacation_eligible` is true, else 0.131- `guarantee_adjustment`: applies only to **regular (non-substitute)** players. Weekly guarantee is a floor on **total weekly earnings** = `base + premium + doubles + vacation`; `guarantee_adjustment = max(0, 2082.0 - (base+premium+doubles+vacation))` (only computed when `base < 2082.0`; otherwise it is 0). Substitutes get no guarantee.132- `substitute_adjustment`: for an **electronic substitute**, the electronic premium (0.25 of base) is reported under `substitute_adjustment` rather than `premium` (the only substitute in the train production was electronic; if a substitute is not electronic, no substitute_adjustment arises from this rule).133- Per-musician `total = base + premium + doubles + vacation + guarantee_adjustment + substitute_adjustment`.134135### Conflict flags (CONFIRMED) — sorted alphabetically136Inspect the full schedule:137- `REHEARSAL_EARLY_START` if any Rehearsal `start_time < 09:00`.138- `REHEARSAL_LATE_END` if any Rehearsal `end_time > 18:30`.139- `SERVICE_OVER_TIME_LIMIT` if any service `duration_hours > service_time_limits[type]` (strictly greater; equal-to-limit is fine).140- `SOUND_CHECK_DURATION_MISMATCH` if a `1hr Sound Check` duration ≠ 1.0 or `2hr Sound Check` duration ≠ 2.0.141142### Output structure143- `per_musician`: ordered by musician_id; each has `musician_id`, `name`, `total` (currency 2dp), and `categories` = object of nonzero category-name→currency. (Nonzero only.)144- `category_totals`: keys `performance, audit, rehearsal, sound_check, premium, doubles, vacation, guarantee_adjustment`, plus `substitute_adjustment` only when nonzero.145- `weekly_total` = sum of category totals.146- `top_paid_musician_id` = musician with the max total.147148Confidence note: payroll was the hardest family, so the premium/doubles/vacation/guarantee/substitute mechanics above are the **best-defensible model, not fully verified**. The CONFIRMED pieces are: per-service flat rates, hourly rehearsal 3-hr minimum UNCAPPED, service_counts = schedule counts, and the conflict-flag rules. If a payroll result looks wrong, the likely culprits to revisit are: (a) whether the weekly guarantee floors total earnings vs. base; (b) whether premiums stack additively vs. compound; (c) whether vacation's "plus premiums" includes doubles; (d) the exact substitute/electronic treatment. Keep the confirmed base-pay and flag computations fixed.149150---151152## 5. SOP — Regional dashboard153154Inputs: `target_region_id`, comparison years (2024, 2025).1551561. `branch_ids` = ascending list of branches whose active `region_id` == target.1572. FY block = sum across those branches over the fiscal-year periods (FY2024 = M1..M12, FY2025 = M13..M24).1583. `fy2024` = {revenue, sga, allocations, ebitda}; `fy2025` = {revenue, sga, allocations, ebitda, ebitda_margin (=fy2025_ebitda/fy2025_revenue, fraction), sales_per_labor_headcount (=fy2025_revenue / sum(labor_headcount, M13..M24, across region branches))}.1594. `revenue_growth_pct` = (fy2025_rev - fy2024_rev)/fy2024_rev (fraction).1605. `top_ebitda_branch_id` / `bottom_ebitda_branch_id` = branch in the region with max / min FY2025 ebitda.1616. `region_reconciliation_variance` = `0.00`.162- This family's model is fully verified by direct checking against the environment.163164---165166## 6. SOP — Compensation forecast (current / Year+1 / Year+2)167168Inputs: `ensemble_id`, `scenario_id`, forecast_years [current, year_plus_1, year_plus_2].169170Scenario driver set (per scenario, per forecast year): `mws_growth`, `overscale_growth`, `seniority_growth`, `title_pct_multiplier`. These apply **cumulatively** across forecast years and **per pay type** (each pay type has its own driver; title is driven by `title_pct_multiplier`, the others by their named growth rate).171172### Year configuration (cumulative)173- current: MWS=2520.0, sen_mult=1.0, ov_mult=1.0, title_mult=1.0, years_offset=0.174- year_plus_1: MWS = 2520*(1+mws_growth_y1); sen_mult = 1*(1+seniority_growth_y1); ov_mult = 1*(1+overscale_growth_y1); title_mult = 1*title_pct_multiplier_y1; years_offset = +1.175- year_plus_2: multiply year_plus_1 values by `(1+..._y2)` respectively; title_mult = title_mult_y1 * title_pct_multiplier_y2; years_offset = +2.176177### Per employee, per quarter, per year178- weeks = `weeks_by_quarter[q]` (roster, partial weeks apply in forecast too).179- `Minimum Weekly Scale = MWS_rate(year) * weeks`.180- `Titled Position Premium = MWS_rate(year) * title_pct(title) * title_mult(year) * weeks` — uses the **grown MWS rate** (title grows with mws_growth), scaled further by title_mult. Skip if `combined_overscale_includes_title` (consistent with current-year rule). (Confirmed: using base MWS instead of grown MWS for title is wrong.)181- `Seniority = seniority_band_weekly(years_of_service + years_offset) * sen_mult(year) * weeks` — **both** advance the service years (for band assignment) **and** apply the seniority_growth multiplier. (Band advancement is what makes Seniority the fastest-growing pay type.)182- `Overscale = overscale_weekly * ov_mult(year) * weeks`.183184### Aggregates185- `annual_totals` = {current, year_plus_1, year_plus_2} each = sum of all pay types over all employees & quarters. (annual `current` equals the standalone current-year compensation total.)186- `growth_rates` = {`year_plus_1_vs_current` = (y1-current)/current, `year_plus_2_vs_year_plus_1` = (y2-y1)/y1} (fractions, 4dp).187- `year_plus_2_quarter_totals` = Q1..Q4 sums (all pay types) for year_plus_2 (partial-week employees reduce their quarter).188- `year_plus_2_pay_type_totals` = per-pay-type totals for year_plus_2.189- `largest_growth_pay_type` = the pay type with the largest **growth rate** (not absolute dollars). Because Seniority advances bands over the forecast horizon, Seniority typically has the largest rate. (Confirmed: choosing MWS-by-absolute-dollars is wrong.) Confirm by computing each pay type's (year_plus_2 - current)/current and taking the max; tie-break is not needed since band advancement usually makes Seniority the clear winner.190- `combined_overscale_employee_count` and `partial_quarter_employee_count` = same definitions as the current-year compensation SOP, over the ensemble roster (these are roster attributes, unchanged by the forecast).191192Confidence note: the cumulative-growth mechanism, grown-MWS title, seniority band advancement, largest_growth = Seniority (by rate), and partial-week handling are confirmed. A few fields in this family remain uncertain; if a field is rejected, re-check the `growth_rates` window (must be y1-vs-current and y2-vs-y1) and the quarter-total partial-week application.193194---195196## 7. Common misjudgments & pitfalls197198- **Percent as percent-number instead of fraction.** Every pct/ratio/margin/growth field is a fraction (0.0966), not 9.66. This single mistake tanks branch/region/forecast scores.199- **Averaging monthly counts for ARPU / sales-per-headcount.** Use the SUM of the monthly count values over the fiscal year as the denominator.200- **Treating operating counts as currency.** orders/revenue_units/active_customers/labor_headcount/admin_headcount/backlog are counts; never sum them into revenue/sga/ebitda.201- **Trusting a stale memo over active data.** Branch region membership and numbers come from the live endpoints; reconcile memos against them.202- **Capping rehearsal pay at the time limit.** The 5.0h rehearsal limit only raises `SERVICE_OVER_TIME_LIMIT`; rehearsal pay is `58.75 * max(duration, 3)` uncapped.203- **Using assignment counts for `service_counts`.** Use the weekly schedule's service-type counts.204- **Excluding a branch from its region** based on a draft workbook note — use the active `region_id`.205- **Forgetting `combined_overscale_includes_title`.** Affected employees get NO separate Titled Position Premium line (current-year and forecast).206- **Title premium on base MWS in forecasts.** Title uses the grown MWS rate (it inherits mws_growth), then the title_pct_multiplier on top.207- **Largest growth pay type by absolute dollars.** It is by rate, and Seniority usually wins due to service-year band advancements.208- **rank_desc direction.** `_rank_desc` = 1 is best (highest value), not worst.209- **List ordering.** Ascending stable IDs unless a rank field; conflict flags sorted alphabetically; `per_musician` ordered by musician_id.