PowerLASCOPF.jl/
├── src/
│ ├── PowerLASCOPF.jl # Main module
│ ├── models/
│ │ └── solver_models/ # Your existing solver code
│ ├── data_interface/ # NEW: Data layer
│ │ ├── DataLoader.jl
│ │ ├── DataValidator.jl
│ │ └── ResultsWriter.jl
│ └── api/ # NEW: API layer
│ └── Server.jl
├── example_cases/ # Your test cases
│ ├── RTS_GMLC/
│ ├── case5/
│ └── case118/
├── data_engineering/ # NEW: Python data engineering
│ ├── ingestion/
│ │ ├── __init__.py
│ │ ├── load_examples.py
│ │ ├── scada_simulator.py
│ │ └── weather_api.py
│ ├── validation/
│ │ ├── expectations.py
│ │ └── schemas.py
│ ├── transformation/
│ │ ├── converters.py
│ │ └── powersystems_builder.py
│ └── orchestration/
│ └── airflow_dags/
│ ├── daily_pipeline.py
│ └── realtime_pipeline.py
├── database/ # NEW: Database schemas
│ ├── migrations/
│ ├── init_timescale.sql
│ └── init_postgres.sql
├── docker/ # NEW: Docker setup
│ ├── docker-compose.yml
│ ├── Dockerfile.julia
│ └── Dockerfile.airflow
├── config/ # NEW: Configuration
│ ├── database.yml
│ └── pipeline_config.yml
└── tests/
├── data_pipeline/
└── integration/
Create docker/docker-compose.yml:
version: '3.8'
services:
# TimescaleDB for time-series power system measurements
timescaledb:
image: timescale/timescaledb:latest-pg15
container_name: lascopf_timescale
environment:
POSTGRES_DB: lascopf_timeseries
POSTGRES_USER: lascopf
POSTGRES_PASSWORD: lascopf_pass_2024
ports:
- "5432:5432"
volumes:
- timescale_data:/var/lib/postgresql/data
- ./database/init_timescale.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- lascopf_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lascopf"]
interval: 10s
timeout: 5s
retries: 5
# PostgreSQL for metadata and results
postgres:
image: postgres:15-alpine
container_name: lascopf_postgres
environment:
POSTGRES_DB: lascopf_metadata
POSTGRES_USER: lascopf
POSTGRES_PASSWORD: lascopf_pass_2024
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/init_postgres.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- lascopf_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lascopf"]
interval: 10s
timeout: 5s
retries: 5
# Redis for caching and Airflow broker
redis:
image: redis:7-alpine
container_name: lascopf_redis
ports:
- "6379:6379"
networks:
- lascopf_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
# MinIO for data lake storage
minio:
image: minio/minio:latest
container_name: lascopf_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: lascopf
MINIO_ROOT_PASSWORD: lascopf_minio_2024
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
networks:
- lascopf_network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
# Airflow PostgreSQL backend
airflow-db:
image: postgres:15-alpine
container_name: lascopf_airflow_db
environment:
POSTGRES_DB: airflow
POSTGRES_USER: airflow
POSTGRES_PASSWORD: airflow
volumes:
- airflow_db_data:/var/lib/postgresql/data
networks:
- lascopf_network
# Airflow Webserver
airflow-webserver:
build:
context: .
dockerfile: Dockerfile.airflow
container_name: lascopf_airflow_webserver
command: webserver
environment:
AIRFLOW__CORE__EXECUTOR: CeleryExecutor
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
AIRFLOW__CORE__FERNET_KEY: ''
AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
AIRFLOW__CORE__LOAD_EXAMPLES: 'false'
AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth'
_AIRFLOW_DB_UPGRADE: 'true'
_AIRFLOW_WWW_USER_CREATE: 'true'
_AIRFLOW_WWW_USER_USERNAME: airflow
_AIRFLOW_WWW_USER_PASSWORD: airflow
volumes:
- ./data_engineering/orchestration/airflow_dags:/opt/airflow/dags
- ./data_engineering:/opt/airflow/plugins/data_engineering
- ./example_cases:/opt/airflow/example_cases
- airflow_logs:/opt/airflow/logs
ports:
- "8080:8080"
networks:
- lascopf_network
depends_on:
airflow-db:
condition: service_started
redis:
condition: service_healthy
# Airflow Scheduler
airflow-scheduler:
build:
context: .
dockerfile: Dockerfile.airflow
container_name: lascopf_airflow_scheduler
command: scheduler
environment:
AIRFLOW__CORE__EXECUTOR: CeleryExecutor
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
volumes:
- ./data_engineering/orchestration/airflow_dags:/opt/airflow/dags
- ./data_engineering:/opt/airflow/plugins/data_engineering
- ./example_cases:/opt/airflow/example_cases
- airflow_logs:/opt/airflow/logs
networks:
- lascopf_network
depends_on:
airflow-webserver:
condition: service_started
# Airflow Worker
airflow-worker:
build:
context: .
dockerfile: Dockerfile.airflow
container_name: lascopf_airflow_worker
command: celery worker
environment:
AIRFLOW__CORE__EXECUTOR: CeleryExecutor
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@airflow-db/airflow
AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
volumes:
- ./data_engineering/orchestration/airflow_dags:/opt/airflow/dags
- ./data_engineering:/opt/airflow/plugins/data_engineering
- ./example_cases:/opt/airflow/example_cases
- airflow_logs:/opt/airflow/logs
networks:
- lascopf_network
depends_on:
airflow-scheduler:
condition: service_started
# Julia Solver Container
julia-solver:
build:
context: .
dockerfile: Dockerfile.julia
container_name: lascopf_julia_solver
environment:
JULIA_NUM_THREADS: 8
DATABASE_URL: postgresql://lascopf:lascopf_pass_2024@postgres:5432/lascopf_metadata
TIMESCALE_URL: postgresql://lascopf:lascopf_pass_2024@timescaledb:5432/lascopf_timeseries
volumes:
- ./:/app
- julia_depot:/root/.julia
networks:
- lascopf_network
depends_on:
postgres:
condition: service_healthy
timescaledb:
condition: service_healthy
command: julia --project=/app -e "using Pkg; Pkg.instantiate(); using PowerLASCOPF"
# Grafana for monitoring
grafana:
image: grafana/grafana:latest
container_name: lascopf_grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
GF_INSTALL_PLUGINS: grafana-clock-panel
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
networks:
- lascopf_network
depends_on:
- postgres
- timescaledb
volumes:
timescale_data:
postgres_data:
minio_data:
airflow_db_data:
airflow_logs:
julia_depot:
grafana_data:
networks:
lascopf_network:
driver: bridgeCreate docker/Dockerfile.airflow:
FROM apache/airflow:2.8.0-python3.10
USER root
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
USER airflow
# Install Python packages
COPY data_engineering/requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# Install Julia (for calling Julia from Python)
RUN pip install --no-cache-dir julia
WORKDIR /opt/airflowCreate docker/Dockerfile.julia:
FROM julia:1.10
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
postgresql-client \
git \
&& rm -rf /var/lib/apt/lists/*
# Copy project files
COPY Project.toml Manifest.toml ./
# Instantiate Julia environment
RUN julia --project=. -e 'using Pkg; Pkg.instantiate()'
# Pre-compile packages
RUN julia --project=. -e 'using Pkg; Pkg.precompile()'
COPY . .
# Set Julia to use multiple threads
ENV JULIA_NUM_THREADS=8
CMD ["julia", "--project=.", "-e", "using PowerLASCOPF"]Create data_engineering/requirements.txt:
apache-airflow==2.8.0
apache-airflow-providers-postgres==5.9.0
psycopg2-binary==2.9.9
pandas==2.1.4
numpy==1.26.2
sqlalchemy==2.0.23
great-expectations==0.18.8
pydantic==2.5.3
fastapi==0.108.0
uvicorn==0.25.0
python-dotenv==1.0.0
boto3==1.34.19
minio==7.2.0
redis==5.0.1
pyarrow==14.0.2
Create database/init_timescale.sql:
-- Enable TimescaleDB extension
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
-- ============================================================================
-- POWER MEASUREMENTS TABLE (Real-time and historical measurements)
-- ============================================================================
CREATE TABLE power_measurements (
time TIMESTAMPTZ NOT NULL,
measurement_id UUID DEFAULT gen_random_uuid(),
bus_id TEXT NOT NULL,
bus_name TEXT,
-- Voltage measurements
voltage_magnitude DOUBLE PRECISION,
voltage_angle_rad DOUBLE PRECISION,
-- Active and reactive power
active_power_mw DOUBLE PRECISION,
reactive_power_mvar DOUBLE PRECISION,
-- Frequency
frequency_hz DOUBLE PRECISION DEFAULT 60.0,
-- Data quality
measurement_quality INTEGER DEFAULT 100, -- 0-100 quality indicator
source TEXT, -- 'SCADA', 'PMU', 'SIMULATED'
-- Metadata
scenario_id TEXT,
case_name TEXT,
PRIMARY KEY (time, bus_id)
);
-- Convert to hypertable with 1-day chunks
SELECT create_hypertable('power_measurements', 'time', chunk_time_interval => INTERVAL '1 day');
-- Create indexes for faster queries
CREATE INDEX idx_power_meas_bus ON power_measurements (bus_id, time DESC);
CREATE INDEX idx_power_meas_scenario ON power_measurements (scenario_id, time DESC);
CREATE INDEX idx_power_meas_case ON power_measurements (case_name, time DESC);
-- ============================================================================
-- GENERATOR MEASUREMENTS TABLE
-- ============================================================================
CREATE TABLE generator_measurements (
time TIMESTAMPTZ NOT NULL,
measurement_id UUID DEFAULT gen_random_uuid(),
generator_id TEXT NOT NULL,
generator_name TEXT,
bus_id TEXT NOT NULL,
-- Power output
active_power_mw DOUBLE PRECISION,
reactive_power_mvar DOUBLE PRECISION,
-- Status
commitment_status BOOLEAN,
available_capacity_mw DOUBLE PRECISION,
-- Fuel and costs
fuel_consumption DOUBLE PRECISION,
marginal_cost DOUBLE PRECISION,
-- Constraints
ramp_rate_actual DOUBLE PRECISION,
-- Metadata
generator_type TEXT, -- 'THERMAL', 'HYDRO', 'WIND', 'SOLAR', 'STORAGE'
source TEXT,
scenario_id TEXT,
case_name TEXT,
PRIMARY KEY (time, generator_id)
);
SELECT create_hypertable('generator_measurements', 'time', chunk_time_interval => INTERVAL '1 day');
CREATE INDEX idx_gen_meas_gen ON generator_measurements (generator_id, time DESC);
CREATE INDEX idx_gen_meas_bus ON generator_measurements (bus_id, time DESC);
CREATE INDEX idx_gen_meas_type ON generator_measurements (generator_type, time DESC);
-- ============================================================================
-- BRANCH/LINE MEASUREMENTS TABLE
-- ============================================================================
CREATE TABLE branch_measurements (
time TIMESTAMPTZ NOT NULL,
measurement_id UUID DEFAULT gen_random_uuid(),
branch_id TEXT NOT NULL,
branch_name TEXT,
from_bus_id TEXT NOT NULL,
to_bus_id TEXT NOT NULL,
-- Power flows
from_bus_active_power_mw DOUBLE PRECISION,
from_bus_reactive_power_mvar DOUBLE PRECISION,
to_bus_active_power_mw DOUBLE PRECISION,
to_bus_reactive_power_mvar DOUBLE PRECISION,
-- Current and loading
current_magnitude_pu DOUBLE PRECISION,
loading_percentage DOUBLE PRECISION,
-- Status
in_service BOOLEAN DEFAULT TRUE,
-- Metadata
source TEXT,
scenario_id TEXT,
case_name TEXT,
PRIMARY KEY (time, branch_id)
);
SELECT create_hypertable('branch_measurements', 'time', chunk_time_interval => INTERVAL '1 day');
CREATE INDEX idx_branch_meas_branch ON branch_measurements (branch_id, time DESC);
CREATE INDEX idx_branch_meas_buses ON branch_measurements (from_bus_id, to_bus_id, time DESC);
-- ============================================================================
-- LOAD MEASUREMENTS TABLE
-- ============================================================================
CREATE TABLE load_measurements (
time TIMESTAMPTZ NOT NULL,
measurement_id UUID DEFAULT gen_random_uuid(),
load_id TEXT NOT NULL,
load_name TEXT,
bus_id TEXT NOT NULL,
-- Load values
active_power_mw DOUBLE PRECISION,
reactive_power_mvar DOUBLE PRECISION,
-- Forecast vs actual
forecasted_active_power_mw DOUBLE PRECISION,
forecast_error_mw DOUBLE PRECISION,
-- Metadata
load_type TEXT, -- 'RESIDENTIAL', 'COMMERCIAL', 'INDUSTRIAL'
source TEXT,
scenario_id TEXT,
case_name TEXT,
PRIMARY KEY (time, load_id)
);
SELECT create_hypertable('load_measurements', 'time', chunk_time_interval => INTERVAL '1 day');
CREATE INDEX idx_load_meas_load ON load_measurements (load_id, time DESC);
CREATE INDEX idx_load_meas_bus ON load_measurements (bus_id, time DESC);
-- ============================================================================
-- RENEWABLE FORECAST TABLE
-- ============================================================================
CREATE TABLE renewable_forecasts (
forecast_time TIMESTAMPTZ NOT NULL,
target_time TIMESTAMPTZ NOT NULL,
generator_id TEXT NOT NULL,
-- Forecasted values
forecasted_power_mw DOUBLE PRECISION,
confidence_lower_mw DOUBLE PRECISION,
confidence_upper_mw DOUBLE PRECISION,
-- Forecast metadata
forecast_horizon_hours INTEGER,
forecast_model TEXT,
scenario_id TEXT,
PRIMARY KEY (forecast_time, target_time, generator_id)
);
SELECT create_hypertable('renewable_forecasts', 'forecast_time', chunk_time_interval => INTERVAL '1 day');
CREATE INDEX idx_renewable_fc_gen ON renewable_forecasts (generator_id, target_time DESC);
-- ============================================================================
-- CONTINUOUS AGGREGATES FOR ANALYTICS
-- ============================================================================
-- Hourly average power measurements
CREATE MATERIALIZED VIEW power_measurements_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
bus_id,
case_name,
AVG(voltage_magnitude) as avg_voltage,
AVG(active_power_mw) as avg_active_power,
AVG(reactive_power_mvar) as avg_reactive_power,
MIN(voltage_magnitude) as min_voltage,
MAX(voltage_magnitude) as max_voltage,
COUNT(*) as measurement_count
FROM power_measurements
GROUP BY bucket, bus_id, case_name;
-- Refresh policy: update every hour
SELECT add_continuous_aggregate_policy('power_measurements_hourly',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour');
-- Daily generator statistics
CREATE MATERIALIZED VIEW generator_daily_stats
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', time) AS day,
generator_id,
generator_type,
case_name,
AVG(active_power_mw) as avg_generation,
MAX(active_power_mw) as peak_generation,
SUM(active_power_mw) as total_generation_mwh, -- Assuming hourly data
AVG(marginal_cost) as avg_marginal_cost
FROM generator_measurements
GROUP BY day, generator_id, generator_type, case_name;
SELECT add_continuous_aggregate_policy('generator_daily_stats',
start_offset => INTERVAL '3 days',
end_offset => INTERVAL '1 day',
schedule_interval => INTERVAL '1 day');
-- ============================================================================
-- DATA QUALITY METRICS TABLE
-- ============================================================================
CREATE TABLE data_quality_metrics (
metric_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
check_time TIMESTAMP DEFAULT NOW(),
table_name TEXT NOT NULL,
metric_type TEXT NOT NULL, -- 'COMPLETENESS', 'VALIDITY', 'CONSISTENCY'
metric_name TEXT NOT NULL,
metric_value DOUBLE PRECISION,
pass_fail TEXT, -- 'PASS', 'FAIL', 'WARNING'
details JSONB,
affected_records INTEGER
);
CREATE INDEX idx_quality_time ON data_quality_metrics (check_time DESC);
CREATE INDEX idx_quality_table ON data_quality_metrics (table_name, metric_type);
-- ============================================================================
-- PIPELINE EXECUTION LOGS TABLE
-- ============================================================================
CREATE TABLE pipeline_execution_logs (
log_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
pipeline_name TEXT NOT NULL,
dag_run_id TEXT,
task_id TEXT,
execution_date TIMESTAMP NOT NULL,
start_time TIMESTAMP,
end_time TIMESTAMP,
status TEXT, -- 'RUNNING', 'SUCCESS', 'FAILED'
error_message TEXT,
records_processed INTEGER,
metadata JSONB
);
CREATE INDEX idx_pipeline_logs_name ON pipeline_execution_logs (pipeline_name, execution_date DESC);
CREATE INDEX idx_pipeline_logs_status ON pipeline_execution_logs (status);
-- ============================================================================
-- VIEWS FOR COMMON QUERIES
-- ============================================================================
-- Latest execution results summary
CREATE VIEW v_latest_executions AS
SELECT
e.execution_id,
e.execution_time,
t.case_name,
e.convergence_status,
e.objective_value,
e.solve_time_seconds,
e.num_time_steps,
e.num_contingencies,
COUNT(DISTINCT gd.generator_id) as num_generators_dispatched
FROM lascopf_executions e
LEFT JOIN network_topology t ON e.topology_id = t.topology_id
LEFT JOIN generator_dispatch_results gd ON e.execution_id = gd.execution_id
GROUP BY e.execution_id, e.execution_time, t.case_name, e.convergence_status,
e.objective_value, e.solve_time_seconds, e.num_time_steps, e.num_contingencies
ORDER BY e.execution_time DESC
LIMIT 100;
-- Generator dispatch summary
CREATE VIEW v_generator_dispatch_summary AS
SELECT
gd.execution_id,
gd.time_step,
gd.target_datetime,
gd.scenario_type,
COUNT(*) as num_generators,
SUM(gd.active_power_mw) as total_generation_mw,
SUM(gd.generation_cost) as total_generation_cost,
AVG(gd.active_power_mw) as avg_generation_per_unit
FROM generator_dispatch_results gd
GROUP BY gd.execution_id, gd.time_step, gd.target_datetime, gd.scenario_type;
-- System-wide LMP statistics
CREATE VIEW v_lmp_statistics AS
SELECT
br.execution_id,
br.time_step,
br.target_datetime,
br.scenario_type,
AVG(br.lmp_total) as avg_lmp,
MIN(br.lmp_total) as min_lmp,
MAX(br.lmp_total) as max_lmp,
STDDEV(br.lmp_total) as lmp_std_dev,
MAX(br.lmp_total) - MIN(br.lmp_total) as lmp_spread
FROM bus_results br
GROUP BY br.execution_id, br.time_step, br.target_datetime, br.scenario_type; RETENTION POLICIES
-- ============================================================================
-- Keep raw measurements for 90 days, then compress
SELECT add_compression_policy('power_measurements', INTERVAL '7 days');
SELECT add_retention_policy('power_measurements', INTERVAL '90 days');
SELECT add_compression_policy('generator_measurements', INTERVAL '7 days');
SELECT add_retention_policy('generator_measurements', INTERVAL '90 days');
SELECT add_compression_policy('branch_measurements', INTERVAL '7 days');
SELECT add_retention_policy('branch_measurements', INTERVAL '90 days');
-- Keep forecasts for 30 days
SELECT add_compression_policy('renewable_forecasts', INTERVAL '3 days');
SELECT add_retention_policy('renewable_forecasts', INTERVAL '30 days');Create database/init_postgres.sql:
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================================
-- NETWORK TOPOLOGY TABLE
-- ============================================================================
CREATE TABLE network_topology (
topology_id SERIAL PRIMARY KEY,
case_name TEXT NOT NULL UNIQUE,
version INTEGER DEFAULT 1,
-- Topology characteristics
num_buses INTEGER,
num_generators INTEGER,
num_branches INTEGER,
num_loads INTEGER,
base_mva DOUBLE PRECISION DEFAULT 100.0,
-- System data (JSON representation of PowerSystems.System)
system_data JSONB NOT NULL,
-- PowerSystems.jl serialized data
powersystems_file_path TEXT,
-- Validity period
valid_from TIMESTAMP DEFAULT NOW(),
valid_until TIMESTAMP,
-- Metadata
description TEXT,
source TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
created_by TEXT
);
CREATE INDEX idx_topology_case ON network_topology (case_name);
CREATE INDEX idx_topology_valid ON network_topology (valid_from, valid_until);
-- ============================================================================
-- CONTINGENCY DEFINITIONS TABLE
-- ============================================================================
CREATE TABLE contingencies (
contingency_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
topology_id INTEGER REFERENCES network_topology(topology_id),
contingency_name TEXT NOT NULL,
contingency_type TEXT NOT NULL, -- 'N-1', 'N-1-1', 'N-2'
-- Affected elements
affected_branches TEXT[], -- Array of branch IDs
affected_generators TEXT[], -- Array of generator IDs
-- Probability and severity
probability DOUBLE PRECISION DEFAULT 0.01,
severity_level INTEGER DEFAULT 1, -- 1-5 scale
-- Metadata
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_contingency_topology ON contingencies (topology_id);
CREATE INDEX idx_contingency_type ON contingencies (contingency_type);
-- ============================================================================
-- LASCOPF EXECUTION RECORDS TABLE
-- ============================================================================
CREATE TABLE lascopf_executions (
execution_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
execution_time TIMESTAMP NOT NULL DEFAULT NOW(),
topology_id INTEGER REFERENCES network_topology(topology_id),
-- Execution parameters
optimization_horizon_hours INTEGER,
num_time_steps INTEGER,
num_contingencies INTEGER,
-- Algorithm parameters
admm_rho DOUBLE PRECISION,
app_beta DOUBLE PRECISION,
app_gamma DOUBLE PRECISION,
max_iterations INTEGER,
convergence_tolerance DOUBLE PRECISION,
-- Execution results
convergence_status TEXT, -- 'CONVERGED', 'MAX_ITERATIONS', 'INFEASIBLE'
final_iteration INTEGER,
objective_value DOUBLE PRECISION,
solve_time_seconds DOUBLE PRECISION,
-- Performance metrics
avg_iteration_time_seconds DOUBLE PRECISION,
peak_memory_mb DOUBLE PRECISION,
num_parallel_workers INTEGER,
-- File paths
results_file_path TEXT,
log_file_path TEXT,
-- Metadata
triggered_by TEXT, -- 'SCHEDULED', 'MANUAL', 'EVENT'
airflow_dag_run_id TEXT,
git_commit_hash TEXT,
julia_version TEXT,
notes TEXT
);
CREATE INDEX idx_exec_time ON lascopf_executions (execution_time DESC);
CREATE INDEX idx_exec_topology ON lascopf_executions (topology_id);
CREATE INDEX idx_exec_status ON lascopf_executions (convergence_status);
-- ============================================================================
-- GENERATOR DISPATCH RESULTS TABLE
-- ============================================================================
CREATE TABLE generator_dispatch_results (
result_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
execution_id UUID REFERENCES lascopf_executions(execution_id) ON DELETE CASCADE,
time_step INTEGER NOT NULL,
target_datetime TIMESTAMP,
generator_id TEXT NOT NULL,
scenario_type TEXT DEFAULT 'BASE', -- 'BASE', 'CONTINGENCY_1', etc.
-- Dispatch values
active_power_mw DOUBLE PRECISION,
reactive_power_mvar DOUBLE PRECISION,
commitment_status BOOLEAN,
-- Costs
generation_cost DOUBLE PRECISION,
startup_cost DOUBLE PRECISION,
shutdown_cost DOUBLE PRECISION,
-- Constraints status
at_pmin BOOLEAN DEFAULT FALSE,
at_pmax BOOLEAN DEFAULT FALSE,
ramp_limited BOOLEAN DEFAULT FALSE,
-- Shadow prices
lambda_p DOUBLE PRECISION,
lambda_q DOUBLE PRECISION
);
CREATE INDEX idx_dispatch_exec ON generator_dispatch_results (execution_id, time_step);
CREATE INDEX idx_dispatch_gen ON generator_dispatch_results (generator_id, target_datetime);
CREATE INDEX idx_dispatch_scenario ON generator_dispatch_results (execution_id, scenario_type);
-- ============================================================================
-- BUS RESULTS TABLE
-- ============================================================================
CREATE TABLE bus_results (
result_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
execution_id UUID REFERENCES lascopf_executions(execution_id) ON DELETE CASCADE,
time_step INTEGER NOT NULL,
target_datetime TIMESTAMP,
bus_id TEXT NOT NULL,
scenario_type TEXT DEFAULT 'BASE',
-- Voltage solution
voltage_magnitude_pu DOUBLE PRECISION,
voltage_angle_rad DOUBLE PRECISION,
-- Power balance
net_injection_mw DOUBLE PRECISION,
net_injection_mvar DOUBLE PRECISION,
-- Shadow prices (locational marginal prices)
lmp_energy DOUBLE PRECISION,
lmp_congestion DOUBLE PRECISION,
lmp_loss DOUBLE PRECISION,
lmp_total DOUBLE PRECISION
);
CREATE INDEX idx_bus_results_exec ON bus_results (execution_id, time_step);
CREATE INDEX idx_bus_results_bus ON bus_results (bus_id, target_datetime);
-- ============================================================================
-- BRANCH FLOW RESULTS TABLE
-- ============================================================================
CREATE TABLE branch_flow_results (
result_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
execution_id UUID REFERENCES lascopf_executions(execution_id) ON DELETE CASCADE,
time_step INTEGER NOT NULL,
target_datetime TIMESTAMP,
branch_id TEXT NOT NULL,
scenario_type TEXT DEFAULT 'BASE',
-- Power flows
from_bus_active_power_mw DOUBLE PRECISION,
from_bus_reactive_power_mvar DOUBLE PRECISION,
to_bus_active_power_mw DOUBLE PRECISION,
to_bus_reactive_power_mvar DOUBLE PRECISION,
-- Loading
loading_percentage DOUBLE PRECISION,
-- Constraint status
flow_constrained BOOLEAN DEFAULT FALSE,
shadow_price DOUBLE PRECISION
);
CREATE INDEX idx_branch_results_exec ON branch_flow_results (execution_id, time_step);
CREATE INDEX idx_branch_results_branch ON branch_flow_results (branch_id, target_datetime);
-- ============================================================================
-- CONVERGENCE HISTORY TABLE
-- ============================================================================
CREATE TABLE convergence_history (
history_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
execution_id UUID REFERENCES lascopf_executions(execution_id) ON DELETE CASCADE,
iteration INTEGER NOT NULL,
-- Residuals
primal_residual DOUBLE PRECISION,
dual_residual DOUBLE PRECISION,
-- Objective values
objective_value DOUBLE PRECISION,
-- Timing
iteration_time_seconds DOUBLE PRECISION,
cumulative_time_seconds DOUBLE PRECISION,
-- Metadata
recorded_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_conv_history_exec ON convergence_history (execution_id, iteration);
-- ============================================================================
-- DATA