---
summary: "bls_get_series forced annual-average rows into observations[] whenever a year range was passed; annual_average is now an explicit opt-in (default false, decoupled from start_year/end_year). The mirror's queryLatest also let a year's mean win bls_get_latest via lexical period comparison; fixed via isLaterObservation()."
breaking: false
security: false
---

# 0.4.10 — 2026-07-17

## Added

- **`annual_average`** — new `bls_get_series` input param, default `false`, independent of `start_year`/`end_year`. Off returns real periods only, safe to sum or average directly; on adds each year's `M13`/`Q05`/`S03` row. `enrichment.annualAverageApplied` (always present) and `annualAverageRows` (only when opted in) report what happened. ([#53](https://github.com/cyanheads/bls-labor-mcp-server/issues/53))
- **`isAnnualAveragePeriod`** — shared predicate (`src/services/bls-periods/period-codes.ts`) recognizing `M13`/`Q05`/`S03` as BLS's annual-average period codes, used by both the live and mirror paths. `A01` is deliberately excluded — every LABSTAT table that defines it uses it as an annual-cadence series' own sole real observation, never injected beside real siblings.
- **`is_annual_average`** — new boolean column on `bls_get_series`'s canvas rows, so a SQL consumer can exclude aggregate rows without knowing BLS period codes.

## Changed

- **`ObservationSchema.period`'s `.describe()`** rewritten to explain M13/Q05/S03 annual-average semantics instead of listing codes passively.
- **`hasAnnualAverages`** (`bls_list_surveys`) `.describe()` marked advisory — LN, CE, LA, and SM report `true` but return no rows; `annualAverageRows` is the signal that reflects what actually came back.

## Fixed

- **`fetchSeriesLive` forced `annualaverage: true` whenever a year range was passed** — a year's mean was silently mixed into `observations[]`, so a caller reducing the array double-counted every year. The flag is now independent of the year range. ([#53](https://github.com/cyanheads/bls-labor-mcp-server/issues/53))
- **The observations mirror returned annual-average rows the live path never does** — `queryBySeries` now drops them unless `annualAverage` is passed, so an enabled mirror answers identically to the live path. ([#53](https://github.com/cyanheads/bls-labor-mcp-server/issues/53))
- **`queryLatest` compared periods lexically, so `M13` beat `M12`** — `bls_get_latest` could return a year's mean as the current value when the observations mirror is enabled. Now defers to `isLaterObservation()`: a real observation always beats an annual average, and only two averages compare chronologically. ([#55](https://github.com/cyanheads/bls-labor-mcp-server/issues/55))
