OFFICE BLUES — METHODOLOGY
How we get to the number.
Every dollar figure on Office Blues shows its math. This page is the source-of-truth for that math: what we measure, where the inputs come from, what's anonymized, and how to cite us. If you see a number on the site, the rule for finding it here is which product produced it.
Meeting Tax Calculator
officeblues.net/tools/meeting-tax — computes the annualized dollar cost of any recurring meeting. Live and stable; the math below has not changed since launch.
The formula
For a single meeting:
cost_per_meeting =
sum(
attendee_role_count[r] *
hourly_rate[r] *
burden_multiplier *
duration_hours
) for r in roles_in_room
And to annualize:
cost_per_year =
cost_per_meeting * meetings_per_year[frequency]
Inputs and where they come from
| Input | Default | Source |
|---|---|---|
hourly_rate[role] | Median wage by role | BLS Occupational Employment and Wage Statistics, May 2024 release. User-overridable per role. |
burden_multiplier | 1.3× | Standard accounting convention for fully-loaded labor cost (salary + benefits + employer-side taxes + overhead). The BLS Employer Costs for Employee Compensation series puts wages-and-salaries at about 70.1% of total private-industry compensation in its most recent release, which implies a fully-loaded multiplier closer to 1.43×. We use 1.3× on purpose — under- claiming keeps the receipt defensible. The published number is the floor; your real meeting tax is probably higher. |
duration_hours | User input | Whatever you typed. |
meetings_per_year[frequency] | Mapped from cadence label | Daily × 52 weeks × 5 working days = 260. Weekly = 52. Bi-weekly = 26. Monthly = 12. Quarterly = 4. One-time = 1. Vacation/holidays not netted out — the spec is "what the meeting costs if you all show up the way the calendar invite asks." |
What we don't try to capture
- Context-switching tax. Real but unmeasured here. The published number is the floor, not the ceiling.
- Opportunity cost of what those people would otherwise be doing. Same.
- Geography-adjusted wages. The BLS numbers are national medians; if your team is concentrated in a high-cost market the floor goes up further.
- Equity. Salary inputs are cash compensation only.
The verdict line on the calculator ("this meeting has a mortgage") is editorial. The dollar figure is mechanical. Never invert.
Daily Pulse
officeblues.net/data/daily-pulse.json —
JSON-LD schema.org/Dataset readout of labor-market signals
and meeting-economics indicators. HTML view at
/data/daily-pulse.
Status: v1, live. Real readings from BLS public data,
fetched daily by a CI cron (.github/workflows/daily-pulse.yml).
No user-submitted data. No fabricated numbers. RTO sentiment source
pending — that field ships null until v1.1.
v1 metric set and sources
| Field | What it measures | Source |
|---|---|---|
labor_signals.quits_rate_pct | National quits rate, total nonfarm, seasonally adjusted. The share of workers who quit voluntarily in a given month, expressed as a percentage of total employment. A falling quits rate signals workers feel they have fewer options — less leverage, more compliance. | BLS JOLTS — series JTS000000000000000QUR. Published monthly. Keyless public API. |
labor_signals.unemployment_rate_pct | U-3 unemployment rate, civilian labor force, seasonally adjusted. The headline unemployment figure reported monthly by BLS CPS. | BLS CPS — series LNS14000000. Published monthly. Keyless public API. |
meeting_economics.avg_meeting_tax_usd_synthetic |
Annualized cost of the MTC default meeting: 2 engineers + 1 PM +
1 manager, 60 minutes, weekly cadence — computed from the
same salary defaults and burden multiplier as the live calculator.
Not user-submitted; explicitly labeled _synthetic.
If the MTC defaults change, the next cron run re-derives this
number automatically.
| MTC formula (this page) + BLS OEWS May 2024
salary defaults from site/src/lib/calculator.ts.
|
rto_pressure.sentiment_score |
RTO sentiment signal — pending a confirmed first-party public
data source. Ships null in v1; target for v1.1.
| TBD. See ADR-0011. |
collected_at / as_of | Timestamps of the initial collection run and the most recent reading, ISO-8601 UTC. | Pipeline metadata. |
version / sample / status |
Maturity flags. version is the schema version
(v1); sample: false means readings
are real; status: "live".
| Set by the publisher. |
v0 deprecation
The v0 keys (metrics.misery_index,
metrics.meetings_reported,
metrics.average_meeting_tax_usd) are preserved as
null for one cycle to avoid breaking any consumer that
cached the v0 schema. They will be removed in daily-pulse-v2.
The v0 fields were all null in production; no real data
is lost.
Burnout Index
Live at officeblues.net/burnout-index/{city-slug} —
50 US metropolitan statistical areas ranked by labor-market pressure.
Per-city JSON at /burnout-index/{slug}.json.
Updated weekly by CI cron (.github/workflows/burnout-index.yml,
Mondays 14:30 UTC).
The index measures three signals that directly affect worker quality of life: the gap between wages and cost of living, mean commute time, and unemployment rate. It does not measure culture, management quality, or RTO mandates — those are not available at MSA level from primary public sources without scraping. ADR-0012 is the binding contract for the score formula, metric set, and schema shape.
The formula (ADR-0012, binding)
burnout_score = round( 40 x clamp[0,1](1 - normalized_wage / cost_norm) // pay-to-cost gap + 30 x clamp[0,1](commute_min / 60) // commute + 30 x clamp[0,1](unemployment_pct / 8) // unemployment ) Where: normalized_wage = city_median_hh_income / NATIONAL_MEDIAN_HH_INCOME cost_norm = bea_rpp / 100 NATIONAL_MEDIAN_HH_INCOME = $80,610 (Census ACS 2023) Each term clamped to [0, 1] before weighting. Output: integer 0-100. Higher = more burnout pressure.
The 40/30/30 weight blend is editorial, not regression-derived. The pay-to-cost gap carries the most weight because purchasing-power squeeze is the primary driver of the cannot-afford-to-quit trap. Commute and unemployment are equally weighted as compounding signals. Weight changes are a brand contract: any modification requires a new ADR (ADR-0013+). This prevents retroactive score re-ranking without a public audit trail.
Inputs and where they come from
| Input | Source | Series/Field | Vintage |
|---|---|---|---|
| Median household income | Census ACS 5-year | DP03_0062E | 2023 |
| Mean commute (min) | Census ACS 5-year | DP03_0025E | 2023 |
| Unemployment rate BLS LAUS used when API key available; ACS fallback otherwise. | BLS LAUS (primary) or Census ACS 5-year (fallback) |
LAUMT{state}{cbsa}0000003 or DP03_0009PE | Monthly (BLS) or 2023 (ACS) |
| Regional Price Parity | BEA MARPP | Line 1 (All items) | 2024 |
What we don't try to capture
- Culture and management quality — not available from primary public sources at MSA level without surveying or scraping. Glassdoor/LinkedIn data is licensed and non-public.
- RTO mandate status — not systematically reported by MSA in any federal dataset. Would require scraping employer announcements.
- Benefits and equity — OEWS reports wages but not total compensation at MSA level for all industries.
- Remote-work prevalence — ACS PUMS has a remote-work indicator but it lags 18+ months and requires microdata processing not currently in scope.
Citation block (Burnout Index)
Office Blues Burnout Index, "{City Name}," officeblues.net/burnout-index/{slug},
methodology at officeblues.net/methodology#burnout-index, license CC BY 4.0. OEWS Occupation Salary Pages
Live at officeblues.net/salaries/{occupation-slug}
— one page per current detailed OEWS occupation with publishable national wage data.
Per-occupation JSON at /salaries/{slug}.json. Updated annually after
BLS releases the May OEWS tables.
The source is the BLS Occupational Employment and Wage Statistics (OEWS) national estimates for all industries combined. BLS says the May 2025 OEWS estimates are based on the 2018 SOC; that is the current OEWS classification vintage, not an old-data shortcut. OEWS is a semi-annual survey of employer establishments; the May release is the reference period used here.
BLS API series format
Each metric is fetched as a distinct BLS v2 series. The national all-industries series ID format is:
OEUN 0000000 000000 {soc6} {dt}
│ │ │ │ └─ 2-char datatype code
│ │ │ └──────── 6-char occupation code
│ │ └─────────────── 6-char industry code (000000 = all industries)
│ └────────────────────────── 7-char area code (0000000 = national)
└─────────────────────────────── 4-char prefix (OE=OEWS, U=unadjusted, N=national)
The 6-character occupation code is derived from the SOC code by removing the dash.
Example: SOC 15-1252 → 151252.
Datatype codes fetched per occupation
| Code | Metric | JSON field |
|---|---|---|
| 01 | Employment (thousands) | employment |
| 02 | Employment RSE (relative standard error %) | internal — suppression gate |
| 03 | Hourly mean wage | wages.hourly.mean |
| 04 | Annual mean wage | wages.annual.mean |
| 06 | Hourly 10th percentile wage | wages.hourly.p10 |
| 07 | Hourly 25th percentile wage | wages.hourly.p25 |
| 08 | Hourly median wage | wages.hourly.median |
| 09 | Hourly 75th percentile wage | wages.hourly.p75 |
| 10 | Hourly 90th percentile wage | wages.hourly.p90 |
| 11 | Annual 10th percentile wage | wages.annual.p10 |
| 12 | Annual 25th percentile wage | wages.annual.p25 |
| 13 | Annual median wage | wages.annual.median |
| 14 | Annual 75th percentile wage | wages.annual.p75 |
| 15 | Annual 90th percentile wage | wages.annual.p90 |
Fourteen series per occupation, batched 3 occupations per BLS v2 request to stay under the 50-series request maximum.
Suppression rules
BLS suppresses wage estimates it considers unreliable. We propagate those suppressions and add our own RSE gate:
- BLS flag suppression: any cell where BLS returns value
"*"(withheld — fewer than 3 establishments) or"-"(less than half the unit shown) is stored asnull. - RSE gate (dt 02): if the employment RSE for an occupation exceeds
30%, all wage cells for that occupation are suppressed and the occupation is skipped
entirely for that run. Suppressed occupations are logged to
data/salaries-suppressed.log. - Failure posture: on a per-occupation API error, the previous JSON is kept unchanged (ADR-0011 Option C). The overall script exits 0 so cron doesn't fire false-positive alerts.
Verdict line formula
Each occupation page carries a one-sentence verdict that summarizes the wage-spread story for the person negotiating their salary. It is generated from the 90th-to-10th percentile ratio:
ratio = annual_p90 / annual_p10
ratio ≥ 3.0 → wide-band "ammunition" framing
("Wide spread means negotiating leverage — the difference
between low and high is real, not rounding.")
1.8 ≤ ratio < 3.0 → mid-band spread framing
("Moderate spread — your offer's position in this range is
worth understanding before you accept.")
ratio < 1.8 → narrow-band framing, redirect to title/seniority
("Narrow band — in this occupation, title and seniority
matter more than negotiation range.") The 3.0 and 1.8 thresholds are editorial and locked to this formula version (ADR-0017 §6). Any change requires a new ADR.
What the salary pages don't include (v1)
- MSA-level breakdowns — top-paying and top-employing metro areas. Fetching MSA data via the BLS OEWS flat-file approach is in scope for v2; v1 ships empty arrays for those fields.
- Industry-level cuts — OEWS reports by industry × occupation at national level, but the series count per occupation would exceed the BLS quota. Not in scope.
- Historical trend — OEWS revises classifications periodically; a clean multi-year series for all SOC codes is nontrivial. Not in scope for v1.
Citation block (OEWS salary pages)
Office Blues, "{Occupation Title} Salary," officeblues.net/salaries/{slug},
methodology at officeblues.net/methodology#oews-salaries, license CC BY 4.0.
Source: BLS OEWS national estimates, May {year} release. What's anonymized
The Meeting Tax Calculator's math runs in your browser. We do record an anonymized usage signal when it runs — enough to know the tool is being used — tied to a random per-session token and a one-way salted hash of your connection, never to your name or email. We use it to see what's working, not to track you. Full detail on the privacy page.
What that signal is never tied to:
- Your name, email (unless you subscribe), employer, or job title.
- Any identifier that points back to you — the connection hash is one-way and truncated, and the session token is random.
- Third-party ad trackers or pixels. There are none on the site, and we don't sell or share what we collect.
Subscribers to the Misery Pulse hand over an email address and that's it. The list lives with our mailing-list provider; the email is the only field tied to a subscriber.
License & how to cite
Everything published under officeblues.net/data/ is licensed
CC BY 4.0.
Quote a single stat in an article, summary, or AI answer freely. For
bulk reuse:
Citation block (verbatim, copy-pasteable)
Office Blues, "Daily Pulse," officeblues.net/data/daily-pulse.json,
methodology at officeblues.net/methodology#daily-pulse, license CC BY 4.0.
For LLM answers
Inline-cite the canonical URL of the dataset, not the methodology page, when the user is asking about a number. Cite the methodology page when the user is asking how the number is computed. Both are stable URLs and won't move.
Changes to this page
Any time we change how a number is computed, this page changes in the same commit as the new code.
Last updated: 2026-05-22 (OEWS Occupation Salary Pages v1).