Skip to main content

PPE Recommendation System

Problem Statement

Businesses see PPE requirements in SDS documents but don't know how to translate generic requirements into actionable purchasing decisions:

Hand Protection:

  • "Chemical resistant nitrile rubber gloves" — What specific thickness (mil) is needed? Which brands meet the requirement?

Eye Protection:

  • "Safety glasses with side shields" vs "Chemical splash goggles" — When is each required? What ANSI rating is needed?

Respiratory Protection:

  • "Use in well-ventilated area" vs "Organic vapor respirator required" — What cartridge type? What APF rating?

Body Protection:

  • "Wear protective clothing" — Lab coat sufficient? Chemical apron? Full Tyvek coverall?

This creates a gap between regulatory compliance (having an SDS) and practical safety (using the right PPE).


Research Summary

OSHA Requirements for PPE Selection

According to OSHA's Personal Protective Equipment Standard (29 CFR 1910.138), hand protection is required when workers are at risk of:

  • Skin absorption of harmful substances
  • Severe cuts or lacerations
  • Abrasions, punctures, chemical burns
  • Thermal burns and temperature extremes

Gloves must be selected based on:

  1. Material being handled
  2. Particular hazard involved
  3. Suitability for the operation

Key Factors in Glove Selection

Per OSHA Glove Selection Guidelines:

FactorDescription
Type of chemicalsCAS numbers and chemical families
Nature of contactTotal immersion, splash, incidental
Duration of contactShort-term vs. continuous exposure
Breakthrough timeHow long before chemical permeates
Degradation ratingHow quickly material breaks down
Area requiring protectionHand only, forearm, full arm
Grip requirementsDry, wet, oily surfaces
Thermal protectionHot or cold materials

Chemical Resistance Rating Systems

ANSI/ISEA 105 Standard Ratings:

RatingBreakthrough Time
0< 10 minutes
1≥ 10 minutes
2≥ 30 minutes
3≥ 60 minutes
4≥ 120 minutes
5≥ 240 minutes
6≥ 480 minutes

DOE Qualitative Ratings:

  • VG - Very Good (recommended)
  • G - Good (acceptable)
  • F - Fair (use with caution)
  • P - Poor (not recommended)

Glove Material Properties

MaterialBest ForAvoid
NitrileOils, greases, acids, caustics, alcohols, chlorinated solventsStrong oxidizers, aromatic solvents, ketones, acetates
ButylKetones, esters, aldehydes, strong acids/bases, peroxidesAliphatic/aromatic hydrocarbons, gasoline
NeopreneStraight-chain hydrocarbons, alcohols, fats/oils, FreonStrong oxidizers
PVCAcids, bases, fats, oilsMost organic solvents
VitonAromatic/aliphatic hydrocarbons, chlorinated solventsKetones, esters
Latex (Natural Rubber)Aqueous solutions, alcohols, dilute acids/basesOils, organic solvents

Important Warnings

From OSHA PPE Guidelines:

"Improper selection of gloves may give workers a false sense of security since chemicals may penetrate the 'protection' without it showing any signs of failure."

"No fabric (including leather, neoprene, latex) can provide protection against all potential hazards."


Eye and Face Protection

OSHA Requirements (29 CFR 1910.133)

Per OSHA Eye and Face Protection Standard, employers must provide eye/face protection when exposed to:

  • Flying particles
  • Molten metal
  • Liquid chemicals, acids, or caustic liquids
  • Chemical gases or vapors
  • Potentially injurious light radiation

Eye Protection Types

TypeANSI MarkingBest ForLimitations
Safety GlassesZ87+Impact hazards, dust, flying particlesNO splash protection, gaps around frame
Safety Glasses w/ Side ShieldsZ87+Impact + side protectionStill has gaps, not for liquids
Chemical Splash GogglesZ87+ D3Chemical splashes, liquid hazardsCan fog, less comfortable
Indirect Vent GogglesZ87+ D4Chemical vapors + splashMaximum chemical protection
Face ShieldZ87+Secondary protection over glasses/gogglesNOT standalone eye protection
Full-Face RespiratorZ87.1 + NIOSHCombined eye + respiratoryMost protection, requires fit testing

ANSI Z87.1 Markings Explained

MarkingMeaning
Z87+High-impact rated (required for all safety eyewear)
D3Droplet and splash protection
D4Dust protection
D5Fine dust protection
W + shade #Welding filter shade

Eye Protection Selection Matrix

Hazard TypeMinimum ProtectionRecommended
Dust, particlesSafety glasses w/ side shieldsGoggles for heavy dust
Liquid splash (non-corrosive)Chemical splash goggles (D3)Face shield + goggles
Corrosive liquids (acids/bases)Indirect vent goggles (D3/D4)Face shield + goggles
Chemical vapors/fumesIndirect vent goggles (D4)Full-face respirator
High-velocity impactSafety glasses (Z87+)Face shield + glasses

Key Selection Factors

  1. Physical state of chemical: Liquid splash vs. vapor/gas
  2. Corrosivity: Corrosive chemicals require sealed goggles
  3. Splash probability: High splash risk = goggles + face shield
  4. GHS Hazard Codes:
    • H314 (Severe skin burns and eye damage) → Indirect vent goggles + face shield
    • H318 (Serious eye damage) → Chemical splash goggles minimum
    • H319 (Eye irritation) → Safety glasses with side shields

Respiratory Protection

OSHA Requirements (29 CFR 1910.134)

Per OSHA Respiratory Protection Standard, respiratory protection is required when:

  • Engineering controls are not feasible
  • Engineering controls are being implemented
  • Emergencies may occur

Assigned Protection Factors (APF)

The APF indicates the level of protection a respirator provides when properly fitted. From OSHA APF Guidelines:

Respirator TypeAPFUse When Exposure Is
Filtering Facepiece (N95, P100)10≤ 10x PEL
Half-Mask with Cartridges10≤ 10x PEL
Full-Face with Cartridges50≤ 50x PEL
Powered Air-Purifying (PAPR) Half-Mask50≤ 50x PEL
PAPR Full-Face/Hood1000≤ 1000x PEL
Supplied Air (SAR) Half-Mask10≤ 10x PEL
SAR Full-Face50≤ 50x PEL
SCBA (Self-Contained)10,000IDLH conditions

Calculating Required APF:

