Outdoor sound propagation and occupational noise exposure (ISO 9613-1, ISO 9613-2, ISO 9612) (#89)
* feat: ISO 9613-1 atmospheric sound absorption
Full alpha(f, T, RH, p) model per ISO 9613-1:1993 clauses 6.2-6.4:
oxygen/nitrogen relaxation frequencies, water-vapour molar
concentration from relative humidity, classical + rotational +
vibrational terms. Validated against a 13-point Table 1 grid
(worst deviation 0.39 %, rounding-limited, vs the standard's
stated +/-10 % accuracy) with the exact midband convention of
Eq. (6) Note 5. air_attenuation_m() feeds the ISO 354 m parameter
directly, closing the user-supplied gap. Two conformance checks
added (34/34).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* refactor: C1 review nits — test name and comment direction
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* test: extend ISO 9613-1 Table 1 oracle against the full standard text
The full 37-page ISO 9613-1:1993 text (now in hand) confirms every
implemented constant of Eqs. (3)-(6), the Annex B psychrometric
conversion and the clause 4.2 reference conditions exactly as coded.
Oracle extended from 13 to 23 Table 1 points, adding the 20-50 degC
sub-tables 1(i)-1(p) unavailable in the interim source. Worst
deviation unchanged at 0.385 % (rounding-limited).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9613-2 outdoor sound propagation
Implement the ISO 9613-2:1996 general method for octave-band outdoor sound
attenuation: geometrical divergence (Eq. 7), atmospheric absorption (Eq. 8,
via ISO 9613-1 air_absorption), ground effect (general per-region method of
7.3.1 with the Table 3 a'/b'/c'/d' functions, Eq. 9, plus the alternative
method of 7.3.2, Eq. 10, and the DOmega index Eq. 11), and screening (Eq. 12-18
with the C2/C3 factors, single/double diffraction, Kmet and the 20/25 dB caps).
Adds the Cmet meteorological correction (Eq. 21/22) and the LfT(DW)=Lw+Dc-A
receiver-level composition (Eq. 3/6). Frozen dataclass result exposes the
per-term breakdown (Adiv/Aatm/Agr/Abar). New module, tests and exports.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* feat: ISO 9612 occupational noise exposure strategies and uncertainty
Add phonometry.occupational_exposure implementing the three ISO 9612:2009
measurement strategies and the normative Annex C uncertainty budget:
- task_based_exposure (Clause 9): per-task energy average (Eq 7), task
contributions (Eq 8), daily level (Eq 9/10); uncertainty Eq C.3 with
sampling u1a (Eq C.6), duration u1b (Eq C.7) and sensitivity coefficients
c1a (Eq C.4) / c1b (Eq C.5).
- job_based_exposure (Clause 10): effective-day energy average (Eq 11),
daily level (Eq 12); Table 1 minimum-duration check; uncertainty Eq C.9.
- full_day_exposure (Clause 11): whole-day averaging (Eq 11/13) with the
Clause 11.3 three-measurement / 3 dB spread rule.
- Table C.4 (job/full-day c1*u1, bilinear interpolation), Table C.5 (u2),
u3 = 1.0 dB, coverage factor k = 1.65 (one-sided 95 %).
Frozen dataclasses (Task, TaskContribution, ExposureResult), ExposureWarning
advisories for the sampling rules, and exports appended to __init__.
Tests reproduce the Annex D (task), E (job) and F (full-day) worked examples
to the standard's precision, plus closed-form oracles (single 85 dB/8 h task
= 85; equal-level tasks; -3.01 dB per halved duration; k = 1.65) and the
Table C.4 / Table 1 lookups. make check green: 1167 passed, 12 skipped.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: ISO 9612 review corrections — Table C.4 digits and Eq C.7 denominator
Two Table C.4 cells verified against the page image at 200 dpi and
corrected (N=6/u1=3.5: 3.4 -> 3.3; N=20/u1=2.0: 0.6 -> 0.5), the test
that echoed the wrong N=6 cell now pins the printed value plus the
corrected N=20 cell. The duration_samples branch of Eq C.7 divides by
J(J-1) like Eq C.6 (standard error of the mean), with a regression test
pinning u1b = 1.0 h for samples (4, 6) h. Module docstring now states
the Lp,Cpeak uncertainty scope cut (Table C.5, NOTE 1).
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* test: ISO 9613-2 and ISO 9612 conformance checks
Add a new "Outdoor propagation & occupational exposure" conformance domain
with seven closed-form / worked-example checks backed by shared constants in
tests/reference_data.py:
- ISO 9613-2:1996 Eq. (7) geometrical divergence Adiv = 51 dB at 100 m.
- ISO 9613-2:1996 Table 3 ground limit b'(0) = 10,1 -> Agr(250 Hz) = 17,2 dB
(porous ground, hs = hr = 0, dp -> inf).
- ISO 9613-2:1996 clause 7.4 single/double diffraction 20/25 dB caps.
- ISO 9612:2009 Annex D/E/F daily exposure LEX,8h and expanded uncertainty U
(84,3/2,7, 88,1/3,8, 90,1/3,4 dB), the Task objects rebuilt from the shared
input table.
Registry grows 34 -> 41 checks across 8 domains; docs/CONFORMANCE.md
regenerated via make conformance. Registration and reference-data consistency
tests pin the constants to the published values.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: figures for outdoor propagation and occupational exposure
Add four documentation figures (each EN/ES x light/dark) plus a setup diagram:
- air_absorption_alpha: ISO 9613-1 alpha(f) over frequency at four
temperature/humidity combinations (relaxation roll-off, f^2 growth).
- outdoor_attenuation_breakdown: ISO 9613-2 per-term stacked bar (Adiv, Aatm,
Agr, Abar) with the total A, for a 200 m path over porous ground with a 4 m
barrier.
- exposure_uncertainty: ISO 9612 Annex D task contributions with the daily
LEX,8h and the one-sided 95 % upper limit LEX,8h + U.
- diagram_outdoor_geometry: source-barrier-receiver geometry (dss/dsr/z, the
blocked direct ray and the diffracted ray over the top edge).
Spanish translations added to the generate_graphs / generate_diagrams
dictionaries (decimal commas, UNE terminology). Every image visually audited.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: ISO 9613-1/2 outdoor propagation guide and ISO 9612 exposure (EN)
Add the didactic Outdoor Sound Propagation guide (docs/outdoor-propagation.md)
covering ISO 9613-1 atmospheric absorption and the ISO 9613-2 general method
(divergence, atmospheric, ground and barrier terms, the OutdoorAttenuation
breakdown, the alternative 7.3.2 ground method, Cmet), with executed snippets
and honest scope notes (Amisc/reflections not implemented, 7.3.2 not
auto-wired, per-band Cmet a convenience). Extend the Levels guide with the
ISO 9612 occupational-exposure section (task/job/full-day strategies, the
C.6 I(I-1) vs C.12 N-1 asymmetry, Table C.4, k = 1,65, LEX,8h + U upper limit,
Lp,Cpeak uncertainty out of scope), reproducing the Annex D/E/F worked
examples.
Add the matching theory sections, API-reference rows for every new public
name, README feature list + docs table, docs index and CHANGELOG entries.
llms-full.txt / llms.txt regenerated (make llms) with the new page wired into
gen_llms.py. GitHub-safe math throughout; every snippet executed with pasted
outputs.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* docs: outdoor propagation and exposure site twins (EN + ES)
Add the English and Spanish Starlight twins of the new content:
- guides/outdoor-propagation.md (EN + ES) — code-identical snippets to the
docs page; Spanish prose uses UNE terminology ("ponderación temporal"),
decimal commas outside math and code, and the _es figure variants.
- The ISO 9612 section added to guides/levels.md (EN + ES).
- reference/theory.md and reference/api.md rows (EN + ES).
- Landing cards and standards list bumped 34 -> 37 (index.mdx EN + ES);
guides/outdoor-propagation added to the sidebar.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: final branch review — API rows, precision phrasing and ES links
Adds the five missing public-symbol rows (TaskContribution,
table_c4_contribution, minimum_cumulative_duration_hours, COVERAGE_FACTOR,
INSTRUMENT_U2) to the three API references, states the ISO 9613-2 default
atmosphere (20 °C / 70 %, a Table 2 reference atmosphere) in the API rows
and guides, and softens "digit-for-digit" to "the standard's printed
precision" wherever it covered Annex E, whose final level differs only by
the standard's own pre-rounding. ES pages now use the /es/ link prefix
consistently. Code nits from the review: dead arg > 1 guard removed
(z > 0 implies arg >= 3), _with_advisory uses dataclasses.replace, and the
float-keyed _PRIME_BY_BAND gets a safety comment. llms files regenerated.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
* fix: validate propagation/exposure inputs and docs lint (PR review)
Addresses the PR #89 bot review:
- Barrier.__post_init__ rejects negative edge distances and a zero
edge_separation (which previously divided by zero in the C3 double-
diffraction factor); Task.__post_init__ rejects empty samples and a
non-positive duration.
- ground_attenuation and barrier_attenuation reject non-positive
frequencies (a zero frequency gave an infinite wavelength / division by
zero); ground_attenuation also checks a positive distance and
non-negative heights. job_based_exposure rejects a non-positive
sample_duration_hours.
- Rewrite the ISO 9613-2 7.3.2 closed form in the guides and API tables so
Markdownlint no longer parses `(...)[...]` as a reversed link (MD011).
- Regression tests for every new guard; docstrings note the new raises.
Claude-Session: https://claude.ai/code/session_01HUHMx4dxDes2t5UPQ25djP
Didactic documentation: theory everywhere, bilingual figures, setup diagrams, parameter tables (#77)
* docs: harden diagram engine per visual audit
- dim() draws real witness lines from the measured points to the offset
dimension line (the offset=0 path emitted zero-length lines); vertical
labels sit left of the line, clear of masts and figures
- env diagram: the 4 m dimension now measures the mic axis itself and
the flush-mounted label moved to the clear zone between masts
- tonality top view: the 1.00 m dimension connects face and microphone
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: bilingual diagram variants (EN/ES x light/dark)
Every setup diagram is now emitted in four variants; Spanish strings
(including decimal commas) come from a translation table applied by the
SVG engine, and layouts were adjusted where the longer Spanish labels
collided (env titles, processing-chain box width, calibration output
label, tonality dimension label side).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: bilingual figure variants for the ES site (EN/ES x light/dark)
generate_graphs.py now emits every figure in four variants: a Spanish
translation table (~75 exact strings + parametrized patterns) is applied
to the figure's Text artists at savefig time, so the generator functions
stay single-language internally. Spanish output uses UNE terminology
(ponderación temporal, líneas isofónicas, fonios, ráfaga) and a uniform
decimal comma (annotations and tick labels included); code identifiers
and normative mode names (Fast/Slow/Impulse) stay untranslated. The ES
site pages now reference the _es/_es_dark variants (56 URLs). The ES set
passed a 10-figure visual audit (10/10 approved, no blockers).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: didactic calibration page - theory, setup diagram, stability figure, parameter table
Adds the sensitivity-factor derivation with the calibration-chain SVG,
a new autogenerated calibration_stability figure (stable vs 3% AM tone
against the IEC 60942:2017 class 1 deviation band; 4 language/theme
variants; the ES decimal-comma pass now preserves clause numbers like
5.3.3), and a full parameter table for calculate_sensitivity - EN and
ES in parity.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: SEL concept, Lden 24h profile and annotated TNR spectrum figures
Three new didactic figures for the levels guide, each in four
language/theme variants: sel_concept (pass-by event vs its Leq and the
equal-energy 1 s SEL block, dBFS mode), lden_profile (synthetic urban
24 h LAeq with colored day/evening/night bands and the +5/+10 weighted
period levels clearly separated from the composite Lden), and
tonality_spectrum (averaged Hann spectrum with the critical band, the
detected tone and the live TNR value vs its criterion, computed by the
library itself).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: didactic levels guide, theory extensions and refreshed why-phonometry
- levels guide (EN docs+site): energy-mean rationale for Leq, percentile
semantics for LN, SEL and noise-dose theory with the new figures
embedded, TNR critical-band explanation with the annotated spectrum,
Lden profile figure, and parameter tables (type/units/range/default)
for every level function including the spectrogram
- theory page: five new sections (G-weighting pole/zero derivation,
ISO 226 formulae, ECMA-418-1 TNR/PR math, event/dose metrics,
ISO 1996-1 descriptors) with cross-references to the guides
- why-phonometry: rewritten around the conformance-testing story with
the per-standard verification table and honest ecosystem positioning
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: weighting/time-weighting theory and parameter tables; fix math-block delimiters
The physiological origin of A/C (inverted 40/100-phon contours, the four
IEC corner frequencies) and the exponential-detector derivation with the
tau semantics, each with full parameter tables. Display math inserted by
the docs expansion now always uses block-style $$ delimiters on their
own lines - mid-line multi-line $$ blocks silently derailed remark
parsing and truncated every heading after them (caught because the
built pages lost their anchor ids); also replaced a raw < in a table.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: processing-chain and multirate diagrams, filter-bank math, streaming patterns
- getting-started embeds the processing-chain diagram as a guided map of
the guides
- filter-banks gains the IEC 61260-1 base-10 band math (G, fm, band
edges), a new multirate-decimation diagram (d5, four language/theme
variants) and the full octavefilter/OctaveFilterBank parameter table
- block-processing gains the canonical real-time level-meter loop
(validated against the actual API - TimeWeighting is inherently
stateful) and a stateful-constraints table
- multichannel gains the accepted-shapes table
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: Spanish parity for the didactic expansion
Every enriched page now has its Spanish twin: levels (theory, three new
figures via _es variants, six parameter tables), weighting and
time-weighting (curve-origin and exponential-detector sections),
filter-banks (fractional-band math, multirate diagram, full parameter
table - terminology unified on 'decimación'), getting-started
(processing-chain map), block-processing (real-time meter pattern and
stateful constraints), multichannel (shapes table), theory (five new
sections) and a full rewrite of why-phonometry around the conformance
story. API reference now covers all 27 public exports in EN and ES
(added ToneAssessment and CalibrationWarning rows). UNE terminology and
Spanish decimal commas throughout; site builds with all links valid.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs: audited fixes - Spanish tick commas and multirate note
Final visual-audit round: the d5 diagram now shows the no-decimation
note on the 16 kHz row in both languages, and Spanish figures render
every numeric tick with a decimal comma (FixedFormatter sequences are
rewritten in place; set_xticklabels-style FuncFormatters and plain
linear ScalarFormatters are wrapped - the first naive wrapper broke
ScalarFormatter ticks entirely and was caught visually).
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* docs(site): language selector in the mobile header, scrollable display math
Custom Header component (default markup plus a compact icon-only
LanguageSelect visible below the md breakpoint, where Starlight hides
the whole right group behind the hamburger menu) and overflow-x on
katex-display so wide formulas scroll inside their box on phones.
Verified with Playwright at 390 px in EN and ES.
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf
* fix: address review feedback on the didactic docs
- levels tables: spectrogram calibration_factor/dbfs marked constructor-
only, overlap range as 0 <= overlap < 1, composite hours constraints,
ln_levels shared-semantics row filled; ES translation leftovers and
the 'delante' typo fixed
- block-processing: y[..., -1] so the meter loop is multichannel-safe;
high_accuracy wording matches the real behavior (default resolves to
False, explicit True raises)
- API: CalibrationWarning row states the fs/validate preconditions (3 files)
- weighting: high_accuracy default column and oversampling claim made
precise (8x cap; silently ignored for G); ES row in parity
- filter-banks: 25 Hz relative bandwidth corrected to 0.024% of Nyquist
(EN/ES); verify_filter_class classes documented as 1/2/None
- generate_graphs: dropped the global plt.savefig monkey-patch - the
translation now runs inside themed_path(), which every generator
already calls at save time (verified output identical)
Claude-Session: https://claude.ai/code/session_013kkVt3nxi9svp1an28uHxf