Skip to content

Commit 9c96bd7

Browse files
abelsiqueiraclaude
andcommitted
Document and validate required schema table names for create_connection
The schema argument requires specific TulipaEnergyModel-convention table names (asset, asset_both, asset_commission, asset_milestone, flow, flow_both, flow_commission, flow_milestone). Previously this contract was implicit and callers got cryptic KeyErrors on mismatch. - Add `_REQUIRED_SCHEMA_TABLES` const and `_validate_schema` validator - Throw `ArgumentError` with clear message listing missing tables - Update `create_connection` docstring to document required schema structure - Update README schema-driven design bullet with required table names - Add test verifying ArgumentError is thrown for incomplete schemas Co-authored-by: Claude Code (claude-sonnet-4-6) <noreply@anthropic.com>
1 parent f81634a commit 9c96bd7

3 files changed

Lines changed: 79 additions & 2 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ TulipaBuilder.jl provides a graph-based approach to building energy system model
1616
## Key Features
1717

1818
- **Graph-based modeling**: Intuitive representation of energy systems as connected assets
19-
- **Schema-driven design**: Dynamically compatible with different TulipaEnergyModel.jl versions
19+
- **Schema-driven design**: Dynamically compatible with different TulipaEnergyModel.jl versions.
20+
The `schema` argument passed to `create_connection` must be a dict keyed by TulipaEnergyModel
21+
table names. The following table names are required: `asset`, `asset_both`, `asset_commission`,
22+
`asset_milestone`, `flow`, `flow_both`, `flow_commission`, `flow_milestone`. Each entry maps
23+
column names to type/default metadata. `TulipaEnergyModel.schema` satisfies this automatically.
2024
- **Simplified workflow**: Streamlined process from model creation to optimization
2125

2226
### Magic transformations

src/create-connection.jl

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
export create_connection
22

3+
const _REQUIRED_SCHEMA_TABLES = [
4+
"asset",
5+
"asset_both",
6+
"asset_commission",
7+
"asset_milestone",
8+
"flow",
9+
"flow_both",
10+
"flow_commission",
11+
"flow_milestone",
12+
]
13+
14+
"""
15+
_validate_schema(schema)
16+
17+
Checks that `schema` contains the table names required by [`create_connection`](@ref).
18+
Throws an `ArgumentError` listing any missing tables if they are absent.
19+
This function is for internal use only.
20+
"""
21+
function _validate_schema(schema)
22+
missing_tables = filter(t -> !haskey(schema, t), _REQUIRED_SCHEMA_TABLES)
23+
if !isempty(missing_tables)
24+
throw(
25+
ArgumentError(
26+
"The provided schema is missing required table(s): " *
27+
join(sort(missing_tables), ", ") *
28+
".\nExpected table names follow TulipaEnergyModel conventions " *
29+
"(e.g. asset, asset_both, asset_commission, asset_milestone, " *
30+
"flow, flow_both, flow_commission, flow_milestone). " *
31+
"Pass TulipaEnergyModel.schema to satisfy this requirement.",
32+
),
33+
)
34+
end
35+
end
36+
337
"""
438
create_empty_table_from_schema!(connection, table_name, schema, columns)
539
@@ -147,10 +181,25 @@ end
147181
create_connection(tulipa_data, schema, db)
148182
149183
Creates a DuckDB connection and populates it with the data from `tulipa_data`.
150-
The `schema` should be a dict in the same format as `TulipaEnergyModel.schema`.
184+
185+
The `schema` must be a `Dict{String, Dict{String, Dict{String, Any}}}` whose
186+
top-level keys are TulipaEnergyModel table names. The following table names are
187+
**required**:
188+
189+
- `"asset"`, `"asset_both"`, `"asset_commission"`, `"asset_milestone"`
190+
- `"flow"`, `"flow_both"`, `"flow_commission"`, `"flow_milestone"`
191+
192+
Each table entry maps column names to dicts with at least a `"type"` key and
193+
an optional `"default"` key. This structure matches `TulipaEnergyModel.schema`
194+
(derived from `input-schemas.json` in TulipaEnergyModel.jl), which is the
195+
intended source for this argument.
196+
197+
Throws `ArgumentError` if any required table name is missing from `schema`.
198+
151199
If `db` is not given, a memory connection is created.
152200
"""
153201
function create_connection(tulipa::TulipaData, schema; db = ":memory:")
202+
_validate_schema(schema)
154203
connection = DBInterface.connect(DuckDB.DB, db)
155204
run_query(s) = DuckDB.query(connection, s)
156205

@@ -468,6 +517,7 @@ function create_connection(tulipa::TulipaData, schema; db = ":memory:")
468517
# Handle all profiles (stored with 4-tuple key: profile_type, milestone_year, commission_year, scenario)
469518
for ((profile_type, milestone_year, commission_year, scenario), profile_value) in
470519
asset.profiles
520+
471521
profile_name = "$asset_name-$profile_type-$commission_year"
472522

473523
# Use DataFrame for efficient bulk insertion
@@ -527,6 +577,7 @@ function create_connection(tulipa::TulipaData, schema; db = ":memory:")
527577
(profile_type, milestone_year, commission_year, scenario),
528578
profile_value,
529579
) in flow.profiles
580+
530581
profile_name = "$from_asset_name-$to_asset_name-$profile_type-$commission_year"
531582
profiles_df = DataFrame(
532583
profile_name = profile_name,

test/test-create-connection.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@
33
const MIN_FLOW_TABLES = ["flow", "flow_commission", "flow_milestone"]
44
end
55

6+
@testitem "create_connection throws ArgumentError for missing required schema tables" tags =
7+
[:schema, :unit, :fast] setup = [CommonSetup, TestSchema] begin
8+
tulipa = TulipaData()
9+
add_asset!(tulipa, "producer", :producer)
10+
11+
# Schema missing all required tables
12+
@test_throws ArgumentError create_connection(tulipa, Dict{String,Any}())
13+
14+
# Schema missing one required table
15+
incomplete_schema = copy(TestSchema.schema)
16+
delete!(incomplete_schema, "asset_both")
17+
@test_throws ArgumentError create_connection(tulipa, incomplete_schema)
18+
19+
# Error message lists the missing table
20+
err = try
21+
create_connection(tulipa, incomplete_schema)
22+
catch e
23+
e
24+
end
25+
@test occursin("asset_both", err.msg)
26+
end
27+
628
@testitem "Create connection after add_asset" tags = [:schema] setup =
729
[CommonSetup, CreateConnectionSetup, TestSchema] begin
830
tulipa = TulipaData()

0 commit comments

Comments
 (0)