Required APF = Exposure Level ÷ Permissible Exposure Limit (PEL)
Example: 500 ppm exposure ÷ 50 ppm PEL = APF 10 minimum

Filter/Cartridge Classifications

Per NIOSH Filter Classifications:

Particulate Filters (N/R/P Series):

SeriesOil ResistanceEfficiency Options
N (Not oil resistant)NoneN95, N99, N100
R (Resistant to oil)LimitedR95, R99, R100
P (Oil Proof)YesP95, P99, P100
  • 95 = 95% filtration efficiency
  • 99 = 99% filtration efficiency
  • 100 = 99.97% efficiency (HEPA equivalent)

Chemical Cartridge Color Codes:

ColorProtection AgainstCommon Uses
BlackOrganic Vapors (OV)Solvents, paints, adhesives
WhiteAcid Gases (AG)Chlorine, hydrogen chloride, sulfur dioxide
YellowOrganic Vapors + Acid GasesCombination hazards
GreenAmmonia/MethylamineCleaning products, refrigerants
OliveMulti-Gas/VaporComplex chemical environments
OrangeDust/Mist/FumeParticulates (often w/ other cartridges)
Purple (Magenta)P100 ParticulateAsbestos, lead, pharmaceutical

Respirator Selection Matrix

Chemical TypePhysical FormRecommended Respirator
Dusts, fibersParticulateN95 or P100 filter
Oil-based mistsParticulateR95 or P100
Organic solventsVaporOV cartridge (black)
AcidsVapor/gasAcid gas cartridge (white)
FormaldehydeVaporFormaldehyde cartridge
AmmoniaGasAmmonia cartridge (green)
Mixed organic/acidVaporMulti-gas cartridge (olive)
Unknown/IDLHAnySCBA only

Key Selection Factors

  1. Contaminant identity: Required for cartridge selection
  2. Concentration level: Determines APF needed
  3. Oxygen level: < 19.5% O₂ requires supplied air
  4. Physical form: Particulate vs. vapor/gas
  5. Oil presence: Determines N/R/P series
  6. Exposure limit: OSHA PEL, NIOSH REL, or ACGIH TLV

Warning Signs Requiring Respirators

From SDS hazard statements:

  • H330/H331 (Fatal/Toxic if inhaled) → Full-face respirator minimum
  • H332 (Harmful if inhaled) → Half-mask with appropriate cartridge
  • H334 (Respiratory sensitization) → Consider supplied air
  • H335 (Respiratory irritation) → N95 or cartridge based on form

Skin and Body Protection

OSHA Requirements (29 CFR 1910.132)

Protective clothing is required when employees are exposed to hazards that can cause injury through absorption, physical contact, or contamination.

Protection Levels (EPA/OSHA)

LevelComponentsUse For
AFully encapsulated suit, SCBAUnknown hazards, high vapor/gas
BChemical splash suit, SCBAHigh liquid splash, known hazards
CChemical splash suit, APRKnown hazards, adequate APF
DWork uniform, safety glassesNuisance contamination only

Note: Most industrial/commercial settings use Level C or D

Body Protection Types

TypeMaterial OptionsBest ForLimitations
Lab CoatCotton, polyester, FRLight splash, particulatesAbsorbs liquids, no barrier
Chemical ApronPVC, neoprene, rubberFront splash protectionBack/arms exposed
Sleeve ProtectorsTyvek, PVCArm protection w/ apronLimited coverage
CoverallsCotton, FR, TyvekFull body particulateLimited chemical resistance
Chemical CoverallsTychem, SaranexFull body chemical splashHot, single-use
Encapsulated SuitTychem, VitonMaximum protectionExpensive, training required

Material Selection for Chemical Resistance

MaterialChemical ResistanceBest For
TyvekParticulates onlyDry chemicals, dusts, asbestos
Tychem QCLight splashGeneral chemical splash
Tychem 2000Most chemicalsModerate chemical exposure
Tychem 4000Concentrated chemicalsStrong acids/bases
Tychem 6000Highly toxic chemicalsToxic industrial chemicals
SaranexMany organicsSolvents, oils
NeopreneAcids, caustics, oilsChemical processing
VitonAromatics, chlorinatedPetrochemical

Body Protection Selection Matrix

Hazard TypeMinimum ProtectionEnhanced Protection
Dry particulatesLab coatTyvek coverall
Light liquid splashChemical apronTychem QC coverall
Corrosive splashChemical apron + sleevesTychem 2000+ coverall
Concentrated acids/basesTychem 4000 coverallLevel B suit
Toxic liquidsTychem 6000 coverallLevel A suit
Full immersionLevel A suitDouble suit

Key Selection Factors

  1. Chemical concentration: Higher = more resistant material
  2. Contact type: Splash vs. immersion vs. vapor
  3. Duration of exposure: Longer = more protection
  4. Body area at risk: Full body vs. specific areas
  5. GHS Hazard Codes:
    • H310 (Fatal in contact with skin) → Chemical coverall minimum
    • H311 (Toxic in contact with skin) → Chemical apron + sleeves
    • H312 (Harmful in contact with skin) → Lab coat + gloves
    • H314 (Severe skin burns) → Chemical-resistant coverall

Proposed System Architecture

Phase 1: Chemical Resistance Database (MVP)

Build a PPE Material Compatibility Database using data from:

Database Schema

-- ============================================================================
-- PPE Chemical Resistance Reference Table (Gloves)
-- Maps chemicals (by CAS number) to glove material compatibility
-- ============================================================================

CREATE TABLE ppe_glove_chemical_resistance (
resistance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cas_number VARCHAR(20) NOT NULL,
chemical_name VARCHAR(255) NOT NULL,
chemical_family VARCHAR(100), -- e.g., 'ketone', 'alcohol', 'acid'

-- Glove material ratings (VG=Very Good, G=Good, F=Fair, P=Poor, NR=Not Recommended)
nitrile_rating VARCHAR(10),
butyl_rating VARCHAR(10),
neoprene_rating VARCHAR(10),
pvc_rating VARCHAR(10),
viton_rating VARCHAR(10),
latex_rating VARCHAR(10),

-- Breakthrough times in minutes (per material)
nitrile_breakthrough_min INT,
butyl_breakthrough_min INT,
neoprene_breakthrough_min INT,
viton_breakthrough_min INT,

-- Recommended thickness (mil)
recommended_thickness_mil INT,

-- Metadata
source VARCHAR(255), -- e.g., 'OSHA', 'Ansell', 'North Safety'
notes TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP,

UNIQUE(cas_number)
);

