Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/PRASReport.jl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: PRASReport.jl tests
# Run on master, tags, or any pull request
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST)
push:
branches: [main]
tags: ["*"]
pull_request:
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- "lts" # Latest LTS release, min supported
- "1" # Latest release
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
- aarch64
exclude:
- os: windows-latest
arch: aarch64
- os: ubuntu-latest
arch: aarch64
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-
${{ runner.os }}-${{ matrix.arch }}-test-
${{ runner.os }}-${{ matrix.arch }}-
${{ runner.os }}-
- run: julia --project=PRASReport.jl -e 'import Pkg;
Pkg.develop([
(path="PRASCore.jl",),
(path="PRASFiles.jl",),
])'
shell: bash
- uses: julia-actions/julia-buildpkg@latest
with:
project: PRASReport.jl
- run: |
git config --global user.name Tester
git config --global user.email te@st.er
- uses: julia-actions/julia-runtest@latest
with:
project: PRASReport.jl
env:
JULIA_NUM_THREADS: 2
- uses: julia-actions/julia-processcoverage@v1
with:
directories: PRASReport.jl/src
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ PRASFiles.jl/test/PRAS_Results_Export/

docs/build/
docs/site/
docs/src/examples/
docs/src/examples/
# Any html and duckdb files generated during testing
*.html
*.duckdb
3 changes: 3 additions & 0 deletions PRASCore.jl/src/Results/Results.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ EUE(x::AbstractShortfallResult, r::AbstractString, ::Colon) =
EUE(x::AbstractShortfallResult, ::Colon, ::Colon) =
EUE.(x, x.regions.names, permutedims(x.timestamps))

NEUE(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) =
NEUE.(x, x.regions.names, t)

NEUE(x::AbstractShortfallResult, r::AbstractString, ::Colon) =
NEUE.(x, r, x.timestamps)

Expand Down
6 changes: 6 additions & 0 deletions PRASCore.jl/src/Results/Shortfall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ function NCVAR(x::ShortfallResult, cvar::CVAR, r::AbstractString)

end

function NEUE(x::ShortfallResult{N,L,T,E}, r::AbstractString, t::ZonedDateTime) where {N,L,T,E}
i_r = findfirstunique(x.regions.names, r)
i_t = findfirstunique(x.timestamps, t)
return NEUE(div(MeanEstimate(x[r, t]..., x.nsamples),x.regions.load[i_r,i_t]/1e6))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if the load = 0 update ever made it through as tests might fail.

end

