Skip to main content
Glama
puran-water

Corrosion Engineering MCP Server

by puran-water
csv_loaders.py11.4 kB
""" CSV Data Loaders for Authoritative Corrosion Data This module loads all corrosion engineering data from CSV files instead of hardcoded dictionaries. All CSV files contain full citations to authoritative sources (ASTM standards, NORSOK, NRL, etc.). NO hardcoded data - everything loaded from version-controlled CSV files. CSV Files: - materials_compositions.csv - Material compositions (ASTM A240, B443, etc.) - astm_g48_cpt_data.csv - Critical Pitting Temperature data (ASTM G48-11) - astm_g82_galvanic_series.csv - Galvanic series (ASTM G82-98 via NRL) All loaders are lazy (data loaded on first access) for performance. """ import csv from pathlib import Path from typing import Dict, Optional from dataclasses import dataclass import logging logger = logging.getLogger(__name__) # Location of CSV data files DATA_DIR = Path(__file__).parent @dataclass class MaterialComposition: """ Material composition from CSV file. Source: materials_compositions.csv (ASTM A240, B443, B152, etc.) """ UNS: str common_name: str Cr_wt_pct: float Mo_wt_pct: float N_wt_pct: float Ni_wt_pct: float Fe_bal: bool = True density_kg_m3: float = 8000.0 grade_type: str = "austenitic" n_electrons: int = 2 source: str = "ASTM" # Cache for loaded data (lazy loading) _MATERIALS_CACHE: Optional[Dict[str, MaterialComposition]] = None _CPT_DATA_CACHE: Optional[Dict[str, Dict]] = None _GALVANIC_SERIES_CACHE: Optional[Dict[str, float]] = None _ORR_LIMITS_CACHE: Optional[Dict[str, float]] = None _CHLORIDE_THRESHOLD_CACHE: Optional[Dict[str, float]] = None _TEMP_COEFFICIENT_CACHE: Optional[Dict[str, float]] = None def load_materials_from_csv() -> Dict[str, MaterialComposition]: """ Load material compositions from CSV file. Returns: Dictionary mapping material name to MaterialComposition Example: >>> materials = load_materials_from_csv() >>> ss316 = materials["316L"] >>> ss316.Cr_wt_pct 16.5 >>> ss316.source 'ASTM A240' """ global _MATERIALS_CACHE if _MATERIALS_CACHE is not None: return _MATERIALS_CACHE csv_file = DATA_DIR / "materials_compositions.csv" if not csv_file.exists(): raise FileNotFoundError( f"Materials CSV not found: {csv_file}. " f"Expected file: materials_compositions.csv" ) materials = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: mat = MaterialComposition( UNS=row['UNS'], common_name=row['common_name'], Cr_wt_pct=float(row['Cr_wt_pct']), Ni_wt_pct=float(row['Ni_wt_pct']), Mo_wt_pct=float(row['Mo_wt_pct']), N_wt_pct=float(row['N_wt_pct']), density_kg_m3=float(row['density_kg_m3']), grade_type=row['grade_type'], n_electrons=int(row['n_electrons']), Fe_bal=row['Fe_bal'].lower() == 'true', source=row['source'], ) # Store by common name (primary key) materials[row['common_name']] = mat except (KeyError, ValueError) as e: logger.warning(f"Failed to parse material row: {row}. Error: {e}") continue logger.info(f"Loaded {len(materials)} materials from {csv_file}") _MATERIALS_CACHE = materials return materials def load_cpt_data_from_csv() -> Dict[str, Dict]: """ Load Critical Pitting Temperature (CPT) data from CSV file. Returns: Dictionary mapping material name to CPT data dict Example: >>> cpt_data = load_cpt_data_from_csv() >>> cpt_data["316L"] {'CPT_C': 15, 'CCT_C': 5, 'test_solution': '6% FeCl3', 'source': 'ASTM G48-11'} """ global _CPT_DATA_CACHE if _CPT_DATA_CACHE is not None: return _CPT_DATA_CACHE csv_file = DATA_DIR / "astm_g48_cpt_data.csv" if not csv_file.exists(): raise FileNotFoundError( f"CPT data CSV not found: {csv_file}. " f"Expected file: astm_g48_cpt_data.csv" ) cpt_data = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: material = row['material'] cpt_data[material] = { 'CPT_C': int(row['CPT_C']), 'CCT_C': int(row['CCT_C']), 'test_solution': row['test_solution'], 'source': row['source'], } except (KeyError, ValueError) as e: logger.warning(f"Failed to parse CPT row: {row}. Error: {e}") continue logger.info(f"Loaded CPT data for {len(cpt_data)} materials from {csv_file}") _CPT_DATA_CACHE = cpt_data return cpt_data def load_galvanic_series_from_csv() -> Dict[str, float]: """ Load galvanic series potentials from CSV file. Returns: Dictionary mapping material name to potential (V vs SCE) Note: Potentials are in SCE reference. To convert to SHE, add 0.241V. Example: >>> galv_series = load_galvanic_series_from_csv() >>> galv_series["Carbon Steel"] -0.610 # V vs SCE """ global _GALVANIC_SERIES_CACHE if _GALVANIC_SERIES_CACHE is not None: return _GALVANIC_SERIES_CACHE csv_file = DATA_DIR / "astm_g82_galvanic_series.csv" if not csv_file.exists(): raise FileNotFoundError( f"Galvanic series CSV not found: {csv_file}. " f"Expected file: astm_g82_galvanic_series.csv" ) galvanic_series = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: material = row['material'] # Use SCE values (original ASTM G82 reference) potential_sce = float(row['potential_sce_V']) galvanic_series[material] = potential_sce except (KeyError, ValueError) as e: logger.warning(f"Failed to parse galvanic series row: {row}. Error: {e}") continue logger.info(f"Loaded galvanic series for {len(galvanic_series)} materials from {csv_file}") _GALVANIC_SERIES_CACHE = galvanic_series return galvanic_series def load_orr_diffusion_limits_from_csv() -> Dict[str, float]: """ Load ORR diffusion limits from CSV file. Returns: Dictionary mapping condition name to diffusion-limited current density (A/m²) Example: >>> orr_limits = load_orr_diffusion_limits_from_csv() >>> orr_limits["seawater_25C"] 5.0 # A/m² """ global _ORR_LIMITS_CACHE if _ORR_LIMITS_CACHE is not None: return _ORR_LIMITS_CACHE csv_file = DATA_DIR / "orr_diffusion_limits.csv" if not csv_file.exists(): raise FileNotFoundError( f"ORR diffusion limits CSV not found: {csv_file}. " f"Expected file: orr_diffusion_limits.csv" ) orr_limits = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: condition = row['condition'] i_lim = float(row['i_lim_A_m2']) orr_limits[condition] = i_lim except (KeyError, ValueError) as e: logger.warning(f"Failed to parse ORR limit row: {row}. Error: {e}") continue logger.info(f"Loaded ORR diffusion limits for {len(orr_limits)} conditions from {csv_file}") _ORR_LIMITS_CACHE = orr_limits return orr_limits def load_chloride_thresholds_from_csv() -> Dict[str, float]: """ Load chloride thresholds from CSV file. Returns: Dictionary mapping material name to chloride threshold (mg/L) at 25°C, pH 7.0 Example: >>> thresholds = load_chloride_thresholds_from_csv() >>> thresholds["316L"] 250.0 # mg/L """ global _CHLORIDE_THRESHOLD_CACHE if _CHLORIDE_THRESHOLD_CACHE is not None: return _CHLORIDE_THRESHOLD_CACHE csv_file = DATA_DIR / "iso18070_chloride_thresholds.csv" if not csv_file.exists(): raise FileNotFoundError( f"Chloride thresholds CSV not found: {csv_file}. " f"Expected file: iso18070_chloride_thresholds.csv" ) thresholds = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: material = row['material'] threshold = float(row['threshold_25C_mg_L']) thresholds[material] = threshold except (KeyError, ValueError) as e: logger.warning(f"Failed to parse chloride threshold row: {row}. Error: {e}") continue logger.info(f"Loaded chloride thresholds for {len(thresholds)} materials from {csv_file}") _CHLORIDE_THRESHOLD_CACHE = thresholds return thresholds def load_temperature_coefficients_from_csv() -> Dict[str, float]: """ Load temperature coefficients from CSV file. Returns: Dictionary mapping grade_type to temperature coefficient (/°C) Example: >>> coeffs = load_temperature_coefficients_from_csv() >>> coeffs["austenitic"] 0.05 # /°C """ global _TEMP_COEFFICIENT_CACHE if _TEMP_COEFFICIENT_CACHE is not None: return _TEMP_COEFFICIENT_CACHE csv_file = DATA_DIR / "iso18070_temperature_coefficients.csv" if not csv_file.exists(): raise FileNotFoundError( f"Temperature coefficients CSV not found: {csv_file}. " f"Expected file: iso18070_temperature_coefficients.csv" ) coefficients = {} with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: grade_type = row['grade_type'] coefficient = float(row['temp_coefficient_per_C']) coefficients[grade_type] = coefficient except (KeyError, ValueError) as e: logger.warning(f"Failed to parse temperature coefficient row: {row}. Error: {e}") continue logger.info(f"Loaded temperature coefficients for {len(coefficients)} grade types from {csv_file}") _TEMP_COEFFICIENT_CACHE = coefficients return coefficients def clear_caches(): """Clear all cached CSV data (useful for testing or reloading).""" global _MATERIALS_CACHE, _CPT_DATA_CACHE, _GALVANIC_SERIES_CACHE global _ORR_LIMITS_CACHE, _CHLORIDE_THRESHOLD_CACHE, _TEMP_COEFFICIENT_CACHE _MATERIALS_CACHE = None _CPT_DATA_CACHE = None _GALVANIC_SERIES_CACHE = None _ORR_LIMITS_CACHE = None _CHLORIDE_THRESHOLD_CACHE = None _TEMP_COEFFICIENT_CACHE = None logger.info("Cleared all CSV data caches") __all__ = [ "MaterialComposition", "load_materials_from_csv", "load_cpt_data_from_csv", "load_galvanic_series_from_csv", "load_orr_diffusion_limits_from_csv", "load_chloride_thresholds_from_csv", "load_temperature_coefficients_from_csv", "clear_caches", ]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/puran-water/corrosion-engineering-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server