CREATE INDEX idx_ppe_glove_resistance_cas ON ppe_glove_chemical_resistance(cas_number);

-- ============================================================================
-- Eye Protection Reference Table
-- Maps hazard types to eye protection requirements
-- ============================================================================

CREATE TABLE ppe_eye_protection_reference (
reference_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Hazard classification
hazard_type VARCHAR(50) NOT NULL, -- 'chemical_splash', 'chemical_vapor', 'dust', 'impact'
ghs_hazard_code VARCHAR(10), -- H314, H318, H319, etc.

-- Recommended protection
minimum_protection VARCHAR(50) NOT NULL, -- 'safety_glasses', 'splash_goggles', 'indirect_vent_goggles'
recommended_protection VARCHAR(50),
ansi_marking_required VARCHAR(20), -- 'Z87+', 'Z87+ D3', 'Z87+ D4'

-- Additional requirements
face_shield_recommended BOOLEAN DEFAULT FALSE,
full_face_respirator_option BOOLEAN DEFAULT FALSE,

-- Reasoning
selection_reasoning TEXT,

created_at TIMESTAMP DEFAULT NOW()
);

-- ============================================================================
-- Respiratory Protection Reference Table
-- Maps chemicals to respirator cartridge requirements
-- ============================================================================

CREATE TABLE ppe_respiratory_reference (
reference_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Chemical identification
cas_number VARCHAR(20),
chemical_name VARCHAR(255),
chemical_family VARCHAR(100), -- 'organic_solvent', 'acid', 'ammonia', 'particulate'

-- Exposure limits (for APF calculation)
osha_pel_ppm DECIMAL(10,4),
osha_pel_mgm3 DECIMAL(10,4),
niosh_rel_ppm DECIMAL(10,4),
niosh_rel_mgm3 DECIMAL(10,4),
niosh_idlh_ppm DECIMAL(10,4), -- Immediately Dangerous to Life/Health

-- Cartridge requirements
cartridge_type VARCHAR(50), -- 'organic_vapor', 'acid_gas', 'ammonia', 'multi_gas', 'particulate'
cartridge_color VARCHAR(20), -- 'black', 'white', 'green', 'yellow', 'olive', 'magenta'
particulate_filter VARCHAR(10), -- 'N95', 'P100', etc.

-- Physical form considerations
vapor_pressure_mmhg DECIMAL(10,4),
is_particulate BOOLEAN DEFAULT FALSE,
oil_present BOOLEAN DEFAULT FALSE,

-- Minimum respirator requirements
min_respirator_type VARCHAR(50), -- 'filtering_facepiece', 'half_mask', 'full_face', 'papr', 'scba'
min_apf INT, -- Minimum Assigned Protection Factor

-- Warnings
requires_supplied_air BOOLEAN DEFAULT FALSE, -- For low O2 or IDLH
special_considerations TEXT,

source VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP,

UNIQUE(cas_number)
);

CREATE INDEX idx_ppe_respiratory_cas ON ppe_respiratory_reference(cas_number);
CREATE INDEX idx_ppe_respiratory_family ON ppe_respiratory_reference(chemical_family);

-- ============================================================================
-- Body Protection Reference Table
-- Maps hazard types to body protection requirements
-- ============================================================================

CREATE TABLE ppe_body_protection_reference (
reference_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Hazard classification
hazard_type VARCHAR(50) NOT NULL, -- 'particulate', 'light_splash', 'corrosive', 'toxic', 'immersion'
ghs_hazard_code VARCHAR(10), -- H310, H311, H312, H314, etc.

-- Recommended protection
minimum_protection VARCHAR(50) NOT NULL, -- 'lab_coat', 'chemical_apron', 'tyvek_coverall', 'tychem_coverall'
recommended_protection VARCHAR(50),
protection_level VARCHAR(5), -- 'A', 'B', 'C', 'D'

-- Material requirements
min_material_type VARCHAR(50), -- 'cotton', 'tyvek', 'tychem_qc', 'tychem_2000', etc.

-- Selection reasoning
selection_reasoning TEXT,

created_at TIMESTAMP DEFAULT NOW()
);

-- ============================================================================
-- Body Protection Material Chemical Resistance
-- Maps chemicals to body protection material compatibility
-- ============================================================================

CREATE TABLE ppe_body_material_resistance (
resistance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cas_number VARCHAR(20) NOT NULL,
chemical_name VARCHAR(255) NOT NULL,

-- Material ratings (VG=Very Good, G=Good, F=Fair, P=Poor, NR=Not Recommended)
tyvek_rating VARCHAR(10),
tychem_qc_rating VARCHAR(10),
tychem_2000_rating VARCHAR(10),
tychem_4000_rating VARCHAR(10),
tychem_6000_rating VARCHAR(10),
saranex_rating VARCHAR(10),

-- Breakthrough times in minutes
tychem_2000_breakthrough_min INT,
tychem_4000_breakthrough_min INT,

source VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),

UNIQUE(cas_number)
);

-- ============================================================================
-- PPE Product Catalog (All Types)
-- Curated list of PPE products with specifications
-- ============================================================================

