Spaces:
Sleeping
Sleeping
| """ | |
| 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'}, | |
| } | |