Crescent Finance Ops Reporting SOP
This group has THREE task families against one read-only HTTP API. Each task gives you
prompt.txt, payloads/request_memo.json, and payloads/answer_template.json. Your job
is to return ONE JSON object that exactly matches the template's keys/shapes.
0. Universal procedure (do this every task)
- Read
answer_template.json first. Its required_top_level_keys and field_types
ARE the contract. Emit exactly those keys, nested exactly as shown, no extras, no omissions.
- Read
request_memo.json for the target IDs (target_branch_id, target_region_id,
ensemble_id, scenario_id, production_id), periods, and focus lists.
- IGNORE the
base_url inside payloads/environment_access.json (it points at a wrong
localhost). Always use the remote base URL: <remote-env-url>.
- Memo notes about "draft workbook / background notes / reconcile against active
operations data" are red herrings/distractors. There is NO draft data in the API. They
mean: trust ONLY the live API data and ignore any narrative figures. Reconciliations tie
to zero (see region task).
- Fetch with
curl -s. All endpoints are GET. Compute in a script, then format.
Rounding & ordering conventions (from the templates)
- Currency: round to 2 decimals. Percent / ratio / growth: round to 4 decimals
(these are DECIMAL fractions, e.g. 9.66% ->
0.0966, NOT 9.66).
- Round each output field once at the end from full-precision intermediates. Do NOT chain
rounding. For aggregate totals computed from many rows, sum the unrounded rows and round
the total once (this can make a total differ by a penny from the sum of the displayed,
pre-rounded per-row figures — that is expected and correct).
- Lists are ordered by ascending stable ID (
branch_id, musician_id, employee_id)
UNLESS a field name says otherwise (e.g. *_rank_desc, top_*, "sorted alphabetically").
conflict_flags: sorted alphabetically. per_musician: ordered by musician_id.
pay_types: emit in the rate book's given order
["Minimum Weekly Scale","Titled Position Premium","Seniority","Overscale"].
1. API map
Base: <remote-env-url>
| Endpoint |
Use |
/api/manifest |
entity index (branch/ensemble/production IDs & names) |
/api/finance/branches |
branch_id -> branch_name, region_id, region_name |
/api/finance/period-map |
period M1..M24 -> fiscal_year, month |
/api/finance/accounts |
account -> category (revenue/cogs/sga/allocations/operating) |
/api/finance/records?branch_id=®ion=&account= |
per-branch monthly values |
/api/compensation/rate-book |
scale, seniority bands, title %, business rules |
/api/compensation/rosters?ensemble_id= |
employee rows for an ensemble |
/api/compensation/scenarios |
forecast growth cases |
/api/payroll/rate-book |
service rates, premiums, limits, guarantee |
/api/payroll/productions?production_id= |
schedule + musician roster (list of 1) |
Finance records: 12 branches x 14 accounts; each record has
values = dict keyed M1..M24. records?branch_id=X returns that branch's 14 rows.
2. Period / fiscal-year convention (finance)
From /api/finance/period-map: M1..M12 = FY2024 (Jan..Dec), M13..M24 = FY2025 (Jan..Dec).
So period_convention = {"M1_to_M12":"FY2024","M13_to_M24":"FY2025","current_month":<close_period>, "prior_month":<prior_period>}. Use the raw M-label (e.g. "M24", "M23") as the period label
string — that is the stable identifier the data uses. M24 is the latest close (Dec FY2025).
3. Income statement definitions (finance)
Account categories (/api/finance/accounts):
- revenue =
product_revenue + service_revenue
- cogs =
direct_materials_cogs + direct_labor_cogs
- sga =
sales_sga + admin_sga + occupancy_sga
- allocations =
shared_service_allocations
- operating COUNT accounts (NOT dollars):
orders, revenue_units, active_customers,
labor_headcount, admin_headcount, backlog.
Line items:
gross_margin = revenue - cogs
ebitda = gross_margin - sga - allocations (= revenue - cogs - sga - allocations)
ebitda_margin = ebitda / revenue (4 dp)
A monthly IS sums the single period; an FY IS sums all 12 periods of that fiscal year
(FY2024 = M1..M12, FY2025 = M13..M24). EXCLUDE operating-count accounts from all dollar lines.
Derived ratios
mom_revenue_variance.amount = rev(cur) - rev(prior); .pct = amount / rev(prior) (4 dp).
revenue_growth_pct = (rev_fy2025 - rev_fy2024) / rev_fy2024; same shape for ebitda_growth_pct.
arpu = FY revenue / average monthly active_customers = revenue / (sum of 12 monthly
active_customers / 12). This yields an ANNUAL revenue-per-customer figure. (Do NOT divide by the
raw 12-month sum.)
sales_per_labor_headcount = FY revenue / average monthly labor_headcount
(= revenue / (sum of 12 monthly labor_headcount / 12)). Annual revenue per FTE.
NOTE: average-headcount denominator is the working convention; this is the single biggest
ambiguity in the finance tasks — if a result looks off by a ~12x factor, reconsider sum vs
average. Apply the SAME denominator convention at branch and region level.
4. Regional rollup & rankings (finance)
- A region's figures = sum of its member branches (
/api/finance/branches gives
region_id; records?region=REG-X returns only those branches). REG-WEST = BR-004/005/006,
REG-NORTH = BR-001/002/003, REG-EAST = BR-007/008/011, REG-SOUTH = BR-009/010/012.
branch_ids = ascending list of the region's branch IDs.
- Region
ebitda_margin = region_ebitda / region_revenue; region sales_per_labor_headcount
= region_revenue / (region average monthly labor_headcount).
region_reconciliation_variance = 0.0. There is no separate region-level record; the
rollup ties exactly to the active branch data. (This is what "tie back to active operating
data" means.)
*_rank_desc = 1-based rank, DESCENDING by the metric (1 = largest).
region_context.ebitda_rank_desc: rank of the target region's FY2025 EBITDA among ALL
regions (compute every region's FY2025 EBITDA, sort desc, find target's position).
branch_rankings.sales_growth_rank_desc: rank of the TARGET branch's FY2025-vs-FY2024
revenue growth % among ALL 12 branches, descending.
top_sales_growth_branch_id: branch with highest FY revenue growth % across all 12.
top_arpu_branch_id: branch with highest FY2025 ARPU across all 12 (same ARPU formula).
top_ebitda_branch_id / bottom_ebitda_branch_id (region task): highest / lowest FY2025
EBITDA among the region's branches only.
- Ties: break by ascending branch_id (stable). Rankings span the relevant universe (all 12
branches for branch rankings; all regions for region rank; in-region only for region task's
top/bottom branch).
5. CBA compensation (current-year summary AND forecast)
Data: /api/compensation/rate-book, rosters?ensemble_id=, scenarios.
Rate book: minimum_weekly_scale (MWS, e.g. 2520.0); pay_types (4, ordered);
seniority_weekly bands [{min_years,max_years,weekly_amount}] (max_years null = open-ended);
title_premium_pct (e.g. Concertmaster .22, Principal .20, Section Lead .15, Assistant/Associate
Principal .10); quarter_weeks default 13/quarter; current_year (e.g. 2026).
Each roster row: years_of_service, title (or null), overscale_weekly,
combined_overscale_includes_title (bool), weeks_by_quarter {Q1..Q4}, notes.
Per-employee, per-quarter weekly components (multiply each by that quarter's weeks)
For each quarter q with weeks = weeks_by_quarter[q]:
- Minimum Weekly Scale =
MWS * weeks
- Titled Position Premium =
MWS * title_premium_pct[title] * weeks
— but 0 if title is null OR combined_overscale_includes_title is true (when combined,
the title premium is already inside overscale; do NOT add it separately).
- Seniority =
seniority_weekly(years_of_service) * weeks (band lookup by years).
- Overscale =
overscale_weekly * weeks.
Quarter total = sum of the 4 components over all employees for that quarter.
Pay-type annual total = sum across 4 quarters and all employees.
annual_total = sum of all pay-type annual totals (= sum of 4 quarter totals).
Use actual weeks_by_quarter, not a fixed 13, so partial-quarter employees (a quarter < 13)
are pro-rated automatically.
Review counts (both comp tasks)
roster_count = number of roster rows for the ensemble.
combined_overscale_employee_count = rows with combined_overscale_includes_title == true.
partial_quarter_employee_count = rows where ANY quarter's weeks != 13.
largest_pay_type / largest_growth_pay_type = enum from the 4 pay types (see below).
largest_pay_type (current-year task)
Pay type with the largest ANNUAL dollar total. (MWS dominates and is typically the answer.)
Forecast task (scenarios)
scenario_id selects a case in /api/compensation/scenarios, each with year_plus_1 and
year_plus_2 blocks: mws_growth, overscale_growth, seniority_growth,
title_pct_multiplier. Business rules (in the rate book): for Year+1 add 1 year of service,
for Year+2 add 2 years of service BEFORE assigning seniority bands.
Compute three annual totals:
- current: base rate book, no growth, no added years.
- year_plus_1:
MWS*(1+mws_growth_y1); title premium pct scaled by title_pct_multiplier_y1;
seniority band looked up at yos+1 then scaled by (1+seniority_growth_y1); overscale scaled
by (1+overscale_growth_y1).
- year_plus_2: growth COMPOUNDS off Year+1:
MWS*(1+g_y1)*(1+g_y2); title mult
tm_y1*tm_y2; seniority at yos+2 scaled by (1+sg_y1)*(1+sg_y2); overscale scaled by
(1+og_y1)*(1+og_y2).
growth_rates.year_plus_1_vs_current = (y1-cur)/cur; year_plus_2_vs_year_plus_1 = (y2-y1)/y1
(4 dp).
year_plus_2_quarter_totals / year_plus_2_pay_type_totals: the Year+2 breakdown.
largest_growth_pay_type: pay type with the largest absolute dollar increase from current
to Year+2 (MWS typically wins because it is the largest base). Note seniority can have the
largest PERCENT growth due to band shifts + seniority_growth, but the field tracks absolute
dollar growth.
Ambiguity to flag: seniority_growth is applied as a rate multiplier ON TOP of the band shift
from added years. Both effects compound. (The +years-of-service rule is mandatory; the
seniority_growth multiplier is the literal reading of the scenario field.)
6. Theatre weekly payroll
Data: /api/payroll/rate-book, productions?production_id= (returns a 1-element list; take [0]).
Production has schedule (services) and roster (musicians).
Rate book: service_rates (Performance/Audit per service; Rehearsal per hour; 1hr/2hr Sound
Check per service); service_time_limits (hours); premium_pct
(principal_or_lead .15, quartet .15, electronic .25, concertmaster .20, first_double .25,
additional_double .10, vacation .04); weekly_guarantee; conflict_thresholds
(rehearsal_earliest_start "09:00", rehearsal_latest_end "18:30").
Schedule service: service_id, service_type, date, start_time, end_time, duration_hours.
Roster musician: musician_id, name, instrument, assigned_service_ids[], doubles (int), electronic, lead, principal, quartet, substitute, vacation_eligible.
Per-musician pay (sum over the musician's assigned_service_ids)
- Base service pay per assigned service:
- Rehearsal:
Rehearsal_rate * max(duration_hours, 3.0) (3-hour minimum call, hourly).
- Performance / Audit / 1hr Sound Check / 2hr Sound Check: the flat per-service rate.
- Accumulate into category buckets: Performance->
performance, Audit->audit,
Rehearsal->rehearsal, 1hr/2hr Sound Check->sound_check.
base_total = sum of all base service pay for the musician.
- Premiums (
premium category) = base_total * (sum of applicable pct):
principal_or_lead if principal or lead; quartet if quartet; electronic if
electronic; concertmaster only if a concertmaster flag is present (none in current data,
so usually not applied). Premiums apply to base service pay, before vacation.
- Doubles (
doubles category, separate from premium): if doubles >= 1,
base_total * first_double for the first, plus base_total * additional_double * (doubles - 1) for each additional. (e.g. 2 doubles = 35% of base_total.)
- Vacation (
vacation): if vacation_eligible,
0.04 * (base_total + premiums + doubles) (4% of base service pay plus premiums).
- Guarantee adjustment (
guarantee_adjustment): ONLY for guaranteed regular players =
non-substitute musicians (substitute == false). If base_total < weekly_guarantee,
add weekly_guarantee - base_total. (No explicit guaranteed field exists; "regular" = not
a substitute.) Compare against base_total (base service pay), not the premium-inclusive total.
substitute_adjustment: no substitute rate is defined in the rate book, so it is 0 and
typically omitted (template says "currency when applicable"). Substitutes simply do NOT get
the weekly guarantee.
Musician total = base_total + premiums + doubles + vacation + guarantee_adjustment.
weekly_total = sum of all musician totals. top_paid_musician_id = musician with max total
(break ties by ascending musician_id).
per_musician[i].categories = only the NONZERO category buckets for that musician, rounded 2 dp.
category_totals = sum across musicians per category; compute from UNROUNDED values and round
once (so it may differ a penny from summing the displayed per-musician figures — that is fine).
service_counts
Schedule-level count of each service_type across the production's UNIQUE services (count the
schedule rows by type, e.g. 4 Performance, 2 Rehearsal, 1 Audit, 1 "1hr Sound Check").
(Possible alternative reading is assignment-level counts summed over musicians; prefer the
schedule-level "what happened this week" reading for a weekly payroll package, but sanity-check
against the memo wording.)
conflict_flags (enum, sorted alphabetically; emit only those that trigger)
REHEARSAL_EARLY_START: a Rehearsal start_time earlier than rehearsal_earliest_start (09:00).
REHEARSAL_LATE_END: a Rehearsal end_time later than rehearsal_latest_end (18:30).
SERVICE_OVER_TIME_LIMIT: any service whose duration_hours exceeds its
service_time_limits entry (Rehearsal 5.0, Performance 3.0, Audit 3.0, 1hr SC 1.0, 2hr SC 2.0).
SOUND_CHECK_DURATION_MISMATCH: a "1hr Sound Check" whose duration != 1.0, or a "2hr Sound
Check" whose duration != 2.0.
Compare times by converting "HH:MM" to minutes. A flag fires if ANY service triggers it; emit
the deduplicated, alphabetically sorted set.
7. Step-by-step SOP for an unseen task in this group
- Open
answer_template.json; list required keys and exact field shapes/rounding.
- Read
request_memo.json for target IDs/periods/scenario; ignore distractor memo notes.
- Identify the domain (finance branch close / regional / comp current / comp forecast / payroll)
by the template's top-level keys.
curl the relevant endpoints (always the remote base URL).
- Compute with the formulas above in a script. Keep full precision internally.
- Round at the very end: currency 2 dp, percent/ratio/growth 4 dp. Order lists by ascending
stable ID unless a rank/sort field says otherwise.
- Emit exactly the template keys (no extras), validate JSON, return the single object.
Self-check before returning
- Finance:
gross_margin == revenue - cogs; ebitda == gross_margin - sga - allocations;
region figures == sum of member branches; region_reconciliation_variance == 0.0; ranks are
1-based descending.
- Comp: quarter totals sum to annual_total; pay-type totals sum to annual_total; partial-quarter
count matches rows with a non-13 quarter; title premium suppressed when combined flag true.
- Payroll: sum of per-musician totals == weekly_total; category_totals reconcile to per-musician
category sums (within rounding); guarantee only on non-substitutes below the guarantee; flags
deduplicated and alphabetically sorted; per_musician ordered by musician_id.
1---2name: finance-ops-reporting-23description: SOP for Crescent Finance Ops branch reporting, CBA compensation forecasting, and theatre payroll control tasks against the remote Finance Ops API.4---56# Crescent Finance Ops Reporting SOP78This group has THREE task families against one read-only HTTP API. Each task gives you9`prompt.txt`, `payloads/request_memo.json`, and `payloads/answer_template.json`. Your job10is to return ONE JSON object that exactly matches the template's keys/shapes.1112## 0. Universal procedure (do this every task)13141. **Read `answer_template.json` first.** Its `required_top_level_keys` and `field_types`15 ARE the contract. Emit exactly those keys, nested exactly as shown, no extras, no omissions.162. **Read `request_memo.json`** for the target IDs (`target_branch_id`, `target_region_id`,17 `ensemble_id`, `scenario_id`, `production_id`), periods, and focus lists.183. **IGNORE the `base_url` inside `payloads/environment_access.json`** (it points at a wrong19 localhost). Always use the remote base URL: `<remote-env-url>`.204. **Memo notes about "draft workbook / background notes / reconcile against active21 operations data" are red herrings/distractors.** There is NO draft data in the API. They22 mean: trust ONLY the live API data and ignore any narrative figures. Reconciliations tie23 to zero (see region task).245. Fetch with `curl -s`. All endpoints are GET. Compute in a script, then format.2526### Rounding & ordering conventions (from the templates)27- **Currency**: round to **2 decimals**. **Percent / ratio / growth**: round to **4 decimals**28 (these are DECIMAL fractions, e.g. 9.66% -> `0.0966`, NOT `9.66`).29- Round each output field **once at the end** from full-precision intermediates. Do NOT chain30 rounding. For aggregate totals computed from many rows, sum the **unrounded** rows and round31 the total once (this can make a total differ by a penny from the sum of the displayed,32 pre-rounded per-row figures — that is expected and correct).33- **Lists** are ordered by ascending stable ID (`branch_id`, `musician_id`, employee_id)34 UNLESS a field name says otherwise (e.g. `*_rank_desc`, `top_*`, "sorted alphabetically").35- `conflict_flags`: sorted alphabetically. `per_musician`: ordered by `musician_id`.36- `pay_types`: emit in the rate book's given order37 `["Minimum Weekly Scale","Titled Position Premium","Seniority","Overscale"]`.3839## 1. API map4041Base: `<remote-env-url>`4243| Endpoint | Use |44|---|---|45| `/api/manifest` | entity index (branch/ensemble/production IDs & names) |46| `/api/finance/branches` | branch_id -> branch_name, region_id, region_name |47| `/api/finance/period-map` | period `M1..M24` -> fiscal_year, month |48| `/api/finance/accounts` | account -> category (revenue/cogs/sga/allocations/operating) |49| `/api/finance/records?branch_id=®ion=&account=` | per-branch monthly values |50| `/api/compensation/rate-book` | scale, seniority bands, title %, business rules |51| `/api/compensation/rosters?ensemble_id=` | employee rows for an ensemble |52| `/api/compensation/scenarios` | forecast growth cases |53| `/api/payroll/rate-book` | service rates, premiums, limits, guarantee |54| `/api/payroll/productions?production_id=` | schedule + musician roster (list of 1) |5556Finance records: 12 branches x 14 accounts; each record has57`values` = dict keyed `M1..M24`. `records?branch_id=X` returns that branch's 14 rows.5859## 2. Period / fiscal-year convention (finance)6061From `/api/finance/period-map`: **M1..M12 = FY2024 (Jan..Dec), M13..M24 = FY2025 (Jan..Dec).**62So `period_convention` = `{"M1_to_M12":"FY2024","M13_to_M24":"FY2025","current_month":<close_period>,63"prior_month":<prior_period>}`. Use the raw M-label (e.g. `"M24"`, `"M23"`) as the period label64string — that is the stable identifier the data uses. M24 is the latest close (Dec FY2025).6566## 3. Income statement definitions (finance)6768Account categories (`/api/finance/accounts`):69- **revenue** = `product_revenue` + `service_revenue`70- **cogs** = `direct_materials_cogs` + `direct_labor_cogs`71- **sga** = `sales_sga` + `admin_sga` + `occupancy_sga`72- **allocations** = `shared_service_allocations`73- operating COUNT accounts (NOT dollars): `orders`, `revenue_units`, `active_customers`,74 `labor_headcount`, `admin_headcount`, `backlog`.7576Line items:77- `gross_margin = revenue - cogs`78- `ebitda = gross_margin - sga - allocations` (= revenue - cogs - sga - allocations)79- `ebitda_margin = ebitda / revenue` (4 dp)8081A **monthly** IS sums the single period; an **FY** IS sums all 12 periods of that fiscal year82(FY2024 = M1..M12, FY2025 = M13..M24). EXCLUDE operating-count accounts from all dollar lines.8384### Derived ratios85- `mom_revenue_variance.amount = rev(cur) - rev(prior)`; `.pct = amount / rev(prior)` (4 dp).86- `revenue_growth_pct = (rev_fy2025 - rev_fy2024) / rev_fy2024`; same shape for `ebitda_growth_pct`.87- **`arpu`** = FY revenue / **average monthly `active_customers`** = revenue / (sum of 12 monthly88 active_customers / 12). This yields an ANNUAL revenue-per-customer figure. (Do NOT divide by the89 raw 12-month sum.)90- **`sales_per_labor_headcount`** = FY revenue / **average monthly `labor_headcount`**91 (= revenue / (sum of 12 monthly labor_headcount / 12)). Annual revenue per FTE.92 NOTE: average-headcount denominator is the working convention; this is the single biggest93 ambiguity in the finance tasks — if a result looks off by a ~12x factor, reconsider sum vs94 average. Apply the SAME denominator convention at branch and region level.9596## 4. Regional rollup & rankings (finance)9798- A region's figures = **sum of its member branches** (`/api/finance/branches` gives99 `region_id`; `records?region=REG-X` returns only those branches). REG-WEST = BR-004/005/006,100 REG-NORTH = BR-001/002/003, REG-EAST = BR-007/008/011, REG-SOUTH = BR-009/010/012.101- `branch_ids` = ascending list of the region's branch IDs.102- Region `ebitda_margin` = region_ebitda / region_revenue; region `sales_per_labor_headcount`103 = region_revenue / (region average monthly labor_headcount).104- **`region_reconciliation_variance` = 0.0.** There is no separate region-level record; the105 rollup ties exactly to the active branch data. (This is what "tie back to active operating106 data" means.)107- `*_rank_desc` = 1-based rank, DESCENDING by the metric (1 = largest).108 - `region_context.ebitda_rank_desc`: rank of the target region's FY2025 EBITDA among ALL109 regions (compute every region's FY2025 EBITDA, sort desc, find target's position).110 - `branch_rankings.sales_growth_rank_desc`: rank of the TARGET branch's FY2025-vs-FY2024111 revenue growth % among ALL 12 branches, descending.112 - `top_sales_growth_branch_id`: branch with highest FY revenue growth % across all 12.113 - `top_arpu_branch_id`: branch with highest FY2025 ARPU across all 12 (same ARPU formula).114 - `top_ebitda_branch_id` / `bottom_ebitda_branch_id` (region task): highest / lowest FY2025115 EBITDA among the region's branches only.116- Ties: break by ascending branch_id (stable). Rankings span the relevant universe (all 12117 branches for branch rankings; all regions for region rank; in-region only for region task's118 top/bottom branch).119120## 5. CBA compensation (current-year summary AND forecast)121122Data: `/api/compensation/rate-book`, `rosters?ensemble_id=`, `scenarios`.123124Rate book: `minimum_weekly_scale` (MWS, e.g. 2520.0); `pay_types` (4, ordered);125`seniority_weekly` bands `[{min_years,max_years,weekly_amount}]` (max_years null = open-ended);126`title_premium_pct` (e.g. Concertmaster .22, Principal .20, Section Lead .15, Assistant/Associate127Principal .10); `quarter_weeks` default 13/quarter; `current_year` (e.g. 2026).128129Each roster row: `years_of_service`, `title` (or null), `overscale_weekly`,130`combined_overscale_includes_title` (bool), `weeks_by_quarter` {Q1..Q4}, `notes`.131132### Per-employee, per-quarter weekly components (multiply each by that quarter's weeks)133For each quarter q with `weeks = weeks_by_quarter[q]`:134- **Minimum Weekly Scale** = `MWS * weeks`135- **Titled Position Premium** = `MWS * title_premium_pct[title] * weeks`136 — but **0 if title is null OR `combined_overscale_includes_title` is true** (when combined,137 the title premium is already inside overscale; do NOT add it separately).138- **Seniority** = `seniority_weekly(years_of_service) * weeks` (band lookup by years).139- **Overscale** = `overscale_weekly * weeks`.140141Quarter total = sum of the 4 components over all employees for that quarter.142Pay-type annual total = sum across 4 quarters and all employees.143`annual_total` = sum of all pay-type annual totals (= sum of 4 quarter totals).144145Use **actual `weeks_by_quarter`**, not a fixed 13, so partial-quarter employees (a quarter < 13)146are pro-rated automatically.147148### Review counts (both comp tasks)149- `roster_count` = number of roster rows for the ensemble.150- `combined_overscale_employee_count` = rows with `combined_overscale_includes_title == true`.151- `partial_quarter_employee_count` = rows where ANY quarter's weeks != 13.152- `largest_pay_type` / `largest_growth_pay_type` = enum from the 4 pay types (see below).153154### `largest_pay_type` (current-year task)155Pay type with the largest ANNUAL dollar total. (MWS dominates and is typically the answer.)156157### Forecast task (scenarios)158`scenario_id` selects a case in `/api/compensation/scenarios`, each with `year_plus_1` and159`year_plus_2` blocks: `mws_growth`, `overscale_growth`, `seniority_growth`,160`title_pct_multiplier`. Business rules (in the rate book): **for Year+1 add 1 year of service,161for Year+2 add 2 years of service BEFORE assigning seniority bands.**162163Compute three annual totals:164- **current**: base rate book, no growth, no added years.165- **year_plus_1**: `MWS*(1+mws_growth_y1)`; title premium pct scaled by `title_pct_multiplier_y1`;166 seniority band looked up at `yos+1` then scaled by `(1+seniority_growth_y1)`; overscale scaled167 by `(1+overscale_growth_y1)`.168- **year_plus_2**: growth COMPOUNDS off Year+1: `MWS*(1+g_y1)*(1+g_y2)`; title mult169 `tm_y1*tm_y2`; seniority at `yos+2` scaled by `(1+sg_y1)*(1+sg_y2)`; overscale scaled by170 `(1+og_y1)*(1+og_y2)`.171- `growth_rates.year_plus_1_vs_current = (y1-cur)/cur`; `year_plus_2_vs_year_plus_1 = (y2-y1)/y1`172 (4 dp).173- `year_plus_2_quarter_totals` / `year_plus_2_pay_type_totals`: the Year+2 breakdown.174- `largest_growth_pay_type`: pay type with the largest **absolute dollar** increase from current175 to Year+2 (MWS typically wins because it is the largest base). Note seniority can have the176 largest PERCENT growth due to band shifts + seniority_growth, but the field tracks absolute177 dollar growth.178179Ambiguity to flag: `seniority_growth` is applied as a rate multiplier ON TOP of the band shift180from added years. Both effects compound. (The +years-of-service rule is mandatory; the181seniority_growth multiplier is the literal reading of the scenario field.)182183## 6. Theatre weekly payroll184185Data: `/api/payroll/rate-book`, `productions?production_id=` (returns a 1-element list; take [0]).186Production has `schedule` (services) and `roster` (musicians).187188Rate book: `service_rates` (Performance/Audit per service; Rehearsal per hour; 1hr/2hr Sound189Check per service); `service_time_limits` (hours); `premium_pct`190(principal_or_lead .15, quartet .15, electronic .25, concertmaster .20, first_double .25,191additional_double .10, vacation .04); `weekly_guarantee`; `conflict_thresholds`192(rehearsal_earliest_start "09:00", rehearsal_latest_end "18:30").193194Schedule service: `service_id, service_type, date, start_time, end_time, duration_hours`.195Roster musician: `musician_id, name, instrument, assigned_service_ids[], doubles (int),196electronic, lead, principal, quartet, substitute, vacation_eligible`.197198### Per-musician pay (sum over the musician's `assigned_service_ids`)1991. **Base service pay** per assigned service:200 - Rehearsal: `Rehearsal_rate * max(duration_hours, 3.0)` (3-hour minimum call, hourly).201 - Performance / Audit / 1hr Sound Check / 2hr Sound Check: the flat per-service rate.202 - Accumulate into category buckets: Performance->`performance`, Audit->`audit`,203 Rehearsal->`rehearsal`, 1hr/2hr Sound Check->`sound_check`.204 - `base_total` = sum of all base service pay for the musician.2052. **Premiums** (`premium` category) = `base_total *` (sum of applicable pct):206 `principal_or_lead` if `principal or lead`; `quartet` if `quartet`; `electronic` if207 `electronic`; `concertmaster` only if a concertmaster flag is present (none in current data,208 so usually not applied). Premiums apply to base service pay, before vacation.2093. **Doubles** (`doubles` category, separate from `premium`): if `doubles >= 1`,210 `base_total * first_double` for the first, plus `base_total * additional_double *211 (doubles - 1)` for each additional. (e.g. 2 doubles = 35% of base_total.)2124. **Vacation** (`vacation`): if `vacation_eligible`,213 `0.04 * (base_total + premiums + doubles)` (4% of base service pay plus premiums).2145. **Guarantee adjustment** (`guarantee_adjustment`): ONLY for guaranteed regular players =215 **non-substitute** musicians (`substitute == false`). If `base_total < weekly_guarantee`,216 add `weekly_guarantee - base_total`. (No explicit `guaranteed` field exists; "regular" = not217 a substitute.) Compare against `base_total` (base service pay), not the premium-inclusive total.2186. **`substitute_adjustment`**: no substitute rate is defined in the rate book, so it is 0 and219 typically omitted (template says "currency when applicable"). Substitutes simply do NOT get220 the weekly guarantee.221222Musician `total` = base_total + premiums + doubles + vacation + guarantee_adjustment.223`weekly_total` = sum of all musician totals. `top_paid_musician_id` = musician with max total224(break ties by ascending musician_id).225`per_musician[i].categories` = only the NONZERO category buckets for that musician, rounded 2 dp.226`category_totals` = sum across musicians per category; compute from UNROUNDED values and round227once (so it may differ a penny from summing the displayed per-musician figures — that is fine).228229### `service_counts`230Schedule-level count of each `service_type` across the production's UNIQUE services (count the231`schedule` rows by type, e.g. 4 Performance, 2 Rehearsal, 1 Audit, 1 "1hr Sound Check").232(Possible alternative reading is assignment-level counts summed over musicians; prefer the233schedule-level "what happened this week" reading for a weekly payroll package, but sanity-check234against the memo wording.)235236### `conflict_flags` (enum, sorted alphabetically; emit only those that trigger)237- `REHEARSAL_EARLY_START`: a Rehearsal `start_time` earlier than `rehearsal_earliest_start` (09:00).238- `REHEARSAL_LATE_END`: a Rehearsal `end_time` later than `rehearsal_latest_end` (18:30).239- `SERVICE_OVER_TIME_LIMIT`: any service whose `duration_hours` exceeds its240 `service_time_limits` entry (Rehearsal 5.0, Performance 3.0, Audit 3.0, 1hr SC 1.0, 2hr SC 2.0).241- `SOUND_CHECK_DURATION_MISMATCH`: a "1hr Sound Check" whose duration != 1.0, or a "2hr Sound242 Check" whose duration != 2.0.243Compare times by converting "HH:MM" to minutes. A flag fires if ANY service triggers it; emit244the deduplicated, alphabetically sorted set.245246## 7. Step-by-step SOP for an unseen task in this group2472481. Open `answer_template.json`; list required keys and exact field shapes/rounding.2492. Read `request_memo.json` for target IDs/periods/scenario; ignore distractor memo notes.2503. Identify the domain (finance branch close / regional / comp current / comp forecast / payroll)251 by the template's top-level keys.2524. `curl` the relevant endpoints (always the remote base URL).2535. Compute with the formulas above in a script. Keep full precision internally.2546. Round at the very end: currency 2 dp, percent/ratio/growth 4 dp. Order lists by ascending255 stable ID unless a rank/sort field says otherwise.2567. Emit exactly the template keys (no extras), validate JSON, return the single object.257258### Self-check before returning259- Finance: `gross_margin == revenue - cogs`; `ebitda == gross_margin - sga - allocations`;260 region figures == sum of member branches; `region_reconciliation_variance == 0.0`; ranks are261 1-based descending.262- Comp: quarter totals sum to annual_total; pay-type totals sum to annual_total; partial-quarter263 count matches rows with a non-13 quarter; title premium suppressed when combined flag true.264- Payroll: sum of per-musician totals == weekly_total; category_totals reconcile to per-musician265 category sums (within rounding); guarantee only on non-substitutes below the guarantee; flags266 deduplicated and alphabetically sorted; per_musician ordered by musician_id.