CREATE TABLE ppe_products (
product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Product identification
product_name VARCHAR(255) NOT NULL,
manufacturer VARCHAR(100) NOT NULL,
product_code VARCHAR(100),

-- PPE classification
ppe_category VARCHAR(50) NOT NULL, -- 'hand', 'eye', 'respiratory', 'body'
ppe_type VARCHAR(50) NOT NULL, -- 'gloves', 'goggles', 'respirator', 'coverall', etc.
ppe_subtype VARCHAR(50), -- e.g., 'chemical_splash_goggles', 'half_mask_respirator'

-- === GLOVE-SPECIFIC FIELDS ===
glove_material VARCHAR(50), -- 'nitrile', 'butyl', 'neoprene', etc.
glove_thickness_mil DECIMAL(4,1),
glove_length_inches DECIMAL(4,1),
glove_ansi_level INT, -- 0-6 per ANSI/ISEA 105

-- === EYE PROTECTION FIELDS ===
eye_ansi_marking VARCHAR(20), -- 'Z87+', 'Z87+ D3', 'Z87+ D4'
eye_lens_type VARCHAR(50), -- 'clear', 'tinted', 'anti_fog'
eye_has_side_shields BOOLEAN,
eye_indirect_vent BOOLEAN,

-- === RESPIRATOR FIELDS ===
respirator_type VARCHAR(50), -- 'filtering_facepiece', 'half_mask', 'full_face', 'papr'
respirator_apf INT, -- Assigned Protection Factor
respirator_cartridge_type VARCHAR(50), -- 'organic_vapor', 'acid_gas', 'multi_gas'
respirator_filter_rating VARCHAR(10), -- 'N95', 'P100', etc.
respirator_niosh_approved BOOLEAN DEFAULT TRUE,

-- === BODY PROTECTION FIELDS ===
body_coverage VARCHAR(50), -- 'apron', 'coverall', 'encapsulated'
body_material VARCHAR(50), -- 'tyvek', 'tychem_2000', etc.
body_protection_level VARCHAR(5), -- 'A', 'B', 'C', 'D'
body_seam_type VARCHAR(50), -- 'serged', 'bound', 'taped'

-- === COMMON FIELDS ===
certifications JSONB, -- Array of certifications
size_options VARCHAR(100), -- 'S, M, L, XL, 2XL'

-- Purchasing information
purchase_url VARCHAR(500),
price_range VARCHAR(50), -- e.g., '$10-15/pair', '$50-75/box'
pack_quantity INT,

-- Availability
is_active BOOLEAN DEFAULT TRUE,

-- Metadata
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP
);

CREATE INDEX idx_ppe_products_category ON ppe_products(ppe_category);
CREATE INDEX idx_ppe_products_type ON ppe_products(ppe_type);

-- ============================================================================
-- PPE Recommendations (generated per SDS)
-- Stores calculated recommendations for each SDS based on composition
-- ============================================================================

CREATE TABLE ppe_sds_recommendations (
recommendation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sds_id UUID NOT NULL REFERENCES chemiq_sds_documents(sds_id),

-- === HAND PROTECTION ===
glove_material VARCHAR(50),
glove_thickness_mil INT,
glove_ansi_level INT,
glove_reasoning TEXT,
glove_confidence DECIMAL(3,2),

-- === EYE PROTECTION ===
eye_protection_type VARCHAR(50), -- 'safety_glasses', 'splash_goggles', 'indirect_vent_goggles', 'face_shield'
eye_ansi_marking VARCHAR(20),
eye_face_shield_required BOOLEAN DEFAULT FALSE,
eye_reasoning TEXT,
eye_confidence DECIMAL(3,2),

-- === RESPIRATORY PROTECTION ===
respirator_type VARCHAR(50), -- 'none', 'n95', 'half_mask', 'full_face', 'papr', 'scba'
respirator_cartridge VARCHAR(50),
respirator_filter VARCHAR(10),
respirator_apf_required INT,
respirator_reasoning TEXT,
respirator_confidence DECIMAL(3,2),

-- === BODY PROTECTION ===
body_protection_type VARCHAR(50), -- 'lab_coat', 'chemical_apron', 'coverall', 'encapsulated'
body_material VARCHAR(50),
body_protection_level VARCHAR(5),
body_reasoning TEXT,
body_confidence DECIMAL(3,2),

-- === OVERALL ===
overall_confidence DECIMAL(3,2), -- 0.00 - 1.00
generated_at TIMESTAMP DEFAULT NOW(),
generated_by VARCHAR(50), -- 'algorithm', 'ai', 'manual'
last_reviewed_at TIMESTAMP,
reviewed_by UUID,

UNIQUE(sds_id)
);

-- ============================================================================
-- GHS Hazard Code to PPE Mapping
-- Quick lookup for hazard statement → PPE requirements
-- ============================================================================

CREATE TABLE ppe_ghs_hazard_mapping (
mapping_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

ghs_code VARCHAR(10) NOT NULL, -- H314, H318, H330, etc.
hazard_statement TEXT,
hazard_category VARCHAR(50), -- 'skin', 'eye', 'inhalation', 'ingestion'

-- PPE requirements triggered by this hazard
eye_protection_min VARCHAR(50),
eye_protection_recommended VARCHAR(50),
hand_protection_min VARCHAR(50),
respirator_required BOOLEAN,
respirator_type_min VARCHAR(50),
body_protection_min VARCHAR(50),

notes TEXT,
created_at TIMESTAMP DEFAULT NOW(),

UNIQUE(ghs_code)
);

-- Populate common GHS hazard mappings
INSERT INTO ppe_ghs_hazard_mapping (ghs_code, hazard_statement, hazard_category, eye_protection_min, eye_protection_recommended, hand_protection_min, respirator_required, body_protection_min) VALUES
('H310', 'Fatal in contact with skin', 'skin', 'splash_goggles', 'face_shield', 'chemical_resistant', TRUE, 'chemical_coverall'),
('H311', 'Toxic in contact with skin', 'skin', 'splash_goggles', 'splash_goggles', 'chemical_resistant', FALSE, 'chemical_apron'),
('H312', 'Harmful in contact with skin', 'skin', 'safety_glasses', 'splash_goggles', 'chemical_resistant', FALSE, 'lab_coat'),
('H314', 'Causes severe skin burns and eye damage', 'skin', 'indirect_vent_goggles', 'face_shield', 'chemical_resistant', FALSE, 'chemical_coverall'),
('H315', 'Causes skin irritation', 'skin', 'safety_glasses', 'safety_glasses', 'general_purpose', FALSE, 'lab_coat'),
('H317', 'May cause an allergic skin reaction', 'skin', 'safety_glasses', 'safety_glasses', 'chemical_resistant', FALSE, 'lab_coat'),
('H318', 'Causes serious eye damage', 'eye', 'splash_goggles', 'face_shield', 'general_purpose', FALSE, NULL),
('H319', 'Causes serious eye irritation', 'eye', 'safety_glasses', 'splash_goggles', 'general_purpose', FALSE, NULL),
('H330', 'Fatal if inhaled', 'inhalation', 'full_face_respirator', 'full_face_respirator', 'chemical_resistant', TRUE, 'chemical_coverall'),
('H331', 'Toxic if inhaled', 'inhalation', 'splash_goggles', 'full_face_respirator', 'chemical_resistant', TRUE, 'chemical_apron'),
('H332', 'Harmful if inhaled', 'inhalation', 'safety_glasses', 'splash_goggles', 'general_purpose', TRUE, 'lab_coat'),
('H334', 'May cause allergy or asthma symptoms if inhaled', 'inhalation', 'splash_goggles', 'splash_goggles', 'chemical_resistant', TRUE, 'lab_coat'),
('H335', 'May cause respiratory irritation', 'inhalation', 'safety_glasses', 'safety_glasses', 'general_purpose', TRUE, NULL);