function finalize(
acc::ShortfallAccumulator{S},
system::SystemModel{N,L,T,P,E},
Expand Down
21 changes: 21 additions & 0 deletions PRASReport.jl/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Alliance for Energy Innovation, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
29 changes: 29 additions & 0 deletions PRASReport.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name = "PRASReport"
uuid = "c003f3f0-f5d5-4077-8f93-207e59ecb3ff"
authors = ["Hari Sundar <Hari.Sundar@nlr.gov>"]
version = "0.1.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DuckDB = "d2f5444f-75bc-4fdf-ac35-56f514c445e1"
PRASCore = "c5c32b99-e7c3-4530-a685-6f76e19f7fe2"
PRASFiles = "a2806276-6d43-4ef5-91c0-491704cd7cf1"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"

[compat]
Dates = "1"
PRASCore = "0.9.0"
PRASFiles = "0.9.0"
StatsBase = "0.34"
TimeZones = "^1.14"
julia = "1.10"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"

[targets]
test = ["Test","Suppressor"]
13 changes: 13 additions & 0 deletions PRASReport.jl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# PRASReport.jl

## Usage
```julia
using PRASReport

create_pras_report(system_path="path_to_sys.pras", # Path to your .pras file
samples=100,seed=1, # Number of samples and random seed for analysis
report_name="report", # Name of the output HTML file
report_path=pwd(), # Path for HTML file output
threshold=0, # hourly EUE threshold to be considered part of event
title="Resource Adequacy Report") # Report title
```
12 changes: 12 additions & 0 deletions PRASReport.jl/examples/run_report.jl

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be here. We don't have this in any other repos.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have this in PRAS.jl/examples. I felt like since this is an example specifically for report generation, this should be here rather than in PRAS.jl/examples which goes over more holistic functionality across multiple packages.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Revise
using PRAS
using PRASReport

rts_sys = rts_gmlc();
rts_sys.regions.load .+= 375;

sf,flow = assess(rts_sys,SequentialMonteCarlo(samples=100),Shortfall(),Flow());

create_pras_report(sf,flow, report_name="rts_report",
title="RTS-GMLC (load modified) RA Report",
threshold=1)
31 changes: 31 additions & 0 deletions PRASReport.jl/src/PRASReport.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module PRASReport

import PRASCore.Systems: SystemModel, Regions, Interfaces,
Generators, Storages, GeneratorStorages, Lines,
timeunits, powerunits, energyunits, unitsymbol,
unitsymbol_long
import PRASCore.Simulations: assess, SequentialMonteCarlo
import PRASCore.Results: EUE, LOLE, NEUE,
Shortfall, Flow,
ShortfallResult, FlowResult,
ShortfallSamplesResult, AbstractShortfallResult,
Result, MeanEstimate, findfirstunique,
val, stderror
import PRASFiles: SystemModel
import StatsBase: mean
import Dates: @dateformat_str, format, now, DateTime
import TimeZones: ZonedDateTime, @tz_str, TimeZone
import Base64: base64encode
import Tables: columntable
import DuckDB

export
Event, get_events, event_length,
Shortfall_timeseries, Flow_timeseries,
get_db, create_pras_report

include("events.jl")
include("writedb.jl")
include("report.jl")

end # module PRASReport
95 changes: 95 additions & 0 deletions PRASReport.jl/src/event_db_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
-- System and Simulation parameters

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment here: DuckDB is incredbly fast at doing some of the calculatins we do for EUE, nEUE, etc. I know this might mean a full overhaul of the schema but something to think about.

CREATE TABLE systemsiminfo (
timesteps INTEGER,
step_size INTEGER NOT NULL,
time_unit TEXT NOT NULL,
power_unit TEXT NOT NULL,
energy_unit TEXT NOT NULL,
start_timestamp TIMESTAMP WITHOUT TIME ZONE,
end_timestamp TIMESTAMP WITHOUT TIME ZONE,
timezone TEXT,
n_samples INTEGER,
eue_mean REAL NOT NULL,
eue_stderr REAL NOT NULL,
lole_mean REAL NOT NULL,
lole_stderr REAL NOT NULL,
neue_mean REAL NOT NULL,
neue_stderr REAL NOT NULL,
eventthreshold INTEGER NOT NULL,

-- Constraint to ensure valid ISO 8601 duration units
CONSTRAINT valid_time_unit CHECK (
time_unit IN ('Year', 'Day', 'Hour', 'Minute', 'Second')
)
);

-- Regions lookup table
CREATE TABLE regions (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);

-- Interfaces lookup table (region to region connections)
CREATE TABLE interfaces (
id INTEGER PRIMARY KEY,
region_from_id INTEGER REFERENCES regions(id),
region_to_id INTEGER REFERENCES regions(id),
name TEXT, -- name like "Region1->Region2"
UNIQUE(region_from_id, region_to_id)
);

-- Main events table (clean, no parameters)
CREATE SEQUENCE eventid_sequence START 1;
CREATE TABLE events (
id INTEGER PRIMARY KEY DEFAULT nextval('eventid_sequence'),
name TEXT NOT NULL,
start_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this different from the start_timestamp in the systemsinfo table?

end_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
time_period_count INTEGER NOT NULL -- N parameter
);

-- System-level metrics for each event (aggregated)
CREATE TABLE event_system_shortfall (
event_id INTEGER REFERENCES events(id),
lole REAL NOT NULL,
eue REAL NOT NULL,
neue REAL NOT NULL,
PRIMARY KEY (event_id)
);

-- Regional metrics for each event (aggregated)
CREATE TABLE event_regional_shortfall (
id INTEGER PRIMARY KEY,
event_id INTEGER REFERENCES events(id),
region_id INTEGER REFERENCES regions(id),
lole REAL NOT NULL,
eue REAL NOT NULL,
neue REAL NOT NULL,
UNIQUE(event_id, region_id)
);

-- Time-series metrics for each timestamp within an event (from sf_ts struct)
-- Optimized with better data types and ordering for columnar storage
CREATE TABLE event_timeseries_shortfall (
event_id INTEGER NOT NULL,
region_id INTEGER NOT NULL,
timestamp_value TIMESTAMP WITHOUT TIME ZONE NOT NULL,
lole REAL NOT NULL,
eue REAL NOT NULL,
neue REAL NOT NULL,
FOREIGN KEY (event_id) REFERENCES events(id),
FOREIGN KEY (region_id) REFERENCES regions(id),
PRIMARY KEY (event_id, region_id, timestamp_value)
);

-- Flow data for each timestamp within an event (from flow_ts struct)
-- Optimized with better data types and ordering for columnar storage
CREATE TABLE event_timeseries_flows (
event_id INTEGER NOT NULL,
interface_id INTEGER NOT NULL,
timestamp_value TIMESTAMP WITHOUT TIME ZONE NOT NULL,
flow REAL NOT NULL, -- Flow value (NEUE units)
FOREIGN KEY (event_id) REFERENCES events(id),
FOREIGN KEY (interface_id) REFERENCES interfaces(id),
PRIMARY KEY (event_id, interface_id, timestamp_value)
);
Loading
Loading