package iace // Minimum-distance library — Task #18. // // Anchor source: OSHA 29 CFR 1910 Subpart O (US Federal Public Domain, // 17 U.S.C. §105). The values below are reproduced verbatim from the // Federal Code; conversions to metric are mathematical and carry no // copyright. Engineering rounding to safe-side mm values is BreakPilot's // recommendation and labelled as such. // // EU norm equivalents (EN ISO 13857, EN 349, EN 13855, EN 1010) are // referenced by identifier only — no values are reproduced, because // DIN/Beuth retain copyright on the wording. The DINComparisonNote // field carries a human-curated judgement on whether the EU norm is // stricter / looser / equivalent — this is a qualitative observation // about a publicly available document, not a copy of its text. // // See LICENSE_RULES.md and project_attribution_strategy.md for the // licensing logic. The OSHA values are R1 (verbatim public domain); // the recommended metric values are BreakPilot engineering output (R3 // own-work). DIN references are R3 identifier-only. // MinimumDistanceUnit denotes the original unit system of the source. type MinimumDistanceUnit string const ( UnitInch MinimumDistanceUnit = "inch" UnitFoot MinimumDistanceUnit = "foot" UnitMeter MinimumDistanceUnit = "meter" UnitMM MinimumDistanceUnit = "mm" ) // MinimumDistance is the data contract for a single safety-distance rule. // It can be (a) a fixed gap value, (b) a distance range, or (c) a formula // like OSHA's Ds = 63 in/s × Ts (hand-speed constant). type MinimumDistance struct { ID string `json:"id"` // MD_OSHA_001 // Source identifier — full CFR citation or norm reference. SourceCFR string `json:"source_cfr,omitempty"` // "29 CFR §1910.217(c)(1)(i)" SourceTable string `json:"source_table,omitempty"` // "Table O-10" License string `json:"license"` // "US Federal Public Domain" LicenseRule int `json:"license_rule"` // 1 / 2 / 3 (see LICENSE_RULES.md) // Original verbatim value in the source's own unit. OriginalUnit MinimumDistanceUnit `json:"original_unit"` OriginalValue float64 `json:"original_value,omitempty"` OriginalMin float64 `json:"original_min,omitempty"` OriginalMax float64 `json:"original_max,omitempty"` // Exact conversion to mm — no engineering rounding. ExactMM float64 `json:"exact_mm,omitempty"` ExactMinMM float64 `json:"exact_min_mm,omitempty"` ExactMaxMM float64 `json:"exact_max_mm,omitempty"` // Engineering-recommended metric value with safe-side rounding. // For minimum distances: rounded up. For maximum opening widths: // rounded down. RecommendedMM int `json:"recommended_mm,omitempty"` RecommendedMinMM int `json:"recommended_min_mm,omitempty"` RecommendedMaxMM int `json:"recommended_max_mm,omitempty"` RoundingNote string `json:"rounding_note,omitempty"` // Optional formula constant (e.g. OSHA hand-speed 63 in/s). FormulaInchPerSecond float64 `json:"formula_inch_per_second,omitempty"` FormulaMMPerSecond float64 `json:"formula_mm_per_second,omitempty"` FormulaDescription string `json:"formula_description,omitempty"` Context string `json:"context"` // "Point of Operation Guarding mechanical presses" BodyPart string `json:"body_part,omitempty"` // "finger" / "hand" / "head" / "foot" / "body" HazardTags []string `json:"hazard_tags,omitempty"` // [crush_point, cutting_part, ...] // EU norm cross-reference — IDENTIFIER ONLY, no values reproduced. EUNormHints []EUNormHint `json:"eu_norm_hints,omitempty"` } // EUNormHint references an EU standard by identifier without reproducing // any value or text from it. The DINComparisonNote is a human-curated // qualitative judgement (stricter / equivalent / looser) — not a copy. type EUNormHint struct { Norm string `json:"norm"` // "EN ISO 13857" Section string `json:"section,omitempty"` // "Tab. 4, Schutz gegen Hineingreifen" DINComparisonNote string `json:"din_comparison_note,omitempty"` } // GetOSHAMinimumDistances returns the verbatim OSHA values for // machine-guarding distances. All values are US Federal Public Domain // (17 U.S.C. §105). Engineering rounding is BreakPilot's safe-side // recommendation; OSHA values themselves are unchanged. func GetOSHAMinimumDistances() []MinimumDistance { return []MinimumDistance{ // OSHA Table O-10 row 1 — verbatim values, mathematical conversion, // safe-side rounded engineering recommendation. { ID: "MD_OSHA_O10_R1", SourceCFR: "29 CFR §1910.217(c)(1)(i)", SourceTable: "Table O-10 row 1", License: "US Federal Public Domain (17 U.S.C. §105)", LicenseRule: 1, OriginalUnit: UnitInch, OriginalMin: 0.5, OriginalMax: 1.5, OriginalValue: 0.25, ExactMinMM: 12.7, ExactMaxMM: 38.1, ExactMM: 6.35, RecommendedMinMM: 15, RecommendedMaxMM: 40, RecommendedMM: 6, RoundingNote: "Distance auf 5-mm-Raster aufgerundet, opening auf 1-mm-Raster abgerundet (konservativ in beide Richtungen).", Context: "Point-of-Operation Guarding bei mechanischen Pressen", BodyPart: "finger", HazardTags: []string{"crush_point", "cutting_part"}, EUNormHints: []EUNormHint{ {Norm: "EN ISO 13857", Section: "Tab. 4 (Hineingreifen)", DINComparisonNote: "Andere Methodik (Reichweitenmodell). Unabhaengig pruefen — Werte koennen abweichen."}, }, }, // OSHA Table O-10 row 4 — used as a worked example in the strategy // discussion. Distance 3.5-5.5 in, opening max 5/8 in. { ID: "MD_OSHA_O10_R4", SourceCFR: "29 CFR §1910.217(c)(1)(i)", SourceTable: "Table O-10 row 4", License: "US Federal Public Domain (17 U.S.C. §105)", LicenseRule: 1, OriginalUnit: UnitInch, OriginalMin: 3.5, OriginalMax: 5.5, OriginalValue: 0.625, ExactMinMM: 88.9, ExactMaxMM: 139.7, ExactMM: 15.875, RecommendedMinMM: 90, RecommendedMaxMM: 140, RecommendedMM: 15, RoundingNote: "Distance 88.9→90 (+1.1 mm), 139.7→140 (+0.3 mm) aufgerundet; Opening 15.875→15 (-0.875 mm) abgerundet.", Context: "Point-of-Operation Guarding bei mechanischen Pressen", BodyPart: "finger", HazardTags: []string{"crush_point", "cutting_part"}, EUNormHints: []EUNormHint{ {Norm: "EN ISO 13857", Section: "Tab. 4 (Hineingreifen)", DINComparisonNote: "Andere Methodik (Reichweitenmodell). Compliance-Annotation pflegen."}, }, }, // OSHA §1910.212(a)(5) — fan blade guards. Verbatim 1/2 inch. { ID: "MD_OSHA_212_FAN", SourceCFR: "29 CFR §1910.212(a)(5)", License: "US Federal Public Domain (17 U.S.C. §105)", LicenseRule: 1, OriginalUnit: UnitInch, OriginalValue: 0.5, ExactMM: 12.7, RecommendedMM: 12, RoundingNote: "Luefterblatt-Schutzgitter: max. Spaltoeffnung 1/2 in = 12.7 mm. Konservativ auf 12 mm abgerundet.", Context: "Lüfterblätter unter 7 ft (2.13 m) Höhe", BodyPart: "finger", HazardTags: []string{"rotating_part", "cutting_part"}, EUNormHints: []EUNormHint{ {Norm: "EN ISO 13857", Section: "Tab. 4", DINComparisonNote: "DIN-Wert pruefen."}, }, }, // OSHA §1910.217 Hand-Speed Constant — formula Ds = 63 in/s × Ts { ID: "MD_OSHA_217_PSDI", SourceCFR: "29 CFR §1910.217 (Ds = 63 in/s × Ts)", License: "US Federal Public Domain (17 U.S.C. §105)", LicenseRule: 1, OriginalUnit: UnitInch, FormulaInchPerSecond: 63.0, FormulaMMPerSecond: 1600.2, FormulaDescription: "Hand-Speed-Konstante 63 in/s ≈ 1600 mm/s. " + "Ds (Mindestabstand) = 63 × Ts (Stoppzeit Presse in Sekunden).", Context: "PSDI Presence-Sensing Device Initiation und Two-Hand-Trip", BodyPart: "hand", HazardTags: []string{"crush_point", "high_speed"}, EUNormHints: []EUNormHint{ {Norm: "EN 13855", Section: "Sicherheitsabstaende", DINComparisonNote: "EN 13855 nutzt andere Konstante (1600 mm/s ≈ identisch); EU-Norm unabhaengig pruefen."}, }, }, } } // MinimumDistanceNote is the ready-to-print licensing posture for the // minimum-distance reference — shown by the API so an auditor sees WHY the // OSHA values may be reproduced while the EU norms are reference-only. const MinimumDistanceNote = "OSHA-Werte (29 CFR 1910) sind US-Public-Domain " + "(17 U.S.C. §105) und werden verbatim wiedergegeben; die mm-Umrechnung ist " + "mathematisch, die sicherheitsseitige Rundung ist BreakPilot-Empfehlung. " + "EU-Normen (EN ISO 13857/13854/13855, EN 349) werden nur per Kennung " + "referenziert — keine Werte reproduziert." // GetMinimumDistanceByID returns the OSHA distance entry with the given ID. func GetMinimumDistanceByID(id string) (MinimumDistance, bool) { for _, md := range GetOSHAMinimumDistances() { if md.ID == id { return md, true } } return MinimumDistance{}, false } // MeasureDistanceLink connects a protective measure to the OSHA distance // entries that anchor it. Relation makes the nature of the link explicit so // the join is honest rather than implying every measure's prose IS the OSHA // value: // - "value_source" — the OSHA value is the source the measure's own // mm figure is derived from (it appears in the measure prose). // - "public_domain_crossref" — the measure is dimensioned by an EU norm; the // OSHA entry is offered as the public-domain pendant for independent check. type MeasureDistanceLink struct { MeasureID string `json:"measure_id"` MeasureName string `json:"measure_name,omitempty"` // resolved from the library for name-based UI matching DistanceIDs []string `json:"distance_ids"` Relation string `json:"relation"` Note string `json:"note,omitempty"` } const ( LinkValueSource = "value_source" LinkCrossRef = "public_domain_crossref" ) // AllMeasureDistanceLinks returns the curated measure→OSHA-distance links. // Conservative on purpose: only measures whose CONTEXT genuinely matches an // OSHA entry are linked. Measures whose "OSHA" citation is loose or carries an // ISO value (e.g. M340 robot teach speed, M368 air-receiver wall) are NOT // linked — that would imply a public-domain anchor that does not exist. func AllMeasureDistanceLinks() []MeasureDistanceLink { links := []MeasureDistanceLink{ { MeasureID: "M600", DistanceIDs: []string{"MD_OSHA_217_PSDI"}, Relation: LinkValueSource, Note: "Hand-Speed-Konstante 1.600 mm/s (63 in/s) ist die Obergrenze, aus der die Kriechgeschwindigkeit am Endanschlag abgeleitet ist.", }, { MeasureID: "M254", DistanceIDs: []string{"MD_OSHA_O10_R1", "MD_OSHA_O10_R4"}, Relation: LinkCrossRef, Note: "OSHA Table O-10 (Point-of-Operation an mechanischen Pressen) als Public-Domain-Pendant zur ISO-13855-Methode — Werte eigenstaendig pruefen.", }, { MeasureID: "M065", DistanceIDs: []string{"MD_OSHA_212_FAN"}, Relation: LinkCrossRef, Note: "OSHA §1910.212(a)(5) Luefterschutz (max. 12 mm Spaltoeffnung) als Public-Domain-Pendant zu ISO 13857.", }, } // Enrich each link with the measure name so a name-based UI (persisted // mitigations carry the name, not the ID) can match without a second lookup. lib := GetProtectiveMeasureLibrary() for i := range links { for _, m := range lib { if m.ID == links[i].MeasureID { links[i].MeasureName = m.Name break } } } return links } // LinksForMeasure returns the distance links declared for one measure. func LinksForMeasure(measureID string) []MeasureDistanceLink { var out []MeasureDistanceLink for _, l := range AllMeasureDistanceLinks() { if l.MeasureID == measureID { out = append(out, l) } } return out } // MinimumDistancesForMeasure resolves the OSHA distance entries linked to a // protective measure. This is the join that finally lets the OSHA mm values // "flow into" the measures (read-side), without mutating the measure object. func MinimumDistancesForMeasure(measureID string) []MinimumDistance { var out []MinimumDistance for _, l := range LinksForMeasure(measureID) { for _, id := range l.DistanceIDs { if md, ok := GetMinimumDistanceByID(id); ok { out = append(out, md) } } } return out } // MeasureIDByName resolves a protective-measure name to its catalog ID. Measure // names are unique across the library (verified by test), so a PERSISTED // mitigation — which stores the measure name verbatim but NOT the catalog ID — // can still be joined to its OSHA distance link without a DB schema change or a // re-seed. This is the bridge that lets a project's measures surface the anchor. func MeasureIDByName(name string) (string, bool) { for _, m := range GetProtectiveMeasureLibrary() { if m.Name == name { return m.ID, true } } return "", false } // MinimumDistancesForMeasureName resolves OSHA distances by measure NAME. func MinimumDistancesForMeasureName(name string) []MinimumDistance { if id, ok := MeasureIDByName(name); ok { return MinimumDistancesForMeasure(id) } return nil } // LinksForMeasureName resolves distance links by measure NAME. func LinksForMeasureName(name string) []MeasureDistanceLink { if id, ok := MeasureIDByName(name); ok { return LinksForMeasure(id) } return nil }