Recommendation Algorithms

# ============================================================================
# GLOVE RECOMMENDATION ALGORITHM
# ============================================================================

def get_glove_recommendation(composition: List[SDSComposition]) -> GloveRecommendation:
"""
Analyze SDS composition and recommend appropriate glove material.

Algorithm:
1. Look up each ingredient's CAS number in resistance database
2. For each glove material, find the WORST rating among all ingredients
3. Recommend the material with the best worst-case rating
4. Calculate minimum required thickness based on ingredients
"""

materials = ['nitrile', 'butyl', 'neoprene', 'pvc', 'viton', 'latex']
material_scores = {m: [] for m in materials}

rating_values = {'VG': 4, 'G': 3, 'F': 2, 'P': 1, 'NR': 0}

for ingredient in composition:
if not ingredient.cas_number:
continue

resistance = db.query(PPEGloveChemicalResistance).filter(
PPEGloveChemicalResistance.cas_number == ingredient.cas_number
).first()

if resistance:
for material in materials:
rating = getattr(resistance, f'{material}_rating')
if rating:
material_scores[material].append(rating_values.get(rating, 0))

# Find best material (highest minimum score across all ingredients)
best_material = None
best_min_score = -1

for material, scores in material_scores.items():
if scores:
min_score = min(scores)
if min_score > best_min_score:
best_min_score = min_score
best_material = material

score_to_rating = {4: 'VG', 3: 'G', 2: 'F', 1: 'P', 0: 'NR'}

return GloveRecommendation(
material=best_material,
rating=score_to_rating.get(best_min_score, 'Unknown'),
thickness_mil=8 if best_min_score <= 2 else 4,
confidence=best_min_score / 4.0 if best_min_score >= 0 else 0
)


# ============================================================================
# EYE PROTECTION RECOMMENDATION ALGORITHM
# ============================================================================

def get_eye_protection_recommendation(
hazard_info: SDSHazardInfo,
composition: List[SDSComposition]
) -> EyeProtectionRecommendation:
"""
Recommend eye protection based on GHS hazard statements and chemical properties.

Algorithm:
1. Check for eye-specific hazard codes (H314, H318, H319)
2. Check for corrosive/irritant ingredients
3. Determine if liquid splash or vapor exposure is likely
4. Recommend minimum ANSI marking based on hazards
"""

# Priority levels for eye protection
protection_priority = {
'full_face_respirator': 5,
'indirect_vent_goggles': 4,
'splash_goggles': 3,
'safety_glasses_side_shields': 2,
'safety_glasses': 1,
}

recommended_protection = 'safety_glasses'
face_shield_needed = False
reasoning = []

# Check GHS hazard statements
hazard_statements = hazard_info.hazard_statements or []

for statement in hazard_statements:
# Look up hazard code in mapping table
mapping = db.query(PPEGHSHazardMapping).filter(
PPEGHSHazardMapping.ghs_code == extract_hazard_code(statement)
).first()

if mapping and mapping.eye_protection_min:
if protection_priority.get(mapping.eye_protection_min, 0) > \
protection_priority.get(recommended_protection, 0):
recommended_protection = mapping.eye_protection_min
reasoning.append(f"Required for {mapping.ghs_code}: {mapping.hazard_statement}")

if mapping.eye_protection_recommended == 'face_shield':
face_shield_needed = True

# Check for corrosive chemicals in composition
for ingredient in composition:
if ingredient.is_hazardous and 'corrosive' in (ingredient.ingredient_hazards or []):
if protection_priority.get('indirect_vent_goggles', 0) > \
protection_priority.get(recommended_protection, 0):
recommended_protection = 'indirect_vent_goggles'
reasoning.append(f"Corrosive ingredient: {ingredient.chemical_name}")
face_shield_needed = True

# Determine ANSI marking
ansi_marking_map = {
'safety_glasses': 'Z87+',
'safety_glasses_side_shields': 'Z87+',
'splash_goggles': 'Z87+ D3',
'indirect_vent_goggles': 'Z87+ D4',
'full_face_respirator': 'Z87.1 + NIOSH',
}

return EyeProtectionRecommendation(
protection_type=recommended_protection,
ansi_marking=ansi_marking_map.get(recommended_protection, 'Z87+'),
face_shield_required=face_shield_needed,
reasoning='; '.join(reasoning) or 'Standard eye protection for chemical handling',
confidence=0.8 if reasoning else 0.5
)


# ============================================================================
# RESPIRATORY PROTECTION RECOMMENDATION ALGORITHM
# ============================================================================

def get_respiratory_recommendation(
hazard_info: SDSHazardInfo,
composition: List[SDSComposition],
exposure_controls: SDSExposureControls
) -> RespiratoryRecommendation:
"""
Recommend respiratory protection based on chemical hazards and exposure limits.

Algorithm:
1. Check for inhalation hazard codes (H330, H331, H332, H334, H335)
2. Determine if particulate or vapor hazard
3. Look up cartridge requirements for each ingredient
4. Calculate required APF based on exposure limits
5. Recommend appropriate respirator type
"""

# Priority levels for respirators
respirator_priority = {
'none': 0,
'n95': 1,
'half_mask_ov': 2,
'half_mask_multi': 3,
'full_face': 4,
'papr': 5,
'scba': 6,
}

recommended_type = 'none'
cartridge_type = None
filter_rating = None
min_apf = 1
reasoning = []

# Check GHS hazard statements for inhalation hazards
hazard_statements = hazard_info.hazard_statements or []

for statement in hazard_statements:
code = extract_hazard_code(statement)

# Map hazard codes to respirator requirements
if code in ['H330', 'H331']: # Fatal/Toxic if inhaled
recommended_type = 'full_face'
min_apf = 50
reasoning.append(f"{code}: Toxic inhalation hazard - full-face respirator required")
elif code in ['H332', 'H334', 'H335']: # Harmful/Irritating if inhaled
if respirator_priority.get('half_mask_ov', 0) > respirator_priority.get(recommended_type, 0):
recommended_type = 'half_mask_ov'
min_apf = 10
reasoning.append(f"{code}: Inhalation hazard - respirator recommended")

