Add comprehensive validation system and migrate to .env configuration
This commit includes: 1. Database Configuration Migration: - Migrated from DB.txt (Java JDBC) to .env (python-dotenv) - Added .env.example template with clear variable names - Updated database.py to use environment variables - Added python-dotenv>=1.0.0 to dependencies - Updated .gitignore to exclude sensitive files 2. Validation System (1,294 lines): - comparator.py: Statistical comparison with RMSE, correlation, tolerances - db_extractor.py: Database queries for all sensor types - validator.py: High-level validation orchestration - cli.py: Command-line interface for validation - README.md: Comprehensive validation documentation 3. Validation Features: - Compare Python vs MATLAB outputs from database - Support for all sensor types (RSN, Tilt, ATD) - Statistical metrics: max abs/rel diff, RMSE, correlation - Configurable tolerances (abs, rel, max) - Detailed validation reports - CLI and programmatic APIs 4. Examples and Documentation: - validate_example.sh: Bash script example - validate_example.py: Python programmatic example - Updated main README with validation section - Added validation workflow and troubleshooting guide Benefits: - ✅ No Java driver needed (native Python connectors) - ✅ Secure .env configuration (excluded from git) - ✅ Comprehensive validation against MATLAB - ✅ Statistical confidence in migration accuracy - ✅ Automated validation reports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
410
src/tilt/main.py
410
src/tilt/main.py
@@ -2,21 +2,385 @@
|
||||
Main Tilt sensor data processing module.
|
||||
|
||||
Entry point for tiltmeter sensor data elaboration.
|
||||
Similar structure to RSN module but for tilt/inclinometer sensors.
|
||||
Processes TLHR, BL, PL, KLHR and other tilt sensor types.
|
||||
"""
|
||||
|
||||
import time
|
||||
import logging
|
||||
from typing import Tuple
|
||||
from ..common.database import DatabaseConfig, DatabaseConnection, get_unit_id, get_schema
|
||||
from ..common.database import DatabaseConfig, DatabaseConnection, get_unit_id
|
||||
from ..common.logging_utils import setup_logger, log_elapsed_time
|
||||
from ..common.config import load_installation_parameters, load_calibration_data
|
||||
from .data_processing import (
|
||||
load_tilt_link_hr_data, define_tilt_link_hr_data,
|
||||
load_biaxial_link_data, define_biaxial_link_data,
|
||||
load_pendulum_link_data, define_pendulum_link_data,
|
||||
load_k_link_hr_data, define_k_link_hr_data
|
||||
)
|
||||
from .conversion import (
|
||||
convert_tilt_link_hr_data, convert_biaxial_link_data,
|
||||
convert_pendulum_link_data, convert_k_link_hr_data
|
||||
)
|
||||
from .averaging import (
|
||||
average_tilt_link_hr_data, average_biaxial_link_data,
|
||||
average_pendulum_link_data, average_k_link_hr_data
|
||||
)
|
||||
from .elaboration import (
|
||||
elaborate_tilt_link_hr_data, elaborate_biaxial_link_data,
|
||||
elaborate_pendulum_link_data, elaborate_k_link_hr_data
|
||||
)
|
||||
from .db_write import (
|
||||
write_tilt_link_hr_data, write_biaxial_link_data,
|
||||
write_pendulum_link_data, write_k_link_hr_data
|
||||
)
|
||||
|
||||
|
||||
def process_tlhr_sensors(conn, control_unit_id: str, chain: str, node_list: list,
|
||||
params: dict, logger: logging.Logger) -> bool:
|
||||
"""
|
||||
Process TLHR (Tilt Link High Resolution) sensors.
|
||||
|
||||
Args:
|
||||
conn: Database connection
|
||||
control_unit_id: Control unit identifier
|
||||
chain: Chain identifier
|
||||
node_list: List of TLHR node IDs
|
||||
params: Installation parameters
|
||||
logger: Logger instance
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
n_sensors = len(node_list)
|
||||
logger.info(f"Processing {n_sensors} TLHR sensors")
|
||||
|
||||
# Load calibration data
|
||||
calibration_data = load_calibration_data(control_unit_id, chain, 'TLHR', conn)
|
||||
|
||||
# Get parameters
|
||||
initial_date = params.get('initial_date')
|
||||
initial_time = params.get('initial_time')
|
||||
n_points = params.get('n_points_avg', 100)
|
||||
n_despike = params.get('n_despike', 5)
|
||||
temp_max = params.get('temp_max', 80.0)
|
||||
temp_min = params.get('temp_min', -30.0)
|
||||
|
||||
# Load raw data from database
|
||||
logger.info("Loading TLHR raw data from database")
|
||||
raw_data = load_tilt_link_hr_data(conn, control_unit_id, chain,
|
||||
initial_date, initial_time, node_list)
|
||||
|
||||
if raw_data is None or len(raw_data) == 0:
|
||||
logger.warning("No TLHR data found")
|
||||
return True
|
||||
|
||||
# Define data structure (handle NaN, despike, scale wrapping)
|
||||
logger.info("Structuring TLHR data")
|
||||
angle_data, timestamps, temperature, err_flag = define_tilt_link_hr_data(
|
||||
raw_data, n_sensors, n_despike, temp_max, temp_min
|
||||
)
|
||||
|
||||
if angle_data is None:
|
||||
logger.warning("TLHR data definition failed")
|
||||
return True
|
||||
|
||||
# Convert raw to physical units
|
||||
logger.info("Converting TLHR data")
|
||||
angle_converted, temperature_converted, err_flag = convert_tilt_link_hr_data(
|
||||
angle_data, temperature, calibration_data, n_sensors
|
||||
)
|
||||
|
||||
# Average with Gaussian smoothing
|
||||
logger.info(f"Averaging TLHR data with {n_points} points")
|
||||
angle_avg, temperature_avg, err_flag = average_tilt_link_hr_data(
|
||||
angle_converted, timestamps, temperature_converted, n_points
|
||||
)
|
||||
|
||||
# Elaborate (calculate displacements, differentials)
|
||||
logger.info("Elaborating TLHR data")
|
||||
x_global, y_global, z_global, x_local, y_local, z_local, \
|
||||
x_diff, y_diff, z_diff, err_flag = elaborate_tilt_link_hr_data(
|
||||
conn, control_unit_id, chain, n_sensors, angle_avg,
|
||||
temp_max, temp_min, temperature_avg, err_flag, params
|
||||
)
|
||||
|
||||
# Write to database
|
||||
logger.info("Writing TLHR data to database")
|
||||
write_tilt_link_hr_data(
|
||||
conn, control_unit_id, chain, x_global, y_global, z_global,
|
||||
x_local, y_local, z_local, x_diff, y_diff, z_diff,
|
||||
timestamps, node_list, err_flag
|
||||
)
|
||||
|
||||
logger.info(f"TLHR processing completed: {len(timestamps)} records")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing TLHR sensors: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
def process_bl_sensors(conn, control_unit_id: str, chain: str, node_list: list,
|
||||
params: dict, logger: logging.Logger) -> bool:
|
||||
"""
|
||||
Process BL (Biaxial Link) sensors.
|
||||
|
||||
Args:
|
||||
conn: Database connection
|
||||
control_unit_id: Control unit identifier
|
||||
chain: Chain identifier
|
||||
node_list: List of BL node IDs
|
||||
params: Installation parameters
|
||||
logger: Logger instance
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
n_sensors = len(node_list)
|
||||
logger.info(f"Processing {n_sensors} BL sensors")
|
||||
|
||||
# Load calibration data
|
||||
calibration_data = load_calibration_data(control_unit_id, chain, 'BL', conn)
|
||||
|
||||
# Get parameters
|
||||
initial_date = params.get('initial_date')
|
||||
initial_time = params.get('initial_time')
|
||||
n_points = params.get('n_points_avg', 100)
|
||||
n_despike = params.get('n_despike', 5)
|
||||
temp_max = params.get('temp_max', 80.0)
|
||||
temp_min = params.get('temp_min', -30.0)
|
||||
|
||||
# Load raw data
|
||||
logger.info("Loading BL raw data from database")
|
||||
raw_data = load_biaxial_link_data(conn, control_unit_id, chain,
|
||||
initial_date, initial_time, node_list)
|
||||
|
||||
if raw_data is None or len(raw_data) == 0:
|
||||
logger.warning("No BL data found")
|
||||
return True
|
||||
|
||||
# Define data structure
|
||||
logger.info("Structuring BL data")
|
||||
angle_data, timestamps, temperature, err_flag = define_biaxial_link_data(
|
||||
raw_data, n_sensors, n_despike, temp_max, temp_min
|
||||
)
|
||||
|
||||
if angle_data is None:
|
||||
logger.warning("BL data definition failed")
|
||||
return True
|
||||
|
||||
# Convert
|
||||
logger.info("Converting BL data")
|
||||
angle_converted, temperature_converted, err_flag = convert_biaxial_link_data(
|
||||
angle_data, temperature, calibration_data, n_sensors
|
||||
)
|
||||
|
||||
# Average
|
||||
logger.info(f"Averaging BL data with {n_points} points")
|
||||
angle_avg, temperature_avg, err_flag = average_biaxial_link_data(
|
||||
angle_converted, timestamps, temperature_converted, n_points
|
||||
)
|
||||
|
||||
# Elaborate
|
||||
logger.info("Elaborating BL data")
|
||||
x_global, y_global, z_global, x_diff, y_diff, z_diff, err_flag = \
|
||||
elaborate_biaxial_link_data(
|
||||
conn, control_unit_id, chain, n_sensors, angle_avg,
|
||||
temp_max, temp_min, temperature_avg, err_flag, params
|
||||
)
|
||||
|
||||
# Write to database
|
||||
logger.info("Writing BL data to database")
|
||||
write_biaxial_link_data(
|
||||
conn, control_unit_id, chain, x_global, y_global, z_global,
|
||||
x_diff, y_diff, z_diff, timestamps, node_list, err_flag
|
||||
)
|
||||
|
||||
logger.info(f"BL processing completed: {len(timestamps)} records")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing BL sensors: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
def process_pl_sensors(conn, control_unit_id: str, chain: str, node_list: list,
|
||||
params: dict, logger: logging.Logger) -> bool:
|
||||
"""
|
||||
Process PL (Pendulum Link) sensors.
|
||||
|
||||
Args:
|
||||
conn: Database connection
|
||||
control_unit_id: Control unit identifier
|
||||
chain: Chain identifier
|
||||
node_list: List of PL node IDs
|
||||
params: Installation parameters
|
||||
logger: Logger instance
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
n_sensors = len(node_list)
|
||||
logger.info(f"Processing {n_sensors} PL sensors")
|
||||
|
||||
# Load calibration data
|
||||
calibration_data = load_calibration_data(control_unit_id, chain, 'PL', conn)
|
||||
|
||||
# Get parameters
|
||||
initial_date = params.get('initial_date')
|
||||
initial_time = params.get('initial_time')
|
||||
n_points = params.get('n_points_avg', 100)
|
||||
n_despike = params.get('n_despike', 5)
|
||||
temp_max = params.get('temp_max', 80.0)
|
||||
temp_min = params.get('temp_min', -30.0)
|
||||
|
||||
# Load raw data
|
||||
logger.info("Loading PL raw data from database")
|
||||
raw_data = load_pendulum_link_data(conn, control_unit_id, chain,
|
||||
initial_date, initial_time, node_list)
|
||||
|
||||
if raw_data is None or len(raw_data) == 0:
|
||||
logger.warning("No PL data found")
|
||||
return True
|
||||
|
||||
# Define data structure
|
||||
logger.info("Structuring PL data")
|
||||
angle_data, timestamps, temperature, err_flag = define_pendulum_link_data(
|
||||
raw_data, n_sensors, n_despike, temp_max, temp_min
|
||||
)
|
||||
|
||||
if angle_data is None:
|
||||
logger.warning("PL data definition failed")
|
||||
return True
|
||||
|
||||
# Convert
|
||||
logger.info("Converting PL data")
|
||||
angle_converted, temperature_converted, err_flag = convert_pendulum_link_data(
|
||||
angle_data, temperature, calibration_data, n_sensors
|
||||
)
|
||||
|
||||
# Average
|
||||
logger.info(f"Averaging PL data with {n_points} points")
|
||||
angle_avg, temperature_avg, err_flag = average_pendulum_link_data(
|
||||
angle_converted, timestamps, temperature_converted, n_points
|
||||
)
|
||||
|
||||
# Elaborate
|
||||
logger.info("Elaborating PL data")
|
||||
x_global, y_global, z_global, x_diff, y_diff, z_diff, err_flag = \
|
||||
elaborate_pendulum_link_data(
|
||||
conn, control_unit_id, chain, n_sensors, angle_avg,
|
||||
temp_max, temp_min, temperature_avg, err_flag, params
|
||||
)
|
||||
|
||||
# Write to database
|
||||
logger.info("Writing PL data to database")
|
||||
write_pendulum_link_data(
|
||||
conn, control_unit_id, chain, x_global, y_global, z_global,
|
||||
x_diff, y_diff, z_diff, timestamps, node_list, err_flag
|
||||
)
|
||||
|
||||
logger.info(f"PL processing completed: {len(timestamps)} records")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing PL sensors: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
def process_klhr_sensors(conn, control_unit_id: str, chain: str, node_list: list,
|
||||
params: dict, logger: logging.Logger) -> bool:
|
||||
"""
|
||||
Process KLHR (K Link High Resolution) sensors.
|
||||
|
||||
Args:
|
||||
conn: Database connection
|
||||
control_unit_id: Control unit identifier
|
||||
chain: Chain identifier
|
||||
node_list: List of KLHR node IDs
|
||||
params: Installation parameters
|
||||
logger: Logger instance
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
n_sensors = len(node_list)
|
||||
logger.info(f"Processing {n_sensors} KLHR sensors")
|
||||
|
||||
# Load calibration data
|
||||
calibration_data = load_calibration_data(control_unit_id, chain, 'KLHR', conn)
|
||||
|
||||
# Get parameters
|
||||
initial_date = params.get('initial_date')
|
||||
initial_time = params.get('initial_time')
|
||||
n_points = params.get('n_points_avg', 100)
|
||||
n_despike = params.get('n_despike', 5)
|
||||
temp_max = params.get('temp_max', 80.0)
|
||||
temp_min = params.get('temp_min', -30.0)
|
||||
|
||||
# Load raw data
|
||||
logger.info("Loading KLHR raw data from database")
|
||||
raw_data = load_k_link_hr_data(conn, control_unit_id, chain,
|
||||
initial_date, initial_time, node_list)
|
||||
|
||||
if raw_data is None or len(raw_data) == 0:
|
||||
logger.warning("No KLHR data found")
|
||||
return True
|
||||
|
||||
# Define data structure
|
||||
logger.info("Structuring KLHR data")
|
||||
angle_data, timestamps, temperature, err_flag = define_k_link_hr_data(
|
||||
raw_data, n_sensors, n_despike, temp_max, temp_min
|
||||
)
|
||||
|
||||
if angle_data is None:
|
||||
logger.warning("KLHR data definition failed")
|
||||
return True
|
||||
|
||||
# Convert
|
||||
logger.info("Converting KLHR data")
|
||||
angle_converted, temperature_converted, err_flag = convert_k_link_hr_data(
|
||||
angle_data, temperature, calibration_data, n_sensors
|
||||
)
|
||||
|
||||
# Average
|
||||
logger.info(f"Averaging KLHR data with {n_points} points")
|
||||
angle_avg, temperature_avg, err_flag = average_k_link_hr_data(
|
||||
angle_converted, timestamps, temperature_converted, n_points
|
||||
)
|
||||
|
||||
# Elaborate
|
||||
logger.info("Elaborating KLHR data")
|
||||
x_global, y_global, z_global, x_diff, y_diff, z_diff, err_flag = \
|
||||
elaborate_k_link_hr_data(
|
||||
conn, control_unit_id, chain, n_sensors, angle_avg,
|
||||
temp_max, temp_min, temperature_avg, err_flag, params
|
||||
)
|
||||
|
||||
# Write to database
|
||||
logger.info("Writing KLHR data to database")
|
||||
write_k_link_hr_data(
|
||||
conn, control_unit_id, chain, x_global, y_global, z_global,
|
||||
x_diff, y_diff, z_diff, timestamps, node_list, err_flag
|
||||
)
|
||||
|
||||
logger.info(f"KLHR processing completed: {len(timestamps)} records")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing KLHR sensors: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
def process_tilt_chain(control_unit_id: str, chain: str) -> int:
|
||||
"""
|
||||
Main function to process Tilt chain data.
|
||||
|
||||
Supports sensor types: TLHR, BL, PL, KLHR
|
||||
|
||||
Args:
|
||||
control_unit_id: Control unit identifier
|
||||
chain: Chain identifier
|
||||
@@ -43,12 +407,13 @@ def process_tilt_chain(control_unit_id: str, chain: str) -> int:
|
||||
# Load node configuration
|
||||
logger.info("Loading tilt sensor configuration")
|
||||
|
||||
# Query for tilt sensor types (TL, TLH, TLHR, BL, PL, etc.)
|
||||
# Query for tilt sensor types
|
||||
query = """
|
||||
SELECT idTool, nodeID, nodeType, sensorModel
|
||||
FROM chain_nodes
|
||||
WHERE unitID = %s AND chain = %s
|
||||
AND nodeType IN ('TL', 'TLH', 'TLHR', 'TLHRH', 'BL', 'PL', 'RL', 'ThL', 'IPL', 'IPLHR', 'KL', 'KLHR', 'PT100')
|
||||
AND nodeType IN ('TLHR', 'BL', 'PL', 'KLHR', 'TL', 'TLH', 'TLHRH',
|
||||
'RL', 'ThL', 'IPL', 'IPLHR', 'KL', 'PT100')
|
||||
ORDER BY nodeOrder
|
||||
"""
|
||||
results = conn.execute_query(query, (unit_id, chain))
|
||||
@@ -73,34 +438,43 @@ def process_tilt_chain(control_unit_id: str, chain: str) -> int:
|
||||
params = load_installation_parameters(id_tool, conn)
|
||||
|
||||
# Process each sensor type
|
||||
# TL - Tilt Link (basic biaxial inclinometer)
|
||||
if 'TL' in tilt_sensors:
|
||||
logger.info(f"Processing {len(tilt_sensors['TL'])} TL sensors")
|
||||
# Load, convert, average, elaborate, write
|
||||
# Implementation would follow RSN pattern
|
||||
success = True
|
||||
|
||||
# TLHR - Tilt Link High Resolution
|
||||
# TLHR - Tilt Link High Resolution (most common)
|
||||
if 'TLHR' in tilt_sensors:
|
||||
logger.info(f"Processing {len(tilt_sensors['TLHR'])} TLHR sensors")
|
||||
# Similar processing
|
||||
if not process_tlhr_sensors(conn, control_unit_id, chain,
|
||||
tilt_sensors['TLHR'], params, logger):
|
||||
success = False
|
||||
|
||||
# BL - Biaxial Link
|
||||
if 'BL' in tilt_sensors:
|
||||
logger.info(f"Processing {len(tilt_sensors['BL'])} BL sensors")
|
||||
if not process_bl_sensors(conn, control_unit_id, chain,
|
||||
tilt_sensors['BL'], params, logger):
|
||||
success = False
|
||||
|
||||
# PL - Pendulum Link
|
||||
if 'PL' in tilt_sensors:
|
||||
logger.info(f"Processing {len(tilt_sensors['PL'])} PL sensors")
|
||||
if not process_pl_sensors(conn, control_unit_id, chain,
|
||||
tilt_sensors['PL'], params, logger):
|
||||
success = False
|
||||
|
||||
# Additional sensor types...
|
||||
# KLHR - K Link High Resolution
|
||||
if 'KLHR' in tilt_sensors:
|
||||
if not process_klhr_sensors(conn, control_unit_id, chain,
|
||||
tilt_sensors['KLHR'], params, logger):
|
||||
success = False
|
||||
|
||||
logger.info("Tilt processing completed successfully")
|
||||
# Log completion status
|
||||
if success:
|
||||
logger.info("Tilt processing completed successfully")
|
||||
else:
|
||||
logger.warning("Tilt processing completed with errors")
|
||||
|
||||
# Log elapsed time
|
||||
elapsed = time.time() - start_time
|
||||
log_elapsed_time(logger, elapsed)
|
||||
|
||||
return 0
|
||||
return 0 if success else 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing Tilt chain: {e}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user