Metadata-Version: 2.5
Name: lasercalc
Version: 0.1.0
Summary: IEC 60825-1 laser safety classification, MPE, NOHD and hazard calculations
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: reports
Requires-Dist: jinja2>=3.1; extra == 'reports'
Description-Content-Type: text/markdown

# lasercalc

IEC 60825-1 laser safety classification, MPE, NOHD and hazard calculations as a pure-Python, dependency-free library. The core is plain frozen dataclasses and functions with no I/O, so it drops into a web service, CLI, notebook, desktop app or CI job without adaptation.

## Install

```bash
pip install -e .            # core (stdlib only)
pip install -e ".[dev]"     # + pytest
```

## Usage

```python
from lasercalc import LaserSource, classify, nohd, required_od
from lasercalc.reporting import render_html

src = LaserSource(
    wavelength_nm=532,
    power_w=5e-3,
    beam_diameter_m=1e-3,
    divergence_rad=1e-3,
    label="Green pointer",
)

result = classify(src)
result.laser_class      # "3R"
result.label_text       # mandated warning label wording
result.checks           # full audit trail: every AEL evaluated, pass/fail

n = nohd(src)           # -> 14.8 m
od = required_od(src)   # -> OD 2.3

html = render_html(result, nohd=n, od=od, organisation="Acme Photonics")
# PDF: weasyprint.HTML(string=html).write_pdf()
```

Everything returned is a frozen dataclass, so `dataclasses.asdict()` is enough to serialise a result for JSON APIs, and `LaserSource` can be built directly from any validated input model.

## v0.1 scope — read before trusting outputs

**Implemented:**
- CW classification (Classes 1, 2, 3R, 3B, 4), Condition 3 (naked eye), point and small extended sources (C6 scaling)
- MPE (eye): visible dual photochemical/thermal limits, NIR retinal (700–1400 nm), far-IR (>1400 nm), simplified UV (302.5–400 nm)
- Correction factors C1–C7, T1, T2; C5 (2014 rules, simplified) staged for the pulsed engine
- NOHD, required eyewear OD, irradiance at distance, Gaussian peak irradiance
- Self-contained HTML classification report (stdlib only; PDF conversion is left to the caller)

**Not yet implemented (raises or documented as simplified):**
- Pulsed / pulse-train classification (three-criteria evaluation with C5)
- Classes 1M / 2M / 1C — Condition 1 (telescope) measurement; current results assume the whole beam fits a 7 mm pupil, which is worst-case for collimated beams
- Skin MPEs, 180–302.5 nm band, EN 207 LB rating mapping
- Full UV dual-limit tables; sub-ns regimes use conservative placeholders

**Validation status: NOT validated for regulatory use.** Tests pin the engine to well-known reference values (25.46 W/m² visible aversion MPE, 0.39/1/5 mW pointer class boundaries, 50 W/m² at 1064 nm, ~15 m pointer NOHD). Before production: extend `tests/` with worked examples from IEC 60825-14 and Henderson & Schulmeister, and cross-check a sample matrix against LaserBee. Reports carry a competent-person disclaimer for this reason.

## Modelling simplifications

Deliberate corners cut in v0.1, marked in the source with `Simplification:` comments. Each names its ceiling and upgrade path.

| Where | Simplification | Direction |
| --- | --- | --- |
| `correction_factors.c6` | α_max fixed at 100 mrad; the 2014 edition makes it time-dependent for pulsed sources | Exact for CW |
| `correction_factors.c5` | Point-source rule only; extended-source refinements (α > 5 mrad) omitted | Conservative |
| `mpe` visible & NIR, t < 100 ps | Flat placeholder radiant exposure (1.5e-4 J/m² · C6) instead of the Table A.1 short-pulse rows | Unverified — do not rely on |
| `mpe` NIR, t < 50 µs | One plateau for the whole 700–1400 nm band; strictly 13 µs for 700–1050 nm | Conservative below 1050 nm |
| `mpe` far IR, 1 ns ≤ t < 10 s | Single 5600·t^0.25 J/m² curve for the whole band | **Non-conservative** at 1400–1500 and 1800–2600 nm, where Table A.1 is lower |
| `mpe` far IR, t < 1 ns | Flat 100 J/m² placeholder | Unverified — do not rely on |
| `mpe` UV, 302.5–400 nm | Dominant 30·C2 J/m² photochemical limit compared against the C1 thermal limit, rather than the full dual-limit tables | Verify vs Table A.1 |
| `ael.ael_class1_cw_w` | UV and far-IR AELs derived as MPE × aperture rather than read from the explicit tables | Verify vs Tables 3–8 |
| `ael.ael_class3b_cw_w` | UV (<315 nm) 3B AEL flattened to 30 mW | Verify vs Table 8 |
| `hazards.beam_diameter_m` | Far-field linear growth d(z) = d₀ + φ·z; no Rayleigh-range treatment | Standard NOHD practice |
| `hazards.irradiance_w_m2` | Top-hat beam profile; a Gaussian peak is ~2× higher | Matches the standard's aperture-averaged measurement |

Correction factors return 1.0 outside their applicable wavelength range, by convention — the tables that would use them do not apply there.

## Layout

```
lasercalc/
├── sources.py              # LaserSource frozen dataclass (SI units, validated)
├── correction_factors.py   # C1–C7, T1, T2
├── mpe.py                  # MPE engine + limiting apertures
├── ael.py                  # AEL tables (CW)
├── classify.py             # classification engine + audit trail
├── hazards.py              # NOHD, OD, beam irradiance
└── reporting/render.py     # HTML report
tests/test_classification.py
```

## Roadmap

1. Pulsed engine: single-pulse / average / pulse-train criteria with C5, worst-case duration search
2. Condition 1 evaluation → 1M/2M determination
3. EN 207 (LB ratings) and EN 208 mapping from OD
4. Golden test corpus from published worked examples
5. Table data extracted to versioned data modules for the 60825-1 amendment cycle

## License

MIT — see [LICENSE](LICENSE).