# Determine cartridge type based on chemical composition
cartridge_requirements = set()

for ingredient in composition:
if not ingredient.cas_number:
continue

resp_ref = db.query(PPERespiratoryReference).filter(
PPERespiratoryReference.cas_number == ingredient.cas_number
).first()

if resp_ref:
if resp_ref.cartridge_type:
cartridge_requirements.add(resp_ref.cartridge_type)

if resp_ref.is_particulate:
filter_rating = 'P100' if resp_ref.oil_present else 'N95'

if resp_ref.requires_supplied_air:
recommended_type = 'scba'
min_apf = 10000
reasoning.append(f"Supplied air required for {ingredient.chemical_name}")

# Determine final cartridge type
if 'organic_vapor' in cartridge_requirements and 'acid_gas' in cartridge_requirements:
cartridge_type = 'multi_gas' # Olive
elif 'organic_vapor' in cartridge_requirements:
cartridge_type = 'organic_vapor' # Black
elif 'acid_gas' in cartridge_requirements:
cartridge_type = 'acid_gas' # White
elif 'ammonia' in cartridge_requirements:
cartridge_type = 'ammonia' # Green

# Check SDS-specified respiratory requirements
if exposure_controls and exposure_controls.ppe:
sds_resp = exposure_controls.ppe.respiratory_protection
if sds_resp and 'SCBA' in sds_resp.upper():
recommended_type = 'scba'
reasoning.append("SDS specifies SCBA")
elif sds_resp and 'supplied air' in sds_resp.lower():
recommended_type = 'scba'
reasoning.append("SDS specifies supplied air")

return RespiratoryRecommendation(
respirator_type=recommended_type,
cartridge_type=cartridge_type,
filter_rating=filter_rating,
min_apf=min_apf,
reasoning='; '.join(reasoning) or 'Standard respiratory protection assessment',
confidence=0.8 if reasoning else 0.5
)


# ============================================================================
# BODY PROTECTION RECOMMENDATION ALGORITHM
# ============================================================================

def get_body_protection_recommendation(
hazard_info: SDSHazardInfo,
composition: List[SDSComposition]
) -> BodyProtectionRecommendation:
"""
Recommend body/skin protection based on GHS hazards and chemical properties.

Algorithm:
1. Check for skin hazard codes (H310, H311, H312, H314, H315, H317)
2. Determine severity of skin contact hazard
3. Look up material resistance for each ingredient
4. Recommend appropriate protection level
"""

# Priority levels for body protection
protection_priority = {
'none': 0,
'lab_coat': 1,
'chemical_apron': 2,
'tyvek_coverall': 3,
'tychem_qc_coverall': 4,
'tychem_2000_coverall': 5,
'tychem_4000_coverall': 6,
'level_b_suit': 7,
'level_a_suit': 8,
}

recommended_protection = 'lab_coat'
recommended_material = 'cotton'
protection_level = 'D'
reasoning = []

# Check GHS hazard statements
hazard_statements = hazard_info.hazard_statements or []

for statement in hazard_statements:
code = extract_hazard_code(statement)

mapping = db.query(PPEGHSHazardMapping).filter(
PPEGHSHazardMapping.ghs_code == code
).first()

if mapping and mapping.body_protection_min:
current_priority = protection_priority.get(recommended_protection, 0)
new_priority = protection_priority.get(mapping.body_protection_min, 0)

if new_priority > current_priority:
recommended_protection = mapping.body_protection_min
reasoning.append(f"{code}: {mapping.hazard_statement}")

# Check material resistance for composition
for ingredient in composition:
if not ingredient.cas_number:
continue

body_resistance = db.query(PPEBodyMaterialResistance).filter(
PPEBodyMaterialResistance.cas_number == ingredient.cas_number
).first()

if body_resistance:
# Find best-rated material
materials = ['tyvek', 'tychem_qc', 'tychem_2000', 'tychem_4000', 'tychem_6000']
rating_values = {'VG': 4, 'G': 3, 'F': 2, 'P': 1, 'NR': 0}

for material in materials:
rating = getattr(body_resistance, f'{material}_rating', None)
if rating and rating_values.get(rating, 0) >= 3:
recommended_material = material
break

# Map protection to level
if 'level_a' in recommended_protection or 'level_b' in recommended_protection:
protection_level = recommended_protection.split('_')[1].upper()
elif 'tychem' in recommended_protection:
protection_level = 'C'
else:
protection_level = 'D'

return BodyProtectionRecommendation(
protection_type=recommended_protection,
material=recommended_material,
protection_level=protection_level,
reasoning='; '.join(reasoning) or 'Standard body protection for chemical handling',
confidence=0.8 if reasoning else 0.5
)


# ============================================================================
# COMBINED PPE RECOMMENDATION
# ============================================================================

def get_full_ppe_recommendation(sds_detail: SDSDocumentDetail) -> PPERecommendation:
"""
Generate comprehensive PPE recommendations for all protection types.
"""

glove_rec = get_glove_recommendation(sds_detail.composition)
eye_rec = get_eye_protection_recommendation(sds_detail.hazard_info, sds_detail.composition)
resp_rec = get_respiratory_recommendation(
sds_detail.hazard_info,
sds_detail.composition,
sds_detail.exposure_controls
)
body_rec = get_body_protection_recommendation(sds_detail.hazard_info, sds_detail.composition)

# Calculate overall confidence
confidences = [glove_rec.confidence, eye_rec.confidence, resp_rec.confidence, body_rec.confidence]
overall_confidence = sum(confidences) / len(confidences)

return PPERecommendation(
gloves=glove_rec,
eye_protection=eye_rec,
respiratory=resp_rec,
body_protection=body_rec,
overall_confidence=overall_confidence
)

Phase 2: AI-Powered Recommendations

Use an LLM to generate contextual recommendations with explanations:

async def get_ai_ppe_recommendations(sds_detail: SDSDocumentDetail) -> PPERecommendation:
"""
Use LLM to generate specific PPE product recommendations
based on SDS composition and PPE requirements.
"""

composition_text = "\n".join([
f"- {c.chemical_name} (CAS: {c.cas_number}, {c.concentration_text or 'unknown %'})"
for c in sds_detail.composition
])

