diff --git a/data_files/temoa_schema_v3_1.sql b/data_files/temoa_schema_v3_1.sql index 7626b379..b91d03ce 100644 --- a/data_files/temoa_schema_v3_1.sql +++ b/data_files/temoa_schema_v3_1.sql @@ -873,7 +873,7 @@ CREATE TABLE IF NOT EXISTS ReserveCapacityDerate CHECK (factor >= 0 AND factor <= 1) ); CREATE TABLE IF NOT EXISTS TimeSegmentFraction -( +( period INTEGER REFERENCES TimePeriod (period), season TEXT @@ -1054,4 +1054,4 @@ CREATE TABLE IF NOT EXISTS OutputCost FOREIGN KEY (tech) REFERENCES Technology (tech) ); COMMIT; -PRAGMA FOREIGN_KEYS = 1; \ No newline at end of file +PRAGMA FOREIGN_KEYS = 1; diff --git a/pyproject.toml b/pyproject.toml index 5a7e4d54..398ba856 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,14 @@ solver = [ requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +include = [ + "temoa", +] +package-data = { "temoa" = ["db_schema/*.sql"] } + + [tool.ruff] line-length = 100 indent-width = 4 diff --git a/temoa/cli.py b/temoa/cli.py index 79036b8f..41793420 100644 --- a/temoa/cli.py +++ b/temoa/cli.py @@ -1,5 +1,7 @@ +import argparse import logging -from datetime import datetime +from datetime import datetime, timezone +from importlib import resources from pathlib import Path from typing import Annotated @@ -11,6 +13,7 @@ from temoa._internal.temoa_sequencer import TemoaSequencer from temoa.core.config import TemoaConfig from temoa.core.modes import TemoaMode +from temoa.utilities import db_migration_v3_1_to_v4, sql_migration_v3_1_to_v4 from temoa.version_information import TEMOA_MAJOR, TEMOA_MINOR # ============================================================================= @@ -106,7 +109,6 @@ def _version_callback(value: bool) -> None: def _cite_callback(value: bool) -> None: if value: citation_text = Text() - citation_text.append('[bold]How to Cite Temoa:[/bold]\n\n', style='bold') citation_text.append( 'If you use Temoa in your research, please cite the following publication:\n\n' ) @@ -128,6 +130,34 @@ def _cite_callback(value: bool) -> None: raise typer.Exit() +def get_default_schema() -> Path: + """Get the default path to the v4 schema file, handling both installed and development cases.""" + try: + schema_path = resources.files('temoa.db_schema') / 'temoa_schema_v4.sql' + + if not schema_path.is_file(): + raise FileNotFoundError( + f'Schema file not found at expected resource path: {schema_path}' + ) + return Path(str(schema_path)) # Convert Traversable to concrete Path + except Exception as e: + logger.exception('Failed to load schema from resources') + # The fallback for development needs to reflect the current repository structure + # assuming `cli.py` is in `temoa/` and `db_schema/` is a sibling of `cli.py` within `temoa/`. + fallback_path = Path(__file__).parent / 'db_schema' / 'temoa_schema_v4.sql' + if fallback_path.is_file(): + logger.warning( + 'Using fallback schema path: %s. ' + 'This might indicate an issue with package installation or resource setup.', + fallback_path, + ) + return fallback_path + else: + raise FileNotFoundError( + f'Schema file not found using resource system or fallback at {fallback_path}' + ) from e + + app = typer.Typer( name='temoa', help='The Temoa Project: Tools for Energy Model Optimization and Analysis.', @@ -158,7 +188,7 @@ def validate( typer.Option('--output', '-o', help='Directory to save validation log.'), ] = None, silent: Annotated[ - bool, typer.Option('--silent', '-s', help='Suppress informational output on success.') + bool, typer.Option('--silent', '-q', help='Suppress informational output on success.') ] = False, debug: Annotated[ bool, typer.Option('--debug', '-d', help='Enable debug-level logging.') @@ -212,7 +242,7 @@ def run( silent: Annotated[ bool, typer.Option( - '--silent', '-s', help='Silent run. No interactive prompts or INFO logs on console.' + '--silent', '-q', help='Silent run. No interactive prompts or INFO logs on console.' ), ] = False, debug: Annotated[ @@ -255,6 +285,169 @@ def run( raise typer.Exit(code=1) from e +@app.command() +def migrate( + input_path: Annotated[ + Path, + typer.Argument( + help='Path to input file to migrate (SQL dump or SQLite DB).', + exists=True, + resolve_path=True, + ), + ], + output_path: Annotated[ + Path | None, + typer.Option( + '--output', + '-o', + help='Output path for the migrated file. If not provided, a default name ' + '(e.g., input_v4.sql or input_v4.sqlite) will be used in a writable location.', + ), + ] = None, + schema_path: Annotated[ + Path | None, + typer.Option('--schema', '-s', help='Path to v4 schema SQL file.'), + ] = None, + migration_type: Annotated[ + str | None, + typer.Option( + '--type', + help='Migration type: "sql" for SQL dump to SQLite dump, "db" for SQLite DB in-place migration, if omitted, infers from input extension.', + ), + ] = None, + silent: Annotated[ + bool, typer.Option('--silent', '-q', help='Suppress informational output on success.') + ] = False, + debug: Annotated[bool, typer.Option('--debug', '-d', help='Enable debug output.')] = False, +) -> None: + """ + Migrate a single Temoa database file (SQL dump or SQLite DB) from v3.1 to v4 format. + """ + if schema_path is None: + schema_path = get_default_schema() + if not schema_path.is_file(): + rich.print(f'[red]Error: Schema file {schema_path} does not exist or is not a file.[/red]') + raise typer.Exit(1) + + # Validate that input_path is a file, not a directory + if not input_path.is_file(): + rich.print(f'[red]Error: Input path must be a file, not a directory: {input_path}[/red]') + raise typer.Exit(1) + + ext = input_path.suffix.lower() + + # Determine the effective output directory and file + effective_output_dir: Path + final_output_file: Path + + if output_path: + # If explicit output_path is provided, its parent is the desired directory + effective_output_dir = output_path.parent + # Ensure the explicitly provided output_path parent exists + try: + effective_output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + rich.print( + f'[red]Error: Could not create output directory "{effective_output_dir}": {e}[/red]' + ) + raise typer.Exit(1) from e + final_output_file = effective_output_dir / output_path.name + else: + # Try to use the input file's directory + input_dir = input_path.parent + if _is_writable(input_dir): + effective_output_dir = input_dir + else: + # Fallback to current working directory if input_dir is not writable + current_dir = Path.cwd() + if _is_writable(current_dir): + effective_output_dir = current_dir + if not silent: + rich.print( + f'[yellow]Warning: Input directory "{input_dir}" is not writable. ' + f'Saving output to current directory: "{current_dir}"[/yellow]' + ) + else: + rich.print( + f'[red]Error: Neither input directory "{input_dir}" ' + f'nor current working directory "{current_dir}" are writable. ' + 'Please specify a writable output path with --output.[/red]' + ) + raise typer.Exit(1) + + # Ensure the chosen output directory exists + try: + effective_output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + rich.print( + f'[red]Error: Could not create auto-generated output directory "{effective_output_dir}": {e}[/red]' + ) + raise typer.Exit(1) from e + + # For auto-output, derive filename from input_path, place in effective_output_dir + # Determine output file extension based on migration type + if migration_type == 'db' or (migration_type is None and ext in ['.db', '.sqlite']): + # If migrating to DB, output should be .sqlite + final_output_file = effective_output_dir / (input_path.stem + '_v4.sqlite') + else: + # Default to .sql if migrating SQL dump or type 'auto' for .sql input + final_output_file = effective_output_dir / (input_path.stem + '_v4.sql') + + # --- Execute the migration based on type --- + if migration_type == 'sql' or (migration_type is None and ext == '.sql'): + # SQL dump to SQL dump migration + args_namespace = argparse.Namespace( + input=str(input_path), + schema=str(schema_path), + output=str(final_output_file), + debug=debug, + ) + try: + sql_migration_v3_1_to_v4.migrate_dump_to_sqlite(args_namespace) + if not silent: + rich.print(f'[green]SQL dump migration completed: {final_output_file}[/green]') + except Exception as e: + logger.exception('SQL dump migration failed for %s', input_path) + rich.print( + f'[red]SQL dump migration failed for {input_path} -> {final_output_file}: {e}[/red]' + ) + raise typer.Exit(1) from e + elif migration_type == 'db' or (migration_type is None and ext in ['.db', '.sqlite']): + # SQLite DB to SQLite DB migration + args_namespace = argparse.Namespace( + source=str(input_path), + schema=str(schema_path), + out=str(final_output_file), + ) + try: + db_migration_v3_1_to_v4.migrate_all(args_namespace) + if not silent: + rich.print(f'[green]Database migration completed: {final_output_file}[/green]') + except Exception as e: + logger.exception('Database migration failed for %s', input_path) + rich.print( + f'[red]Database migration failed for {input_path} -> {final_output_file}: {e}[/red]' + ) + raise typer.Exit(1) from e + else: + rich.print( + f'[red]Error: Cannot determine migration type for {input_path}. ' + 'Use --type sql, --type db, or ensure file has a .sql, .db, or .sqlite extension.[/red]' + ) + raise typer.Exit(1) + + +def _is_writable(path: Path) -> bool: + """Check if a path is writable.""" + try: + test_file = path / f'.temoa_write_test_{datetime.now(timezone.utc).timestamp()}' + test_file.touch() + test_file.unlink() # Clean up + return True + except OSError: + return False + + # ============================================================================= # Global Options # ============================================================================= diff --git a/temoa/db_schema/temoa_schema_v3.sql b/temoa/db_schema/temoa_schema_v3.sql new file mode 100644 index 00000000..3d0d66fa --- /dev/null +++ b/temoa/db_schema/temoa_schema_v3.sql @@ -0,0 +1,919 @@ +PRAGMA foreign_keys= OFF; +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS MetaData +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +REPLACE INTO MetaData +VALUES ('myopic_base_year', 2000, 'Base Year for Myopic Analysis'); +REPLACE INTO MetaData +VALUES ('DB_MAJOR', 3, 'DB major version number'); +REPLACE INTO MetaData +VALUES ('DB_MINOR', 0, 'DB minor version number'); + +CREATE TABLE IF NOT EXISTS MetaDataReal +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +REPLACE INTO MetaDataReal +VALUES ('global_discount_rate', 0.05, 'Discount Rate for future costs'); +REPLACE INTO MetaDataReal +VALUES ('default_loan_rate', 0.05, 'Default Loan Rate if not specified in LoanRate table'); + +CREATE TABLE IF NOT EXISTS OutputDualVariable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE IF NOT EXISTS OutputObjective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE IF NOT EXISTS SectorLabel +( + sector TEXT, + PRIMARY KEY (sector) +); + +CREATE TABLE IF NOT EXISTS CapacityCredit +( + region TEXT, + period INTEGER, + tech TEXT, + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityFactorProcess +( + region TEXT, + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityFactorTech +( + region TEXT, + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityToActivity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + c2a REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS Commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES CommodityType (label), + description TEXT +); +CREATE TABLE IF NOT EXISTS CommodityType +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO CommodityType +VALUES ('p', 'physical commodity'); +REPLACE INTO CommodityType +VALUES ('e', 'emissions commodity'); +REPLACE INTO CommodityType +VALUES ('d', 'demand commodity'); +REPLACE INTO CommodityType +VALUES ('s', 'source commodity'); + +CREATE TABLE IF NOT EXISTS CostEmission +( + region TEXT + REFERENCES Region (region), + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT NOT NULL + REFERENCES Commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS CostFixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS CostInvest +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS CostVariable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS Demand +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + commodity TEXT + REFERENCES Commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +CREATE TABLE IF NOT EXISTS DemandSpecificDistribution +( + region TEXT, + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + demand_name TEXT + REFERENCES Commodity (name), + dds REAL, + dds_notes TEXT, + PRIMARY KEY (region, season, tod, demand_name), + CHECK (dds >= 0 AND dds <= 1) +); +CREATE TABLE IF NOT EXISTS LoanRate +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS Efficiency +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS EmissionActivity +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS ExistingCapacity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS TechGroup +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS GrowthRateMax +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS GrowthRateSeed +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + seed REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS LoanLifetimeTech +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS LifetimeProcess +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS LifetimeTech +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS LinkedTech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES Technology (tech), + emis_comm TEXT + REFERENCES Commodity (name), + driven_tech TEXT + REFERENCES Technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE IF NOT EXISTS MaxActivity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + max_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MaxCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + max_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MaxResource +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + max_res REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS MinActivity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + min_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MaxCapacityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + max_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); +CREATE TABLE IF NOT EXISTS MinCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + min_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MinCapacityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + min_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); +CREATE TABLE IF NOT EXISTS OutputCurtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES TimePeriod (period), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + curtailment REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS OutputNetCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputBuiltCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputRetiredCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputFlowIn +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS OutputFlowOut +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS PlanningReserveMargin +( + region TEXT + PRIMARY KEY + REFERENCES Region (region), + margin REAL +); +CREATE TABLE IF NOT EXISTS RampDown +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS RampUp +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS Region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS TimeSegmentFraction +( + season TEXT + REFERENCES TimeSeason (season), + tod TEXT + REFERENCES TimeOfDay (tod), + segfrac REAL, + notes TEXT, + PRIMARY KEY (season, tod), + CHECK (segfrac >= 0 AND segfrac <= 1) +); +CREATE TABLE IF NOT EXISTS StorageDuration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS StorageInit +( + tech TEXT + PRIMARY KEY, + value REAL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS TechnologyType +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO TechnologyType +VALUES ('r', 'resource technology'); +REPLACE INTO TechnologyType +VALUES ('p', 'production technology'); +REPLACE INTO TechnologyType +VALUES ('pb', 'baseload production technology'); +REPLACE INTO TechnologyType +VALUES ('ps', 'storage production technology'); + +CREATE TABLE IF NOT EXISTS TechInputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + min_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech) +); +CREATE TABLE IF NOT EXISTS TechInputSplitAverage +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + min_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech) +); +CREATE TABLE IF NOT EXISTS TechOutputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + min_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm) +); +CREATE TABLE IF NOT EXISTS TimeOfDay +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY +); +CREATE TABLE IF NOT EXISTS TimePeriod +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES TimePeriodType (label) +); +CREATE TABLE IF NOT EXISTS TimeSeason +( + sequence INTEGER UNIQUE, + season TEXT + PRIMARY KEY +); +CREATE TABLE IF NOT EXISTS TimePeriodType +( + label TEXT + PRIMARY KEY, + description TEXT +); +CREATE TABLE IF NOT EXISTS MaxActivityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + max_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS MaxCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + max_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS MaxAnnualCapacityFactor +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + factor REAL, + source TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS MaxNewCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + max_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MaxNewCapacityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + max_new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); +CREATE TABLE IF NOT EXISTS MaxNewCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + max_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS MinActivityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + min_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS MinAnnualCapacityFactor +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + factor REAL, + source TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS MinCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + min_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS MinNewCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + min_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech) +); +CREATE TABLE IF NOT EXISTS MinNewCapacityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + min_new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); +CREATE TABLE IF NOT EXISTS MinNewCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + group_name TEXT + REFERENCES TechGroup (group_name), + max_proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, group_name) +); +CREATE TABLE IF NOT EXISTS OutputEmission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + emission REAL, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS MinActivityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + min_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); +CREATE TABLE IF NOT EXISTS EmissionLimit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS MaxActivityGroup +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + group_name TEXT + REFERENCES TechGroup (group_name), + max_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, group_name) +); + +CREATE TABLE RPSRequirement +( + region TEXT NOT NULL + REFERENCES Region (region), + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech_group TEXT NOT NULL + REFERENCES TechGroup (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE TechGroupMember +( + group_name TEXT + REFERENCES TechGroup (group_name), + tech TEXT + REFERENCES Technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE IF NOT EXISTS Technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + variable INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES TechnologyType (label) +); +CREATE TABLE IF NOT EXISTS OutputCost +( + scenario TEXT, + region TEXT, + period INTEGER, + tech TEXT, + vintage INTEGER, + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES TimePeriod (period), + FOREIGN KEY (tech) REFERENCES Technology (tech) +); +COMMIT; +PRAGMA FOREIGN_KEYS = 1; diff --git a/temoa/db_schema/temoa_schema_v3_1.sql b/temoa/db_schema/temoa_schema_v3_1.sql new file mode 100644 index 00000000..b91d03ce --- /dev/null +++ b/temoa/db_schema/temoa_schema_v3_1.sql @@ -0,0 +1,1057 @@ +PRAGMA foreign_keys= OFF; +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS MetaData +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +REPLACE INTO MetaData +VALUES ('DB_MAJOR', 3, 'DB major version number'); +REPLACE INTO MetaData +VALUES ('DB_MINOR', 1, 'DB minor version number'); +REPLACE INTO MetaData +VALUES ('days_per_period', 365, 'count of days in each period'); + +CREATE TABLE IF NOT EXISTS MetaDataReal +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +REPLACE INTO MetaDataReal +VALUES ('global_discount_rate', 0.05, 'Discount Rate for future costs'); +REPLACE INTO MetaDataReal +VALUES ('default_loan_rate', 0.05, 'Default Loan Rate if not specified in LoanRate table'); + +CREATE TABLE IF NOT EXISTS OutputDualVariable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE IF NOT EXISTS OutputObjective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE IF NOT EXISTS SeasonLabel +( + season TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS SectorLabel +( + sector TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS CapacityCredit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityFactorProcess +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityFactorTech +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS CapacityToActivity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + c2a REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS Commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES CommodityType (label), + description TEXT +); +CREATE TABLE IF NOT EXISTS CommodityType +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO CommodityType +VALUES ('s', 'source commodity'); +REPLACE INTO CommodityType +VALUES ('a', 'annual commodity'); +REPLACE INTO CommodityType +VALUES ('p', 'physical commodity'); +REPLACE INTO CommodityType +VALUES ('d', 'demand commodity'); +REPLACE INTO CommodityType +VALUES ('e', 'emissions commodity'); +REPLACE INTO CommodityType +VALUES ('w', 'waste commodity'); +REPLACE INTO CommodityType +VALUES ('wa', 'waste annual commodity'); +REPLACE INTO CommodityType +VALUES ('wp', 'waste physical commodity'); +CREATE TABLE IF NOT EXISTS ConstructionInput +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS CostEmission +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT NOT NULL + REFERENCES Commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS CostFixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS CostInvest +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS CostVariable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS Demand +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + commodity TEXT + REFERENCES Commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +CREATE TABLE IF NOT EXISTS DemandSpecificDistribution +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + demand_name TEXT + REFERENCES Commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +CREATE TABLE IF NOT EXISTS EndOfLifeOutput +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS Efficiency +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS EfficiencyVariable +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS EmissionActivity +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS EmissionEmbodied +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS EmissionEndOfLife +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS ExistingCapacity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS TechGroup +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS LoanLifetimeProcess +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS LoanRate +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS LifetimeProcess +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS LifetimeTech +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS Operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +REPLACE INTO Operator VALUES('e','equal to'); +REPLACE INTO Operator VALUES('le','less than or equal to'); +REPLACE INTO Operator VALUES('ge','greater than or equal to'); +CREATE TABLE IF NOT EXISTS LimitGrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitGrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitGrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitStorageLevelFraction +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + fraction REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tod, tech, vintage, operator) +); +CREATE TABLE IF NOT EXISTS LimitActivity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitActivityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitAnnualCapacityFactor +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS LimitCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitNewCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitNewCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitResource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitSeasonalCapacityFactor +( + region TEXT + REFERENCES Region (region), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechInputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechInputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechOutputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechOutputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS LimitEmission +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE IF NOT EXISTS LinkedTech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES Technology (tech), + emis_comm TEXT + REFERENCES Commodity (name), + driven_tech TEXT + REFERENCES Technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE IF NOT EXISTS OutputCurtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES TimePeriod (period), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + curtailment REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS OutputNetCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputBuiltCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputRetiredCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + cap_eol REAL, + cap_early REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS OutputFlowIn +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS OutputFlowOut +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS OutputStorageLevel +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + level REAL, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE IF NOT EXISTS PlanningReserveMargin +( + region TEXT + PRIMARY KEY + REFERENCES Region (region), + margin REAL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS RampDownHourly +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS RampUpHourly +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS Region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS ReserveCapacityDerate +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS TimeSegmentFraction +( + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + segfrac REAL, + notes TEXT, + PRIMARY KEY (period, season, tod), + CHECK (segfrac >= 0 AND segfrac <= 1) +); +CREATE TABLE IF NOT EXISTS StorageDuration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS LifetimeSurvivalCurve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS TechnologyType +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO TechnologyType +VALUES ('p', 'production technology'); +REPLACE INTO TechnologyType +VALUES ('pb', 'baseload production technology'); +REPLACE INTO TechnologyType +VALUES ('ps', 'storage production technology'); +-- CREATE TABLE IF NOT EXISTS TimeNext +-- ( +-- period INTEGER +-- REFERENCES TimePeriod (period), +-- season TEXT +-- REFERENCES SeasonLabel (season), +-- tod TEXT +-- REFERENCES TimeOfDay (tod), +-- season_next TEXT +-- REFERENCES SeasonLabel (season), +-- tod_next TEXT +-- REFERENCES TimeOfDay (tod), +-- notes TEXT, +-- PRIMARY KEY (period, season, tod) +-- ); +CREATE TABLE IF NOT EXISTS TimeOfDay +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY +); +CREATE TABLE IF NOT EXISTS TimePeriod +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES TimePeriodType (label) +); +CREATE TABLE IF NOT EXISTS TimeSeason +( + period INTEGER + REFERENCES TimePeriod (period), + sequence INTEGER, + season TEXT + REFERENCES SeasonLabel (season), + notes TEXT, + PRIMARY KEY (period, sequence, season) +); +CREATE TABLE IF NOT EXISTS TimeSeasonSequential +( + period INTEGER + REFERENCES TimePeriod (period), + sequence INTEGER, + seas_seq TEXT, + season TEXT + REFERENCES SeasonLabel (season), + num_days REAL NOT NULL, + notes TEXT, + PRIMARY KEY (period, sequence, seas_seq, season), + CHECK (num_days > 0) +); +CREATE TABLE IF NOT EXISTS TimePeriodType +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO TimePeriodType +VALUES('e', 'existing vintages'); +REPLACE INTO TimePeriodType +VALUES('f', 'future'); +CREATE TABLE IF NOT EXISTS OutputEmission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + emission REAL, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS RPSRequirement +( + region TEXT NOT NULL + REFERENCES Region (region), + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech_group TEXT NOT NULL + REFERENCES TechGroup (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS TechGroupMember +( + group_name TEXT + REFERENCES TechGroup (group_name), + tech TEXT + REFERENCES Technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE IF NOT EXISTS Technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES TechnologyType (label) +); +CREATE TABLE IF NOT EXISTS OutputCost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES SectorLabel (sector), + period INTEGER REFERENCES TimePeriod (period), + tech TEXT REFERENCES Technology (tech), + vintage INTEGER REFERENCES TimePeriod (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES TimePeriod (period), + FOREIGN KEY (tech) REFERENCES Technology (tech) +); +COMMIT; +PRAGMA FOREIGN_KEYS = 1; diff --git a/temoa/db_schema/temoa_schema_v4.sql b/temoa/db_schema/temoa_schema_v4.sql new file mode 100644 index 00000000..5cbdb446 --- /dev/null +++ b/temoa/db_schema/temoa_schema_v4.sql @@ -0,0 +1,1112 @@ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS metadata +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +REPLACE INTO metadata +VALUES ('DB_MAJOR', 4, 'DB major version number'); +REPLACE INTO metadata +VALUES ('DB_MINOR', 0, 'DB minor version number'); +REPLACE INTO metadata +VALUES ('days_per_period', 365, 'count of days in each period'); + +CREATE TABLE IF NOT EXISTS metadata_real +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +REPLACE INTO metadata_real +VALUES ('global_discount_rate', 0.05, 'Discount Rate for future costs'); +REPLACE INTO metadata_real +VALUES ('default_loan_rate', 0.05, 'Default Loan Rate if not specified in LoanRate table'); + +CREATE TABLE IF NOT EXISTS output_dual_variable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE IF NOT EXISTS output_objective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE IF NOT EXISTS season_label +( + season TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS sector_label +( + sector TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS capacity_credit +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_factor_process +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_factor_tech +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_to_activity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + c2a REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES commodity_type (label), + description TEXT +); +CREATE TABLE IF NOT EXISTS commodity_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO commodity_type +VALUES ('s', 'source commodity'); +REPLACE INTO commodity_type +VALUES ('a', 'annual commodity'); +REPLACE INTO commodity_type +VALUES ('p', 'physical commodity'); +REPLACE INTO commodity_type +VALUES ('d', 'demand commodity'); +REPLACE INTO commodity_type +VALUES ('e', 'emissions commodity'); +REPLACE INTO commodity_type +VALUES ('w', 'waste commodity'); +REPLACE INTO commodity_type +VALUES ('wa', 'waste annual commodity'); +REPLACE INTO commodity_type +VALUES ('wp', 'waste physical commodity'); +CREATE TABLE IF NOT EXISTS construction_input +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT NOT NULL + REFERENCES commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS cost_fixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_invest +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_variable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS demand +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + commodity TEXT + REFERENCES commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +CREATE TABLE IF NOT EXISTS demand_specific_distribution +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + demand_name TEXT + REFERENCES commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +CREATE TABLE IF NOT EXISTS end_of_life_output +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS efficiency +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS efficiency_variable +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS emission_activity +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS emission_embodied +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS emission_end_of_life +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS existing_capacity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS tech_group +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS loan_lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS loan_rate +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_tech +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +REPLACE INTO operator VALUES('e','equal to'); +REPLACE INTO operator VALUES('le','less than or equal to'); +REPLACE INTO operator VALUES('ge','greater than or equal to'); +CREATE TABLE IF NOT EXISTS limit_growth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_storage_level_fraction +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + fraction REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tod, tech, vintage, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_annual_capacity_factor +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS limit_capacity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_capacity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_resource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_seasonal_capacity_factor +( + region TEXT + REFERENCES region (region), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE IF NOT EXISTS linked_tech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES technology (tech), + emis_comm TEXT + REFERENCES commodity (name), + driven_tech TEXT + REFERENCES technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE IF NOT EXISTS output_curtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_period (period), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + curtailment REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_net_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_built_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_retired_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cap_eol REAL, + cap_early REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_flow_in +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_flow_out +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_storage_level +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + level REAL, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE IF NOT EXISTS planning_reserve_margin +( + region TEXT + PRIMARY KEY + REFERENCES region (region), + margin REAL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS ramp_down_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS ramp_up_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS reserve_capacity_derate +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS time_segment_fraction +( + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES season_label (season), + tod TEXT + REFERENCES time_of_day (tod), + segment_fraction REAL, + notes TEXT, + PRIMARY KEY (period, season, tod), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); +CREATE TABLE IF NOT EXISTS storage_duration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS lifetime_survival_curve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS technology_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO technology_type +VALUES ('p', 'production technology'); +REPLACE INTO technology_type +VALUES ('pb', 'baseload production technology'); +REPLACE INTO technology_type +VALUES ('ps', 'storage production technology'); +-- CREATE TABLE IF NOT EXISTS time_manual +-- ( +-- period INTEGER +-- REFERENCES time_period (period), +-- season TEXT +-- REFERENCES season_label (season), +-- tod TEXT +-- REFERENCES time_of_day (tod), +-- season_next TEXT +-- REFERENCES season_label (season), +-- tod_next TEXT +-- REFERENCES time_of_day (tod), +-- notes TEXT, +-- PRIMARY KEY (period, season, tod) +-- ); +CREATE TABLE IF NOT EXISTS time_of_day +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY +); +CREATE TABLE IF NOT EXISTS time_period +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES time_period_type (label) +); +CREATE TABLE IF NOT EXISTS time_season_all +( + period INTEGER + REFERENCES time_period (period), + sequence INTEGER, + season TEXT + REFERENCES season_label (season), + notes TEXT, + PRIMARY KEY (period, sequence, season) +); +CREATE TABLE IF NOT EXISTS time_season_to_sequential +( + period INTEGER + REFERENCES time_period (period), + sequence INTEGER, + seas_seq TEXT, + season TEXT + REFERENCES season_label (season), + num_days REAL NOT NULL, + notes TEXT, + PRIMARY KEY (period, sequence, seas_seq, season), + CHECK (num_days > 0) +); +CREATE TABLE IF NOT EXISTS time_period_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO time_period_type +VALUES('e', 'existing vintages'); +REPLACE INTO time_period_type +VALUES('f', 'future'); +CREATE TABLE IF NOT EXISTS output_emission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + emission REAL, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS rps_requirement +( + region TEXT NOT NULL + REFERENCES region (region), + period INTEGER NOT NULL + REFERENCES time_period (period), + tech_group TEXT NOT NULL + REFERENCES tech_group (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS tech_group_member +( + group_name TEXT + REFERENCES tech_group (group_name), + tech TEXT + REFERENCES technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE IF NOT EXISTS technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES technology_type (label) +); +CREATE TABLE IF NOT EXISTS output_cost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES sector_label (sector), + period INTEGER REFERENCES time_period (period), + tech TEXT REFERENCES technology (tech), + vintage INTEGER REFERENCES time_period (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES time_period (period), + FOREIGN KEY (tech) REFERENCES technology (tech) +); + +CREATE TABLE IF NOT EXISTS time_season +( + period INTEGER REFERENCES time_period (period), + sequence INTEGER, + season TEXT REFERENCES season_label(season), + notes TEXT, + PRIMARY KEY (period, sequence, season) +); + +CREATE TABLE IF NOT EXISTS time_season_sequential +( + period INTEGER REFERENCES time_period (period), + sequence INTEGER, + seas_seq TEXT, + season TEXT REFERENCES season_label(season), + num_days REAL NOT NULL, + notes TEXT, + PRIMARY KEY (period, sequence, seas_seq, season), + CHECK (num_days > 0) +); + +CREATE TABLE IF NOT EXISTS myopic_efficiency +( + base_year integer, + region text, + input_comm text, + tech text, + vintage integer, + output_comm text, + efficiency real, + lifetime integer, + + FOREIGN KEY (tech) REFERENCES technology (tech), + PRIMARY KEY (region, input_comm, tech, vintage, output_comm) +); +-- for efficient searching by rtv: +CREATE INDEX IF NOT EXISTS region_tech_vintage ON myopic_efficiency (region, tech, vintage); + +CREATE TABLE IF NOT EXISTS output_flow_out_summary +( + scenario TEXT NOT NULL, + region TEXT NOT NULL, + sector TEXT, + period INTEGER, + input_comm TEXT NOT NULL, + tech TEXT NOT NULL, + vintage INTEGER, + output_comm TEXT NOT NULL, + flow REAL NOT NULL, + + FOREIGN KEY (tech) REFERENCES technology (tech), + PRIMARY KEY (scenario, region, period, input_comm, tech, vintage, output_comm) +); + +COMMIT; +PRAGMA foreign_keys = ON; diff --git a/tests/test_cli.py b/tests/test_cli.py index 1075bf79..ba97d1e9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,8 +1,9 @@ +import shutil from pathlib import Path from typer.testing import CliRunner -from temoa.cli import app +from temoa.cli import _is_writable, app runner = CliRunner() @@ -37,11 +38,12 @@ def test_cli_run_command_success_silent(tmp_path): """Test a successful silent run of the `temoa run` command.""" db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' test_config_path = create_test_config(tmp_path, db_path) + # --output is explicitly given, so it should use tmp_path args = ['run', str(test_config_path), '--output', str(tmp_path), '--silent'] result = runner.invoke(app, args) assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' - assert 'Temoa run completed successfully' not in result.stdout + assert 'Temoa run completed successfully' not in result.stdout # Silent run assert (tmp_path / 'temoa-run.log').exists() @@ -49,11 +51,12 @@ def test_cli_run_build_only_silent(tmp_path): """Test the `temoa run --build-only --silent` flags.""" db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' test_config_path = create_test_config(tmp_path, db_path) + # --output is explicitly given, so it should use tmp_path args = ['run', str(test_config_path), '--output', str(tmp_path), '--build-only', '--silent'] result = runner.invoke(app, args) assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' - assert 'Model built successfully' not in result.stdout + assert 'Model built successfully' not in result.stdout # Silent run assert (tmp_path / 'temoa-run.log').exists() @@ -113,4 +116,261 @@ def test_cli_run_missing_config(): assert result.exit_code != 0 # Check that the error mentions the missing file (more robust than exact string match) assert 'non_existent_file.toml' in result.stderr - assert 'does not' in result.stderr and 'exist' in result.stderr + + +# ============================================================================= +# Tests for the `migrate` command +# ============================================================================= + + +def test_cli_migrate_help(): + """Test the `temoa migrate --help` command.""" + result = runner.invoke(app, ['migrate', '--help']) + assert result.exit_code == 0 + assert 'migrate' in result.stdout + assert 'Migrate a single Temoa database file' in result.stdout + + +def test_cli_migrate_sql_file(tmp_path): + """Test migrating a SQL file with explicit --output.""" + # Ensure input file is available in the test environment (e.g., copied from data_files) + input_file_src = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = tmp_path / 'test_input.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_explicit.sql' + args = ['migrate', str(input_file), '--output', str(output_file)] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + assert 'SQL dump migration completed' in result.stdout + assert output_file.exists() + + +def test_cli_migrate_rejects_directory_input(tmp_path): + """Test that the migrate command rejects a directory as input.""" + dummy_dir = tmp_path / 'my_dummy_dir' + dummy_dir.mkdir() + args = ['migrate', str(dummy_dir)] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + assert 'Error: Input path must be a file, not a directory:' in result.stdout + assert str(dummy_dir) in result.stdout + + +def test_cli_migrate_sql_file_auto_output_writable_input_dir(tmp_path): + """ + Test migrating a SQL file without --output, + where the input directory is writable. + Output should be next to input with _v4.sql suffix. + """ + src_file = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = tmp_path / src_file.name # Input file in writable tmp_path + shutil.copy2(src_file, input_file) + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert 'SQL dump migration completed' in result.stdout + expected_output = input_file.with_stem(input_file.stem + '_v4').with_suffix( + '.sql' + ) # Explicit .sql suffix + assert expected_output.exists() + + +def test_cli_migrate_sql_file_auto_output_non_writable_input_dir_fallback_cwd( + tmp_path, monkeypatch +): + """ + Test migrating a SQL file without --output, + where the input directory is NOT writable. + Output should fall back to current working directory (mocked as tmp_path) + and have a _v4.sql suffix. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent' + non_writable_mock_parent.mkdir() + + src_file = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + def mock_is_writable(path: Path) -> bool: + if path == non_writable_mock_parent: + return False + if path == tmp_path: # CWD for the test runner is tmp_path + return True + return _is_writable(path) + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable) + monkeypatch.setattr( + Path, + 'cwd', + classmethod(lambda cls: tmp_path), + ) # Ensure CWD is tmp_path for logging + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert 'SQL dump migration completed' in result.stdout + assert 'Warning: Input directory' in result.stdout + assert str(non_writable_mock_parent) in result.stdout + assert 'is not writable.' in result.stdout + assert 'Saving output to current directory:' in result.stdout + assert str(tmp_path) in result.stdout + + expected_output_in_cwd = tmp_path / (input_file.stem + '_v4.sql') + assert expected_output_in_cwd.exists() + assert not (non_writable_mock_parent / (input_file.stem + '_v4.sql')).exists() + + +def test_cli_migrate_sql_file_auto_output_no_writable_location(tmp_path, monkeypatch): + """ + Test migrating a SQL file without --output, + where neither the input directory nor the CWD are writable. + Should exit with an error. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent_no_cwd' + non_writable_mock_parent.mkdir() + + src_file = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + # Mock _is_writable to return False for both the input directory and the CWD (tmp_path) + def mock_is_writable_always_false(_path: Path) -> bool: + return False + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable_always_false) + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code != 0, 'Migration should fail with a non-zero exit code' + assert 'Error: Neither input directory' in result.stdout + assert 'nor current working directory' in result.stdout + assert 'are writable.' in result.stdout + assert not ( + tmp_path / (input_file.stem + '_v4' + input_file.suffix) + ).exists() # No output created + + +def test_cli_migrate_invalid_file(): + """Test migrating a non-existent file.""" + args = ['migrate', 'non_existent.sql'] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + # Typer handles file existence check, so error is in stderr + assert 'does not exist' in result.stderr or 'does not exist' in str(result.exception) + + +def test_cli_migrate_unknown_type(tmp_path): + """Test migrating a file with unknown extension.""" + unknown_file = tmp_path / 'unknown.txt' + unknown_file.write_text('dummy') + args = ['migrate', str(unknown_file)] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + assert 'Cannot determine migration type' in result.stdout + + +def test_cli_migrate_override_type(tmp_path): + """Test migrating with explicit type override.""" + input_file_src = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = tmp_path / 'test_input_override.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_override.sql' + args = ['migrate', str(input_file), '--output', str(output_file), '--type', 'sql'] + result = runner.invoke(app, args) + + assert result.exit_code == 0 + assert 'SQL dump migration completed' in result.stdout + assert output_file.exists() + + +def test_cli_migrate_sql_file_silent(tmp_path): + """Test migrating a SQL file with --silent flag.""" + input_file_src = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = tmp_path / 'test_input_silent.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_silent.sql' + args = ['migrate', str(input_file), '--output', str(output_file), '--silent'] # Use --silent + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + # In silent mode, success messages should NOT be in stdout + assert 'SQL dump migration completed' not in result.stdout + assert output_file.exists() + + +def test_cli_migrate_db_file_silent(tmp_path): + """Test migrating a DB file with --silent flag.""" + import sqlite3 # Ensure sqlite3 is imported for this test if not already at top level + + input_file = tmp_path / 'test_v3_1_silent.sqlite' + conn = sqlite3.connect(input_file) + conn.execute('CREATE TABLE MetaData (name TEXT, value TEXT)') + conn.execute("INSERT INTO MetaData VALUES ('DB_MAJOR', '3')") + conn.commit() + conn.close() + + output_file = tmp_path / 'migrated_silent.sqlite' + args = ['migrate', str(input_file), '--output', str(output_file), '--silent'] # Use --silent + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + # In silent mode, success messages should NOT be in stdout + assert 'Database migration completed' not in result.stdout + assert output_file.exists() + + +def test_cli_migrate_sql_file_auto_output_non_writable_input_dir_fallback_cwd_silent( + tmp_path, monkeypatch +): + """ + Test migrating a SQL file with --silent, where input dir is not writable. + Output should fall back to CWD, and the warning should NOT be printed. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent_silent' + non_writable_mock_parent.mkdir() + + src_file = Path(__file__).parent.parent / 'data_files' / 'temoa_basics_0.sql' + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + def mock_is_writable(path: Path) -> bool: + if path == non_writable_mock_parent: + return False + if path == tmp_path: + return True + return _is_writable(path) + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable) + monkeypatch.setattr( + Path, + 'cwd', + classmethod(lambda cls: tmp_path), + ) + + args = ['migrate', str(input_file), '--silent'] # Use --silent here + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert 'SQL dump migration completed' not in result.stdout # Should be silent + assert 'Warning: Input directory' not in result.stdout # Warning should be silent + + expected_output_in_cwd = tmp_path / (input_file.stem + '_v4.sql') + assert expected_output_in_cwd.exists() + assert not (non_writable_mock_parent / (input_file.stem + '_v4.sql')).exists()