""" Configuration for CSH2 Web Analytics Dashboard Supports local (.env), Streamlit Cloud (st.secrets), and HuggingFace Spaces (env vars) deployment. """ import os from datetime import timedelta from zoneinfo import ZoneInfo import streamlit as st from dotenv import load_dotenv load_dotenv() # ── Timezone Configuration ────────────────────────────────────────────────── # The PLC logs in US/Eastern. The DB's `dateandtime` column is Eastern local # time, while `utc_full_timestamp` is the real UTC. The `cycle_periods` table # stores Eastern times incorrectly labeled as UTC (+00:00), requiring a +4h # correction for sensor data queries. All data (Mar-Sep 2025) falls within # EDT (UTC-4). We display all user-facing times in Eastern. DISPLAY_TZ = ZoneInfo("US/Eastern") DISPLAY_TZ_LABEL = "ET" # label for axis titles and UI CYCLE_PERIODS_UTC_OFFSET = timedelta(hours=4) # correction for cycle_periods timestamps def _get_secret(key, default=None): """Read from st.secrets (Streamlit Cloud) or os.environ/.env (local dev)""" try: return st.secrets[key] except (KeyError, FileNotFoundError, AttributeError): return os.getenv(key, default) # Database Configuration DB_CONFIG = { 'host': _get_secret('DB_HOST'), 'port': _get_secret('DB_PORT'), 'database': _get_secret('DB_NAME'), 'user': _get_secret('DB_USER'), 'password': _get_secret('DB_PASSWORD'), 'connect_timeout': 10, } ANTHROPIC_API_KEY = _get_secret('ANTHROPIC_API_KEY') # Database Tables TABLES = { 'raw': 'procdatafloattable', 'agg_1sec': 'procdatafloattable_utc_1sec', 'agg_15sec': 'procdatafloattable_utc_15sec', 'tag_metadata': 'procdatatagtable', 'events': 'allevent', 'fill_reports': 'fillreport', 'cooldown_periods': 'cooldown_periods', 'cycle_periods': 'cycle_periods', } # Smart table routing thresholds (seconds) TABLE_ROUTING = { 'raw_max_seconds': 3600, # <= 1 hour -> raw 10Hz 'agg_1sec_max_seconds': 86400, # <= 1 day -> 1sec aggregates # > 1 day -> 15sec aggregates } MAX_ROWS_RETURN = 500000 # Resolution name to table mapping (used by Advanced Data Explorer execution engine) RESOLUTION_MAP = { 'raw': TABLES['raw'], '1sec': TABLES['agg_1sec'], '15sec': TABLES['agg_15sec'], } # Key sensor tags with descriptions and units SENSOR_TAGS = { 'AmbTempAvg': {'description': 'Ambient Temperature Average', 'units': 'K', 'tagindex': 0}, 'AOV140': {'description': 'Valve Position AOV140', 'units': '%', 'tagindex': 1}, 'AvgPower': {'description': 'Average Power', 'units': 'kW', 'tagindex': 2}, 'CalcMass': {'description': 'Calculated Mass', 'units': 'kg', 'tagindex': 3}, 'DPT01T': {'description': 'Differential Pressure', 'units': 'bar', 'tagindex': 4}, 'DispTempAvg': {'description': 'Dispenser Temperature Average', 'units': 'K', 'tagindex': 5}, 'FD100': {'description': 'Flow Detector', 'units': '', 'tagindex': 6}, 'FT140': {'description': 'Flow Transmitter', 'units': 'kg/min', 'tagindex': 7}, 'FTTMP': {'description': 'Flow Transmitter Temperature', 'units': 'K', 'tagindex': 8}, 'GD100': {'description': 'Gas Detector', 'units': '', 'tagindex': 9}, 'H201T': {'description': 'H2O Temperature', 'units': 'K', 'tagindex': 10}, 'IR01T': {'description': 'IR Temperature', 'units': 'K', 'tagindex': 11}, 'LT200': {'description': 'Level Transmitter', 'units': '%', 'tagindex': 12}, 'MC130_VFD_Speed': {'description': 'Motor VFD Speed', 'units': 'RPM', 'tagindex': 13}, 'PowerConsumption': {'description': 'Power Consumption', 'units': 'kW', 'tagindex': 14}, 'PT01T': {'description': 'Cryotank Pressure', 'units': 'bar', 'tagindex': 15}, 'PT100': {'description': 'Pressure PT100', 'units': 'bar', 'tagindex': 16}, 'PT110': {'description': 'Inlet Pressure', 'units': 'bar', 'tagindex': 17}, 'PT130': {'description': 'Discharge Pressure', 'units': 'bar', 'tagindex': 18}, 'PT131': {'description': 'Discharge Pressure Alt', 'units': 'bar', 'tagindex': 19}, 'PT150': {'description': 'Pressure PT150', 'units': 'bar', 'tagindex': 20}, 'PT200': {'description': 'Pressure PT200', 'units': 'bar', 'tagindex': 21}, 'PT310': {'description': 'Pressure PT310', 'units': 'bar', 'tagindex': 22}, 'TT100': {'description': 'Temperature TT100', 'units': 'K', 'tagindex': 23}, 'TT110': {'description': 'Pump Feed Line Temperature', 'units': 'K', 'tagindex': 24}, 'TT120': {'description': 'Temperature TT120', 'units': 'K', 'tagindex': 25}, 'TT121': {'description': 'Temperature TT121', 'units': 'K', 'tagindex': 26}, 'TT130': {'description': 'Discharge Temperature', 'units': 'K', 'tagindex': 27}, 'TT131': {'description': 'Discharge Temperature Alt', 'units': 'K', 'tagindex': 28}, 'TT140': {'description': 'Temperature TT140', 'units': 'K', 'tagindex': 29}, 'MC130_PwrConsumption': {'description': 'Motor Power Consumption', 'units': 'kW', 'tagindex': 30}, 'PercentFill': {'description': 'Percent Fill', 'units': '%', 'tagindex': 31}, 'PeakPower': {'description': 'Peak Power', 'units': 'kW', 'tagindex': 32}, 'N2Density': {'description': 'N2 Density', 'units': 'kg/m3', 'tagindex': 33}, 'PS100': {'description': 'Pump Status', 'units': '', 'tagindex': 34}, 'H2Density': {'description': 'H2 Density', 'units': 'kg/m3', 'tagindex': 35}, 'FTTMP2': {'description': 'Flow Temp 2', 'units': 'K', 'tagindex': 54}, 'M130_BusV': {'description': 'Motor Bus Voltage', 'units': 'V', 'tagindex': 55}, 'M130_Current': {'description': 'Motor Current', 'units': 'A', 'tagindex': 56}, 'M130_Freq': {'description': 'Motor Frequency', 'units': 'Hz', 'tagindex': 58}, 'M130_PF': {'description': 'Motor Power Factor', 'units': '', 'tagindex': 61}, 'M130_Pwr': {'description': 'Motor Power', 'units': 'kW', 'tagindex': 62}, 'M130_PwrCon': {'description': 'Motor Power Consumption', 'units': 'kW', 'tagindex': 63}, 'M130_RPM': {'description': 'Motor RPM', 'units': 'RPM', 'tagindex': 66}, 'M130_Speed': {'description': 'Motor Speed', 'units': 'RPM', 'tagindex': 68}, 'M130_Torque': {'description': 'Motor Torque', 'units': 'Nm', 'tagindex': 70}, 'PT010': {'description': 'Pressure PT010', 'units': 'bar', 'tagindex': 72}, 'PT020': {'description': 'Pressure PT020', 'units': 'bar', 'tagindex': 73}, } # Sensor groups for quick selection SENSOR_GROUPS = { 'Pressure': ['PT01T', 'PT100', 'PT110', 'PT130', 'PT131', 'PT150', 'PT200', 'PT310', 'PT010', 'PT020'], 'Temperature': ['TT100', 'TT110', 'TT120', 'TT121', 'TT130', 'TT131', 'TT140', 'AmbTempAvg', 'DispTempAvg'], 'Flow': ['FT140', 'FD100', 'FTTMP', 'FTTMP2', 'CalcMass'], 'Motor/VFD': ['MC130_VFD_Speed', 'M130_Speed', 'M130_RPM', 'M130_Pwr', 'M130_Current', 'M130_Freq', 'M130_Torque', 'M130_BusV', 'M130_PF'], 'Power': ['AvgPower', 'PeakPower', 'PowerConsumption', 'MC130_PwrConsumption', 'M130_PwrCon'], 'Level & Density': ['LT200', 'PercentFill', 'N2Density', 'H2Density'], 'Status': ['PS100'], } # Motor speed tags (for testing cycle detection - try in order) MOTOR_SPEED_TAGS = ['M130_Speed', 'MC130_VFD_Speed', 'M130_RPM'] # Motor speed display multiplier: M130_Speed (tag 68) reads as a % signal (0-100). # Multiply by 5/3 to convert to actual RPM matching Grafana display. # e.g., M130_Speed=180 raw → 180 × 5/3 = 300 RPM. # Set to 1.0 to disable scaling. MOTOR_SPEED_DISPLAY_MULTIPLIER = 5 / 3 # Testing cycle detection parameters CYCLE_DETECTION = { 'idle_threshold': 10.0, # RPM below which motor is idle 'min_cycle_seconds': 60, # Minimum cycle duration 'smoothing_window': 5, # Rolling median window (in samples) 'max_gap_seconds': 120, # Max gap to merge adjacent cycles # Qualification: cycle must show real compression or flow 'min_peak_pressure_bar': 50.0, # PT130 must exceed this (real fills reach 300+ bar) 'min_pressure_rise_bar': 10.0, # PT130 must rise this much from initial 'min_avg_flow_kg_min': 0.05, # FT140 average must exceed this (real fills do 1+ kg/min) 'max_cycle_duration_hours': 8.0, # Reject multi-day noise spans (no real test > 8h) } # Key tags for cycle detail analysis CYCLE_DETAIL_TAGS = ['PT130', 'PT01T', 'PT110', 'TT110', 'TT130', 'FT140', 'M130_Speed', 'MC130_VFD_Speed', 'AOV140'] # Performance targets PERFORMANCE_TARGETS = { 'pressure': {'phase1': 500, 'h70': 700, 'ultimate': 1000}, 'flow': {'simplex': 2, 'triplex': 6}, 'efficiency': {'pump_gas_ratio': 0.95, 'seal_blowby': 0.03}, 'energy': {'specific_consumption': 0.5}, } # Testing phases TESTING_PHASES = { 'mechanical': {'description': 'Mechanical validation with LN2', 'status': 'completed', 'max_pressure': '937 bar'}, 'phase1a': {'description': 'Phase 1A H2 testing with LH2', 'status': 'completed', 'max_pressure': '495 bar'}, 'phase1b': {'description': 'Phase 1B ccH2 fills', 'status': 'completed', 'max_pressure': '495 bar'}, 'phase2': {'description': 'Phase 2 H35/H70 fills', 'status': 'planned', 'target_pressure': '700+ bar'}, }