prompt = f"""
Based on this SDS information, recommend specific PPE products:

Product: {sds_detail.product_name}
Manufacturer: {sds_detail.manufacturer}

Composition:
{composition_text}

Current PPE Requirements from SDS:
- Eye: {sds_detail.exposure_controls.ppe.eye_protection or 'Not specified'}
- Hand: {sds_detail.exposure_controls.ppe.hand_protection or 'Not specified'}
- Respiratory: {sds_detail.exposure_controls.ppe.respiratory_protection or 'Not specified'}
- Skin: {sds_detail.exposure_controls.ppe.skin_protection or 'Not specified'}

Hazard Statements:
{', '.join(sds_detail.hazard_info.hazard_statements or [])}

Please provide specific recommendations in JSON format:
{{
"gloves": {{
"material": "nitrile/butyl/neoprene/etc",
"thickness_mil": 8,
"reasoning": "explanation based on chemical composition"
}},
"eye_protection": {{
"type": "safety glasses/chemical splash goggles/face shield",
"reasoning": "explanation"
}},
"respiratory": {{
"type": "N95/P100/organic vapor cartridge/etc",
"reasoning": "explanation"
}},
"body_protection": {{
"type": "lab coat/chemical-resistant apron/full coverall",
"reasoning": "explanation"
}}
}}
"""

# Call Claude/OpenAI API
response = await llm_client.complete(prompt)
return parse_ppe_recommendation(response)

Phase 3: Product Catalog Integration

Partner with PPE distributors who have APIs:

SupplierAPI AvailabilityNotes
GraingerYesLarge industrial supplier, extensive catalog
UlineLimitedGood for bulk purchases
Amazon BusinessYesWide selection, Prime shipping
MSC DirectYesIndustrial supplies, technical support
Fisher ScientificYesLab-grade PPE

This enables:

  • Real-time product availability
  • Price comparison across suppliers
  • Direct purchase links
  • Inventory management integration

UI Design

SDSInfoCard Enhancement

Add a "PPE Shopping Guide" section to the existing SDS card:

{/* PPE Shopping Guide */}
{hasPPE && ppeRecommendations && (
<div className="mt-4 pt-4 border-t border-border-light">
<div className="flex items-center justify-between mb-3">
<p className="field-label font-medium flex items-center gap-2">
<ShoppingCart className="w-4 h-4 text-accent-green" />
PPE Shopping Guide
</p>
<button className="text-xs text-accent-blue hover:underline">
View All Recommendations
</button>
</div>

{/* Glove Recommendation Card */}
<div className="p-4 bg-surface-light rounded-lg">
<div className="flex items-start gap-3">
<Hand className="w-8 h-8 text-accent-orange" />
<div className="flex-1">
<p className="font-medium">
Recommended: {ppeRecommendations.gloves.material} Gloves
({ppeRecommendations.gloves.thickness_mil}+ mil)
</p>
<p className="text-sm text-text-muted mt-1">
{ppeRecommendations.gloves.reasoning}
</p>
<div className="mt-2 flex gap-2 flex-wrap">
<span className="badge-success text-xs">
{ppeRecommendations.gloves.rating} Resistance
</span>
<span className="badge-info text-xs">
ANSI Level {ppeRecommendations.gloves.ansi_level}
</span>
</div>

{/* Product suggestions */}
{ppeRecommendations.gloves.products && (
<div className="mt-3 space-y-2">
<p className="text-xs font-semibold text-text-muted uppercase">
Suggested Products
</p>
{ppeRecommendations.gloves.products.map(product => (
<a
key={product.id}
href={product.purchase_url}
target="_blank"
className="flex items-center justify-between p-2 bg-white rounded border hover:border-accent-blue"
>
<div>
<p className="text-sm font-medium">{product.name}</p>
<p className="text-xs text-text-muted">{product.manufacturer}</p>
</div>
<div className="text-right">
<p className="text-sm font-medium text-accent-green">
{product.price_range}
</p>
<ExternalLink className="w-3 h-3 text-text-muted" />
</div>
</a>
))}
</div>
)}
</div>
</div>
</div>

{/* Similar cards for eye, respiratory, body protection */}
</div>
)}

Dedicated PPE Recommendations Page

Create a full page at /chemiq/inventory/{id}/ppe with:

  1. Summary Card - Overall protection level needed
  2. Detailed Recommendations - Per-category with reasoning
  3. Product Comparison Table - Compare suggested products
  4. Purchase Integration - Add to cart / bulk order
  5. Training Materials - Links to proper donning/doffing procedures

Implementation Roadmap

Phase 1: Static Recommendations (2-3 sprints)

Deliverables:

  • Chemical resistance lookup table (~500 common chemicals)
  • Basic recommendation algorithm
  • UI showing "Recommended Glove Material" based on composition
  • Info tooltips explaining material properties

Data Sources:

  • Parse OSHA Glove Selection Chart PDF
  • Digitize Ansell Chemical Resistance Guide
  • Import from PubChem GHS data

Phase 2: Product Catalog (2 sprints)

Deliverables:

  • Curated PPE product database (50-100 products)
  • Link materials to specific products
  • Show "Suggested Products" with purchase links
  • Admin interface to manage product catalog

Phase 3: AI Enhancement (2 sprints)

Deliverables:

  • LLM-powered contextual explanations
  • Handle complex mixtures intelligently
  • Generate custom training materials
  • Natural language Q&A about PPE requirements

Phase 4: Supplier Integration (3+ sprints)

Deliverables:

  • API integration with 1-2 suppliers
  • Real-time pricing and availability
  • Purchase workflow integration
  • Order tracking and history

Data Sources

Free/Open Data

SourceURLContent
PubChem GHShttps://pubchem.ncbi.nlm.nih.gov/ghs/GHS classifications for millions of chemicals
OSHAhttps://www.osha.gov/personal-protective-equipmentExposure limits, PPE requirements
NIOSHhttps://www.cdc.gov/niosh/Occupational exposure data

Manufacturer Data (Requires Parsing)

SourceContent
Ansell Chemical Resistance GuideComprehensive glove material ratings
North/Honeywell ChartsAdditional chemical coverage
Showa Glove GuideAsian chemical coverage
Best ManufacturingSpecialty chemicals

Commercial APIs

ServiceCostFeatures
ChemRADARSubscriptionGHS classification search
3E CompanyEnterpriseFull SDS management
VelocityEHSEnterpriseChemical management platform

Chemical Database Integration

PPE recommendations are significantly enhanced when combined with external chemical database information. See Chemical Database Integration for the complete implementation strategy.

How Enriched Data Improves PPE Selection

PPE TypeSDS Data OnlyWith Chemical Database Enrichment
Gloves"Chemical resistant gloves" → Generic recommendationCAS → chemical_family → Specific material (e.g., Butyl for ketones)
Respiratory"Use respiratory protection" → Generic respiratorvapor_pressure + osha_pel → Calculated APF → Specific cartridge type
Eye"Wear eye protection" → Safety glassesghs_classification → H-codes → Goggles vs face shield decision
Body"Protective clothing" → Generic recommendationChemical toxicity data → Protection level A/B/C/D

Data Flow: SDS Parsing → Chemical Enrichment → PPE

SDS Upload ──► Parse Section 3 ──► Extract CAS Numbers


Check PubChem Cache ──► Miss? ──► Queue Background Fetch


Get Enriched Data:
• chemical_family (for glove material)
• vapor_pressure_mmhg (for respiratory)
• osha_pel_ppm (for APF calculation)
• niosh_idlh_ppm (for urgency level)


Enhanced PPE Recommendation

Integration During SDS Processing (Background Job)

When an SDS is uploaded and processed by the background job:

  1. Parse SDS - Extract all 16 sections via LLM
  2. Store Composition - Save CAS numbers to chemiq_sds_composition
  3. Queue Chemical Enrichment - For each CAS number not in cache:
    • Add to chemiq_chemical_fetch_queue
    • Background worker fetches from PubChem
    • Store in chemiq_pubchem_cache
  4. Generate PPE Recommendations - Using both SDS data and enriched chemical data
  5. Store Recommendations - Save to ppe_sds_recommendations table
# Simplified integration in SDS processing service
async def process_sds_with_ppe(db: Session, sds_id: UUID):
# 1. Parse SDS (existing)
parsed_data = await parse_sds_document(sds_id)

# 2. Extract composition (existing)
composition = parsed_data.get('section3_composition', [])

# 3. Enrich chemicals (NEW - see chemical_database_integration.md)
enrichment_service = ChemicalEnrichmentService(db)
cas_numbers = [c.cas_number for c in composition if c.cas_number]
enriched_data = await enrichment_service.get_bulk_chemical_data(cas_numbers)

# 4. Generate enhanced PPE recommendations
ppe_recommendation = await get_full_ppe_recommendation(
sds_detail=parsed_data,
enriched_chemicals=enriched_data # NEW: chemical database data
)

# 5. Store recommendations
await save_ppe_recommendations(db, sds_id, ppe_recommendation)

Chemical Family to Glove Material Mapping

The chemical_family field from PubChem enables precise glove selection:

Chemical FamilyPrimary MaterialSecondary MaterialAvoid
ketoneButylVitonNitrile, Latex
alcoholNitrileNeoprenePVC
aromaticVitonNitrile (limited)Latex, Neoprene
chlorinatedVitonButylNitrile, Neoprene
inorganic_acidButylNeopreneLeather
organic_acidNitrileNeopreneLatex
amineButylNeopreneLatex
isocyanateButylVitonAll others
epoxyNitrileButylLatex
petroleumNitrileNeopreneLatex

Regulatory Limits for Respiratory Selection

The osha_pel_ppm and vapor_pressure_mmhg fields enable APF calculation:

def calculate_required_apf(
vapor_pressure_mmhg: float,
osha_pel_ppm: float,
expected_concentration_ppm: float = None
) -> int:
"""
Calculate minimum APF based on chemical properties.

Higher vapor pressure = more airborne = higher APF needed
Lower PEL = more toxic = higher APF needed
"""
if expected_concentration_ppm and osha_pel_ppm:
# Direct calculation if concentration known
hazard_ratio = expected_concentration_ppm / osha_pel_ppm

if hazard_ratio <= 10:
return 10 # Half-mask APR
elif hazard_ratio <= 50:
return 50 # Full-face APR
elif hazard_ratio <= 1000:
return 1000 # PAPR
else:
return 10000 # SCBA

# Heuristic based on vapor pressure when concentration unknown
if vapor_pressure_mmhg > 100: # High volatility
return 50 # Full-face minimum
elif vapor_pressure_mmhg > 10: # Moderate volatility
return 10 # Half-mask acceptable
else:
return 10 # Low volatility, half-mask okay

Important Considerations

Per CloudSDS research on AI limitations, any PPE recommendations must include:

Disclaimer: These recommendations are provided as guidance only and do not replace professional judgment. Actual PPE selection depends on specific use conditions, duration of exposure, and task requirements. Consult with an EHS professional for workplace-specific requirements. Always follow manufacturer guidelines and applicable OSHA regulations.

Limitations

  1. Cannot replace EHS professionals - Hazard classification judgments require expertise
  2. Regulations change - Must be updated as OSHA/GHS standards evolve
  3. Site-specific factors - Ventilation, exposure duration, task type matter
  4. Mixture complexity - Some combinations have synergistic effects

Quality Assurance

  • Human review of AI-generated recommendations before display
  • Regular audits of chemical resistance database
  • User feedback mechanism for incorrect recommendations
  • Version tracking for regulatory compliance

References

Hand Protection

  1. OSHA Personal Protective Equipment Overview
  2. OSHA Glove Selection Chart
  3. Ansell Chemical Glove Resistance Guide
  4. ANSI/ISEA 105 Hand Protection Standard

Eye Protection

  1. OSHA Eye and Face Protection Standard (29 CFR 1910.133)
  2. ANSI Z87.1 Eye and Face Protection Standard

Respiratory Protection

  1. OSHA Respiratory Protection Standard (29 CFR 1910.134)
  2. OSHA Respirator Selection eTool
  3. OSHA Assigned Protection Factors Publication
  4. NIOSH Respirator Requirements for Selected Chemicals
  5. 3M Respirator Selection Guide

Body Protection

  1. OSHA PPE General Requirements (29 CFR 1910.132)
  2. DuPont Tychem Chemical Resistance Guide

General Resources

  1. PubChem GHS Classification Summary
  2. ChemRADAR GHS Classification Tool
  3. CloudSDS - Myths of SDS Automation
  1. Chemical Database Integration - PubChem caching strategy and SDS parsing integration