From 3b19e01e19f90d5af0bde29ddc83ee68166e76c9 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Mon, 30 Mar 2026 15:18:05 -0400 Subject: [PATCH 01/10] Add v5.5 smoke harness and sync notes --- docs/UPSTREAM_SYNC.md | 59 ++++++++++++++++++++++++++++++++++ scripts/smoke.bat | 42 +++++++++++++++++++++++++ scripts/smoke.sh | 38 ++++++++++++++++++++++ tests/test_app_smoke.py | 70 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 docs/UPSTREAM_SYNC.md create mode 100644 scripts/smoke.bat create mode 100755 scripts/smoke.sh create mode 100644 tests/test_app_smoke.py diff --git a/docs/UPSTREAM_SYNC.md b/docs/UPSTREAM_SYNC.md new file mode 100644 index 000000000..55e3fb80b --- /dev/null +++ b/docs/UPSTREAM_SYNC.md @@ -0,0 +1,59 @@ +# Upstream Sync Notes + +Use this when pulling a new upstream `MUIO` release into `MUIOGO`. + +## Baseline + +- Start from `origin/main`. +- Compare against the upstream release tag, not `upstream/master`, unless there is a specific reason to include later commits. +- Do not build from exploratory merge branches or dirty worktrees. + +## Review First + +These files are the main overlap surface and should always be reviewed directly against upstream: + +- `API/app.py` +- `API/Classes/Base/Config.py` +- `API/Classes/Base/FileClass.py` +- `API/Classes/Case/DataFileClass.py` +- `API/Classes/Case/OsemosysClass.py` +- `API/Routes/DataFile/DataFileRoute.py` +- `API/Routes/Upload/UploadRoute.py` +- `WebAPP/index.html` +- `WebAPP/Classes/Osemosys.Class.js` +- `WebAPP/Classes/Html.Class.js` +- `WebAPP/Classes/Const.Class.js` +- `WebAPP/Classes/DataModelResult.Class.js` +- `WebAPP/AppResults/Controller/Pivot.js` +- `WebAPP/DataStorage/Variables.json` + +## Reject As-Is + +Do not take these upstream patterns without a deliberate compatibility decision: + +- cwd-relative path regressions +- `WebAPP/app.log` or any other log path under the static web tree +- deleting logs on startup +- `shell=True` solver calls +- dormant files that are not actually wired into the app, such as `FileClassCompressed.py` +- removals of MUIOGO-specific repo infrastructure under `.github/`, `docs/`, `scripts/`, or repo assets +- frontend churn unrelated to the approved sync scope, such as `Home.js` event regressions, `app.config.js`, or theme/image swaps + +## Repeatable Checks + +Run these before starting the port and after each stacked branch lands: + +```bash +./scripts/setup.sh --check +python -m py_compile API/app.py +./scripts/smoke.sh +git ls-files -u +git grep -n -E '^(<<<<<<<|=======|>>>>>>>)($| )' -- . || true +``` + +Notes: + +- `git ls-files -u` must return nothing. That is the real unresolved-merge check. +- The conflict-marker scan is a secondary check and should not replace the Git index check. +- Smoke tests should not depend on the repo root being writable and should be run with the installed MUIOGO interpreter, not whichever `python` happens to be on PATH. +- The smoke command assumes MUIOGO was installed correctly with `./scripts/setup.sh`. If you used a custom `--venv-dir`, activate that venv first or set `MUIOGO_VENV_PYTHON` explicitly. diff --git a/scripts/smoke.bat b/scripts/smoke.bat new file mode 100644 index 000000000..317be57ad --- /dev/null +++ b/scripts/smoke.bat @@ -0,0 +1,42 @@ +@echo off +setlocal enabledelayedexpansion + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_ROOT=%SCRIPT_DIR%.." +set "DEFAULT_VENV_PY=%USERPROFILE%\.venvs\muiogo\Scripts\python.exe" +set "ACTIVE_VENV_PY=%VIRTUAL_ENV%\Scripts\python.exe" + +if not "%CONDA_DEFAULT_ENV%"=="" ( + echo ERROR: Conda environment "%CONDA_DEFAULT_ENV%" is active. + echo Run conda deactivate until no conda env is active, then re-run smoke. + exit /b 1 +) + +if not "%MUIOGO_VENV_PYTHON%"=="" ( + if not exist "%MUIOGO_VENV_PYTHON%" ( + echo ERROR: MUIOGO_VENV_PYTHON is set but not executable: %MUIOGO_VENV_PYTHON% + exit /b 1 + ) + set "PYTHON=%MUIOGO_VENV_PYTHON%" + goto :run +) + +if not "%VIRTUAL_ENV%"=="" if exist "%ACTIVE_VENV_PY%" ( + set "PYTHON=%ACTIVE_VENV_PY%" + goto :run +) + +if exist "%DEFAULT_VENV_PY%" ( + set "PYTHON=%DEFAULT_VENV_PY%" + goto :run +) + +echo ERROR: MUIOGO is not installed in a supported venv. +echo Run scripts\setup.bat first. +echo If you used a custom venv path, activate it before running smoke or set MUIOGO_VENV_PYTHON to that interpreter. +exit /b 1 + +:run +echo Using Python: +%PYTHON% --version +%PYTHON% -m unittest discover -s "%PROJECT_ROOT%\tests" -p "test_*smoke.py" diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100755 index 000000000..b05d042fa --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DEFAULT_VENV_PY="${HOME}/.venvs/muiogo/bin/python" +ACTIVE_VENV_PY="${VIRTUAL_ENV:-}/bin/python" +CONFIGURED_VENV_PY="${MUIOGO_VENV_PYTHON:-}" + +if [ -n "${CONDA_DEFAULT_ENV:-}" ]; then + echo "ERROR: Conda environment '${CONDA_DEFAULT_ENV}' is active." + echo "Run 'conda deactivate' (repeat until your prompt no longer shows a conda env), then re-run smoke." + exit 1 +fi + +PYTHON="" + +if [ -n "$CONFIGURED_VENV_PY" ]; then + if [ ! -x "$CONFIGURED_VENV_PY" ]; then + echo "ERROR: MUIOGO_VENV_PYTHON is set but not executable: $CONFIGURED_VENV_PY" + exit 1 + fi + PYTHON="$CONFIGURED_VENV_PY" +elif [ -n "${VIRTUAL_ENV:-}" ] && [ -x "$ACTIVE_VENV_PY" ]; then + PYTHON="$ACTIVE_VENV_PY" +elif [ -x "$DEFAULT_VENV_PY" ]; then + PYTHON="$DEFAULT_VENV_PY" +fi + +if [ -z "$PYTHON" ]; then + echo "ERROR: MUIOGO is not installed in a supported venv." + echo "Run ./scripts/setup.sh first." + echo "If you used a custom venv path, activate it before running smoke or set MUIOGO_VENV_PYTHON to that interpreter." + exit 1 +fi + +echo "Using Python: $($PYTHON --version) at $(command -v "$PYTHON" || printf '%s' "$PYTHON")" +exec "$PYTHON" -m unittest discover -s "$PROJECT_ROOT/tests" -p "test_*smoke.py" diff --git a/tests/test_app_smoke.py b/tests/test_app_smoke.py new file mode 100644 index 000000000..79f7591cf --- /dev/null +++ b/tests/test_app_smoke.py @@ -0,0 +1,70 @@ +import importlib +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +API_DIR = PROJECT_ROOT / "API" + + +class AppSmokeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + sys.path.insert(0, str(API_DIR)) + os.environ.setdefault("MUIOGO_SECRET_KEY", "smoke-test-secret") + cls.app_module = importlib.import_module("app") + cls.client = cls.app_module.app.test_client() + + def test_app_import_from_arbitrary_cwd(self): + env = os.environ.copy() + env["PYTHONPATH"] = str(API_DIR) + env.setdefault("MUIOGO_SECRET_KEY", "smoke-test-secret") + + with tempfile.TemporaryDirectory() as tmpdir: + result = subprocess.run( + [sys.executable, "-c", "import app; print(app.app.import_name)"], + cwd=tmpdir, + env=env, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, msg=result.stderr) + self.assertIn("app", result.stdout.strip()) + + def test_home_route(self): + response = self.client.get("/") + + self.assertEqual(response.status_code, 200) + self.assertIn(b"", response.data) + + def test_get_session_route(self): + response = self.client.get("/getSession") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get_json(), {"session": None}) + + def test_clear_session_route(self): + response = self.client.post("/setSession", json={"case": None}) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get_json(), {"osycase": None}) + + def test_repo_has_no_unmerged_paths(self): + result = subprocess.run( + ["git", "ls-files", "-u"], + cwd=PROJECT_ROOT, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, msg=result.stderr) + self.assertEqual(result.stdout.strip(), "") + + +if __name__ == "__main__": + unittest.main() From 0054a3e766448cb4d1ca4a01aceb17a0f752695a Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Mon, 30 Mar 2026 15:37:50 -0400 Subject: [PATCH 02/10] Sync v5.5 runtime fixes and model file --- .gitignore | 1 + API/Classes/Base/Config.py | 26 ++++++ API/Classes/Case/DataFileClass.py | 119 ++++++++++++--------------- API/Routes/DataFile/DataFileRoute.py | 41 +++++++-- API/app.py | 64 +++++++++----- WebAPP/SOLVERs/model.v.5.4.txt | 48 +---------- 6 files changed, 158 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index 71cc7f3ae..9bf55330a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ assets/demo-data/CLEWs.Demo.zip Procfile .env +.runtime/ dist/ build/ venv/ diff --git a/API/Classes/Base/Config.py b/API/Classes/Base/Config.py index 49a889043..23e59f3a5 100644 --- a/API/Classes/Base/Config.py +++ b/API/Classes/Base/Config.py @@ -1,5 +1,6 @@ from pathlib import Path import os +import tempfile from dotenv import load_dotenv import platform @@ -62,6 +63,10 @@ def validate_path(base_dir, user_input): CLASS_FOLDER = WEBAPP_PATH / "Classes" SOLVERs_FOLDER = WEBAPP_PATH / "SOLVERs" EXTRACT_FOLDER = BASE_DIR +PRIMARY_RUNTIME_DIR = BASE_DIR / ".runtime" +PRIMARY_RUNTIME_LOG_DIR = PRIMARY_RUNTIME_DIR / "logs" +TEMP_RUNTIME_LOG_DIR = Path(tempfile.gettempdir()) / "muiogo-runtime" / "logs" +_RUNTIME_LOG_PATH = None # Ensure DataStorage exists DATA_STORAGE.mkdir(parents=True, exist_ok=True) @@ -69,6 +74,27 @@ def validate_path(base_dir, user_input): # Validate writability instead of forcing permissions if not os.access(DATA_STORAGE, os.W_OK): raise PermissionError(f"Data storage path is not writable: {DATA_STORAGE}") + + +def get_runtime_log_path(): + global _RUNTIME_LOG_PATH + + if _RUNTIME_LOG_PATH is not None: + return _RUNTIME_LOG_PATH + + for log_dir in (PRIMARY_RUNTIME_LOG_DIR, TEMP_RUNTIME_LOG_DIR): + try: + log_dir.mkdir(parents=True, exist_ok=True) + probe = log_dir / ".write_test" + with open(probe, "a", encoding="utf-8"): + pass + probe.unlink(missing_ok=True) + _RUNTIME_LOG_PATH = log_dir / "app.log" + return _RUNTIME_LOG_PATH + except OSError: + continue + + raise PermissionError("No writable runtime log directory is available.") #absolute paths # OSEMOSYS_ROOT = os.path.abspath(os.getcwd()) # UPLOAD_FOLDER = Path(OSEMOSYS_ROOT, 'WebAPP') diff --git a/API/Classes/Case/DataFileClass.py b/API/Classes/Case/DataFileClass.py index bd5cc38b1..b3fc93190 100644 --- a/API/Classes/Case/DataFileClass.py +++ b/API/Classes/Case/DataFileClass.py @@ -792,17 +792,16 @@ def createCaseRun(self, caserunname, data): try: caseRunPath = Path(Config.DATA_STORAGE,self.case,'res', caserunname) csvPath = Path(Config.DATA_STORAGE,self.case,'res', caserunname, 'csv') - resDataPath = Path(Config.DATA_STORAGE,self.case,'view', 'resData.json') if not os.path.exists(caseRunPath): os.makedirs(caseRunPath) os.makedirs(csvPath) - if not os.path.exists(resDataPath): - File.writeFile( data, resDataPath) + if not os.path.exists(self.resDataPath): + self.resData = {"osy-cases": [data]} + File.writeFile(self.resData, self.resDataPath) else: - resData = File.readFile(resDataPath) - resData['osy-cases'].append(data) - File.writeFile( resData, resDataPath) + self.resData['osy-cases'].append(data) + File.writeFile(self.resData, self.resDataPath) response = { "message": "You have created a case run!", "status_code": "success" @@ -822,8 +821,7 @@ def createCaseRun(self, caserunname, data): def deleteScenarioCaseRuns(self, scenarioId): try: - resData = File.readFile(self.resDataPath) - cases = resData['osy-cases'] + cases = self.resData['osy-cases'] for cs in cases: for sc in cs['Scenarios']: @@ -831,7 +829,7 @@ def deleteScenarioCaseRuns(self, scenarioId): cs['Scenarios'].remove(sc) - File.writeFile(resData, self.resDataPath) + File.writeFile(self.resData, self.resDataPath) response = { "message": "You have deleted scenario from caseruns!", "status_code": "success" @@ -850,7 +848,6 @@ def updateCaseRun(self, caserunname, oldcaserunname, data): caseRunPath = Path(Config.DATA_STORAGE,self.case,'res', oldcaserunname) newcaseRunPath = Path(Config.DATA_STORAGE,self.case,'res', caserunname) csvPath = Path(Config.DATA_STORAGE,self.case,'res', caserunname, 'csv') - resDataPath = Path(Config.DATA_STORAGE,self.case,'view', 'resData.json') if not os.path.exists(newcaseRunPath): os.rename(caseRunPath, newcaseRunPath) @@ -858,14 +855,12 @@ def updateCaseRun(self, caserunname, oldcaserunname, data): if not os.path.exists(csvPath): os.makedirs(csvPath) - resData = File.readFile(resDataPath) - - resdata = resData['osy-cases'] + resdata = self.resData['osy-cases'] for i, case in enumerate(resdata): if case['Case'] == oldcaserunname: - resData['osy-cases'][i] = data + self.resData['osy-cases'][i] = data - File.writeFile( resData, resDataPath) + File.writeFile(self.resData, self.resDataPath) response = { "message": "You have updated a case run!", "status_code": "success" @@ -874,14 +869,12 @@ def updateCaseRun(self, caserunname, oldcaserunname, data): if not os.path.exists(csvPath): os.makedirs(csvPath) - resData = File.readFile(resDataPath) - - resdata = resData['osy-cases'] + resdata = self.resData['osy-cases'] for i, case in enumerate(resdata): if case['Case'] == oldcaserunname: - resData['osy-cases'][i] = data + self.resData['osy-cases'][i] = data - File.writeFile( resData, resDataPath) + File.writeFile(self.resData, self.resDataPath) response = { "message": "You have updated a case run!", "status_code": "success" @@ -924,17 +917,15 @@ def deleteCaseResultsJSON(self, caserunname): def deleteCaseRun(self, caserunname, resultsOnly): try: #caseRunPath = Path(Config.DATA_STORAGE,self.case,'res', caserunname) - #resDataPath = Path(Config.DATA_STORAGE,self.case,'view', 'resData.json') + #self.resData = Path(Config.DATA_STORAGE,self.case,'view', 'resData.json') if not resultsOnly: - resData = File.readFile(self.resDataPath) - - for obj in resData['osy-cases']: + for obj in self.resData['osy-cases']: if obj['Case'] == caserunname: - resData['osy-cases'].remove(obj) + self.resData['osy-cases'].remove(obj) - File.writeFile( resData, self.resDataPath) + File.writeFile(self.resData, self.resDataPath) #delete from view folder for group, array in self.VARIABLES.items(): @@ -970,45 +961,38 @@ def cleanUp(self): # self.viewFolderPath = Path(Config.DATA_STORAGE,case,'view') # folder_path = "C:/putanja/do/foldera" - for caserunname in os.listdir( self.resultsPath): - caserunname_path = os.path.join(self.resultsPath, caserunname) - # Skip files such as .DS_Store that can appear on macOS. - if not os.path.isdir(caserunname_path): - continue - for carerunData in os.listdir( caserunname_path): - file_path = os.path.join(caserunname_path, carerunData) - try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.remove(file_path) - elif os.path.isdir(file_path): - shutil.rmtree(file_path) - except Exception as e: - print(f"Greška pri brisanju {file_path}: {e}") - - for filename in os.listdir( self.viewFolderPath): - if filename !='resData.json': - file_path = os.path.join(self.viewFolderPath, filename) - try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.remove(file_path) - elif os.path.isdir(file_path): - shutil.rmtree(file_path) - except Exception as e: - print(f"Greška pri brisanju {file_path}: {e}") - - #sad moramo napraviti defualt definitions file - viewDefPath = Path(self.viewFolderPath, 'viewDefinitions.json') - configPath = Path(Config.DATA_STORAGE, 'Variables.json') - vars = File.readParamFile(configPath) - viewDef = {} - for group, lists in vars.items(): - for list in lists: - viewDef[list['id']] = [] - - viewData = { - "osy-views": viewDef - } - File.writeFile( viewData, viewDefPath) + if os.path.exists(self.resultsPath) and os.path.isdir(self.resultsPath): + for caserunname in os.listdir(self.resultsPath): + caserunname_path = os.path.join(self.resultsPath, caserunname) + if not os.path.isdir(caserunname_path): + continue + for carerunData in os.listdir(caserunname_path): + file_path = os.path.join(caserunname_path, carerunData) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.remove(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print(f"Greška pri brisanju {file_path}: {e}") + + if os.path.exists(self.viewFolderPath) and os.path.isdir(self.viewFolderPath): + for filename in os.listdir(self.viewFolderPath): + if filename not in {'resData.json', 'viewDefinitions.json'}: + file_path = os.path.join(self.viewFolderPath, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.remove(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print(f"Greška pri brisanju {file_path}: {e}") + + case_names = [c["Case"] for c in self.resData.get("osy-cases", [])] + for case in case_names: + case_path = Path(self.resultsPath, case) + if not case_path.exists(): + case_path.mkdir(parents=True, exist_ok=True) response = { @@ -1073,9 +1057,8 @@ def readDataFile( self, caserunname ): #f = open(self.dataFile, mode="r") dataFilePath = Path(Config.DATA_STORAGE, self.case, 'res',caserunname,'data.txt') if os.path.exists(dataFilePath): - f = open(dataFilePath, mode="r", encoding='utf-8-sig') - data = f.read() - f.close + with open(dataFilePath, mode="r", encoding='utf-8-sig') as f: + data = f.read() else: data = None diff --git a/API/Routes/DataFile/DataFileRoute.py b/API/Routes/DataFile/DataFileRoute.py index 2b7e98eff..c4cb89e9c 100644 --- a/API/Routes/DataFile/DataFileRoute.py +++ b/API/Routes/DataFile/DataFileRoute.py @@ -1,9 +1,11 @@ -from flask import Blueprint, jsonify, request, send_file, session +from flask import Blueprint, Response, jsonify, request, send_file, session from pathlib import Path -import shutil, datetime, time, os +import shutil, datetime, time, os, logging from Classes.Case.DataFileClass import DataFile from Classes.Base import Config +logger = logging.getLogger(__name__) + datafile_api = Blueprint('DataFileRoute', __name__) @datafile_api.route("/generateDataFile", methods=['POST']) @@ -141,6 +143,31 @@ def readDataFile(): return jsonify(response), 200 except(IOError): return jsonify('No existing cases!'), 404 + +@datafile_api.route("/readModelFile", methods=['GET']) +def readModelFile(): + model_path = Path(Config.SOLVERs_FOLDER, 'model.v.5.4.txt') + if not model_path.is_file(): + return jsonify({'message': 'Model file not found.', 'status_code': 'error'}), 404 + + text = model_path.read_text(encoding="utf-8", errors="replace") + return Response(text, mimetype="text/plain; charset=utf-8") + + +@datafile_api.route("/readLogFile", methods=['GET']) +def readLogFile(): + try: + log_path = Config.get_runtime_log_path() + except OSError: + return Response("Runtime logging is not available.\n", mimetype="text/plain; charset=utf-8") + + if not log_path.is_file(): + return Response("No runtime log available yet.\n", mimetype="text/plain; charset=utf-8") + + text = log_path.read_text(encoding="utf-8", errors="replace") + if not text.strip(): + return Response("No runtime log available yet.\n", mimetype="text/plain; charset=utf-8") + return Response(text, mimetype="text/plain; charset=utf-8") @datafile_api.route("/validateInputs", methods=['POST']) def validateInputs(): @@ -218,8 +245,10 @@ def run(): casename = request.json['casename'] caserunname = request.json['caserunname'] solver = request.json['solver'] + logger.info("Starting optimization process for model %s caserun %s", casename, caserunname) txtFile = DataFile(casename) - response = txtFile.run(solver, caserunname) + response = txtFile.run(solver, caserunname) + logger.info("Optimization finished for model %s caserun %s", casename, caserunname) return jsonify(response), 200 # except Exception as ex: # print(ex) @@ -238,6 +267,7 @@ def batchRun(): if modelname != None: txtFile = DataFile(modelname) for caserun in cases: + logger.info("Generating data file for model %s caserun %s", modelname, caserun) txtFile.generateDatafile(caserun) response = txtFile.batchRun( 'CBC', cases) @@ -254,8 +284,9 @@ def cleanUp(): if modelname != None: model = DataFile(modelname) - response = model.cleanUp() + logger.info("Cleaning up results for model %s", modelname) + response = model.cleanUp() return jsonify(response), 200 except(IOError): - return jsonify('Error!'), 404 \ No newline at end of file + return jsonify('Error!'), 404 diff --git a/API/app.py b/API/app.py index 458aad034..3651fbfab 100644 --- a/API/app.py +++ b/API/app.py @@ -3,6 +3,8 @@ import os import secrets import sys +import warnings +from logging.handlers import TimedRotatingFileHandler # Fail fast: unsupported Python hits cryptic pandas/numpy import errors without this. SUPPORTED_PYTHON_MIN = (3, 10) @@ -34,31 +36,52 @@ from Routes.Case.ViewDataRoute import viewdata_api from Routes.DataFile.DataFileRoute import datafile_api -#RADI -# ------------------------- -# FIX: Make template/static paths independent of cwd -# ------------------------- +def _configure_logging(): + if getattr(_configure_logging, "_configured", False): + return getattr(_configure_logging, "_log_path", None) -# This file is in: API/app.py -# So project root is 1 level up -BASE_DIR = Path(__file__).resolve().parents[1] -WEBAPP_PATH = BASE_DIR / "WebAPP" + logger = logging.getLogger() + logger.setLevel(logging.INFO) -template_dir = str(WEBAPP_PATH) -static_dir = str(WEBAPP_PATH) + formatter = logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s") -# template_dir = Config.WebAPP_PATH.resolve() -# static_dir = Config.WebAPP_PATH.resolve() + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler._muiogo_handler = True + logger.addHandler(console_handler) + + log_path = None + try: + log_path = Config.get_runtime_log_path() + file_handler = TimedRotatingFileHandler( + log_path, when="midnight", interval=1, backupCount=7, encoding="utf-8" + ) + file_handler.setFormatter(formatter) + file_handler._muiogo_handler = True + logger.addHandler(file_handler) + except OSError as exc: + logger.warning("Runtime log file unavailable. Continuing with console logging only: %s", exc) + + logging.captureWarnings(True) + warnings.simplefilter("default") + + def log_exception(exc_type, exc_value, exc_traceback): + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + logger.error("UNCAUGHT EXCEPTION", exc_info=(exc_type, exc_value, exc_traceback)) + + sys.excepthook = log_exception + + _configure_logging._configured = True + _configure_logging._log_path = log_path + return log_path -# template_dir = os.path.join(sys._MEIPASS, 'WebAPP') -# static_dir = os.path.join(sys._MEIPASS, 'WebAPP') -#gets absolute path -# template_dir = Path('WebAPP').resolve() -# static_dir = Path('../WebAPP').resolve() +RUNTIME_LOG_PATH = _configure_logging() -# template_dir = 'WebAPP' -# static_dir = '../WebAPP' +template_dir = str(Config.WEBAPP_PATH) +static_dir = str(Config.WEBAPP_PATH) app = Flask(__name__, static_url_path='', static_folder=static_dir, template_folder=template_dir) @@ -66,7 +89,7 @@ secret_key = os.environ.get("MUIOGO_SECRET_KEY", "").strip() if not secret_key: secret_key = secrets.token_hex(32) - logging.warning( + logging.getLogger(__name__).warning( "MUIOGO_SECRET_KEY is not configured. Using a temporary in-memory key. " "Run setup to create a persistent secret in .env." ) @@ -136,7 +159,6 @@ def setSession(): session.pop('osycase', None) return jsonify({"osycase": None}), 200 - from pathlib import Path if not Path(Config.DATA_STORAGE, cs).is_dir(): return jsonify({'message': 'Case not found.', 'status_code': 'error'}), 404 session['osycase'] = cs diff --git a/WebAPP/SOLVERs/model.v.5.4.txt b/WebAPP/SOLVERs/model.v.5.4.txt index 749ace889..d858f3f51 100644 --- a/WebAPP/SOLVERs/model.v.5.4.txt +++ b/WebAPP/SOLVERs/model.v.5.4.txt @@ -23,19 +23,14 @@ set MODExTECHNOLOGYperSTORAGEto{STORAGE} within MODE_OF_OPERATION cross TECHNOLO set MODExTECHNOLOGYperSTORAGEfrom{STORAGE} within MODE_OF_OPERATION cross TECHNOLOGY; set MODExTECHNOLOGYperEMISSION{e in EMISSION} within MODE_OF_OPERATION cross TECHNOLOGY; set MODExTECHNOLOGYperEMISSIONChange{e in EMISSION} within MODE_OF_OPERATION cross TECHNOLOGY; - set INPUTxNEWxCAPACITYperFUEL{COMMODITY} within TECHNOLOGY; set INPUTxTOTALxCAPACITYperFUEL{COMMODITY} within TECHNOLOGY; set INPUTxFUEL; - # Build pair sets from your per-fuel mappings set INPUT_TF_PAIRS_NEW := setof {f in INPUTxFUEL, t in INPUTxNEWxCAPACITYperFUEL[f]} (t,f); - set INPUT_TF_PAIRS_TOTAL := setof {f in INPUTxFUEL, t in INPUTxTOTALxCAPACITYperFUEL[f]} (t,f); - - # ##################### # Parameters # @@ -87,7 +82,6 @@ param UDCMultiplierNewCapacity{r in REGION, t in TECHNOLOGY, u in UDC, y in YEAR param UDCMultiplierActivity{r in REGION, t in TECHNOLOGY, u in UDC, y in YEAR}; param UDCConstant{r in REGION, u in UDC, y in YEAR}; param UDCTag{r in REGION, u in UDC}; - param CapitalRecoveryFactor{r in REGION, t in TECHNOLOGY}; param PvAnnuity{r in REGION, t in TECHNOLOGY}; ########################Storage added VK @@ -96,10 +90,8 @@ param CapitalCostStorage{r in REGION, s in STORAGE, y in YEAR}; param ResidualStorageCapacity{r in REGION, s in STORAGE, y in YEAR}; param TechnologyToStorage{r in REGION, t in TECHNOLOGY, s in STORAGE, m in MODE_OF_OPERATION}; param TechnologyFromStorage{r in REGION, t in TECHNOLOGY, s in STORAGE, m in MODE_OF_OPERATION}; - param StorageLevelStart{r in REGION, s in STORAGE}; param MinStorageCharge{r in REGION, s in STORAGE, y in YEAR}; - param Conversionls{l in TIMESLICE, ls in SEASON}; param Conversionld{l in TIMESLICE, ld in DAYTYPE}; param Conversionlh{l in TIMESLICE, lh in DAILYTIMEBRACKET}; @@ -111,8 +103,6 @@ set TIMESLICEofSEASON{ls in SEASON} within TIMESLICE := {l in TIMESLICE : Conver set TIMESLICEofDAYTYPE{ld in DAYTYPE} within TIMESLICE := {l in TIMESLICE : Conversionld[l,ld] = 1}; set TIMESLICEofDAILYTIMEBRACKET{lh in DAILYTIMEBRACKET} within TIMESLICE := {l in TIMESLICE : Conversionlh[l,lh] = 1}; set TIMESLICEofSDB{ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET} within TIMESLICE := TIMESLICEofSEASON[ls] inter TIMESLICEofDAYTYPE[ld] inter TIMESLICEofDAILYTIMEBRACKET[lh]; - - # ########################## # Model Variables # @@ -128,14 +118,10 @@ var Demand{r in REGION, l in TIMESLICE, f in COMMODITY, y in YEAR} >= 0; var DiscountedSalvageValue{r in REGION, t in TECHNOLOGY, y in YEAR} >= 0; #var InputToNewCapacity{r in REGION, t in TECHNOLOGY, f in COMMODITY, y in YEAR} >= 0; #var InputToTotalCapacity{r in REGION, t in TECHNOLOGY, f in COMMODITY, y in YEAR} >= 0; - - - # Sparse variables: only for technologies that actually use fuel f #var InputToNewCapacity{r in REGION, f in COMMODITY, t in INPUTxNEWxCAPACITYperFUEL[f], y in YEAR} >= 0; #var InputToTotalCapacity{r in REGION, f in COMMODITY, t in INPUTxTOTALxCAPACITYperFUEL[f], y in YEAR} >= 0; - # Variables over sparse domains (choose a consistent index order) var InputToNewCapacity {r in REGION, t in TECHNOLOGY,f in COMMODITY, y in YEAR: @@ -145,7 +131,6 @@ var InputToTotalCapacity {r in REGION, t in TECHNOLOGY, f in COMMODITY,y in YEAR: (t,f) in INPUT_TF_PAIRS_TOTAL} >= 0; - var NewCapacity{r in REGION, t in TECHNOLOGY, y in YEAR} >= 0; var NumberOfNewTechnologyUnits{r in REGION, t in TECHNOLOGY, y in YEAR} >= 0,integer; var ProductionByTechnology{r in REGION, l in TIMESLICE, t in TECHNOLOGY, f in COMMODITY, y in YEAR} >= 0; @@ -220,12 +205,6 @@ s.t. TAC1_TotalModelHorizonTechnologyActivity{r in REGION, t in TECHNOLOGY}: sum # InputToCapacityRatios # s.t. EBb4_EnergyBalanceEachYear4_ICR{r in REGION, f in COMMODITY, y in YEAR}: sum{(m,t) in MODExTECHNOLOGYperFUELout[f], l in TIMESLICE} RateOfActivity[r,l,t,m,y]*OutputActivityRatio[r,t,f,m,y]*YearSplit[l,y] >= sum{(m,t) in MODExTECHNOLOGYperFUELin[f], l in TIMESLICE} RateOfActivity[r,l,t,m,y]*InputActivityRatio[r,t,f,m,y]*YearSplit[l,y] + sum{l in TIMESLICE, rr in REGION} Trade[r,rr,l,f,y]*TradeRoute[r,rr,f,y] + AccumulatedAnnualDemand[r,f,y] + sum{t in TECHNOLOGY} InputToNewCapacity [r, t, f, y] + sum{t in TECHNOLOGY} InputToTotalCapacity [r, t, f, y]; - - - - - - # Energy balance – include only pairs that exist (others implicitly 0) s.t. EBb4_EnergyBalanceEachYear4_ICR {r in REGION, f in COMMODITY, y in YEAR}: @@ -236,17 +215,10 @@ s.t. EBb4_EnergyBalanceEachYear4_ICR RateOfActivity[r,l,t,m,y] * InputActivityRatio[r,t,f,m,y] * YearSplit[l,y] + sum { l in TIMESLICE, rr in REGION } Trade[r,rr,l,f,y] * TradeRoute[r,rr,f,y] + AccumulatedAnnualDemand[r,f,y] - + sum { t in TECHNOLOGY : (t,f) in INPUT_TF_PAIRS_NEW } InputToNewCapacity[r,t,f,y] + sum { t in TECHNOLOGY : (t,f) in INPUT_TF_PAIRS_TOTAL } InputToTotalCapacity[r,t,f,y]; - - - - # s.t. INC1_InputToNewCapacity{r in REGION, t in TECHNOLOGY, f in COMMODITY, y in YEAR: InputToNewCapacityRatio [r, t, f, y] <> 0}: InputToNewCapacityRatio [r, t, f, y] * NewCapacity [r, t, y] = InputToNewCapacity [r, t, f, y]; - - s.t. INC1_InputToNewCapacity {r in REGION, y in YEAR, #f in INPUTxFUEL, @@ -256,11 +228,8 @@ s.t. INC1_InputToNewCapacity InputToNewCapacityRatio[r,t,f,y] * NewCapacity[r,t,y] = InputToNewCapacity[r,t,f,y]; - - #s.t. ITC1_InputToTotalCapacity{r in REGION, t in TECHNOLOGY, f in COMMODITY, y in YEAR: InputToTotalCapacityRatio [r, t, f, y] <> 0}: InputToTotalCapacityRatio [r, t, f, y] * TotalCapacityAnnual [r, t, y] = InputToTotalCapacity [r, t, f, y]; - s.t. ITC1_InputToTotalCapacity {r in REGION, y in YEAR, #f in INPUTxFUEL, @@ -270,8 +239,6 @@ s.t. ITC1_InputToTotalCapacity InputToTotalCapacityRatio[r,t,f,y] * TotalCapacityAnnual[r,t,y] = InputToTotalCapacity[r,t,f,y]; - - # Long_Code_Equations s.t. AAC1_TotalAnnualTechnologyActivity{r in REGION, t in TECHNOLOGY, y in YEAR}: sum{l in TIMESLICE, m in MODEperTECHNOLOGY[t]} RateOfActivity[r,l,t,m,y]*YearSplit[l,y] = TotalTechnologyAnnualActivity[r,t,y]; s.t. CAa3_TotalActivityOfEachTechnology{r in REGION, t in TECHNOLOGY, l in TIMESLICE, y in YEAR}: sum{m in MODEperTECHNOLOGY[t]} RateOfActivity[r,l,t,m,y] = RateOfTotalActivity[r,t,l,y]; @@ -318,11 +285,8 @@ s.t. E10_InterYearActivityEmissionChange{r in REGION, e in EMISSION, (m, t) in M # TN Changed 2024 01 s.t. E11_InterYearActivityEmissionChange{r in REGION, e in EMISSION, (m, t) in MODExTECHNOLOGYperEMISSIONChange[e], y in YEAR, yy in YEAR: y == min{yyy in YEAR} min(yyy)}: 0 = EmissionByActivityChange[r, t, e, m, y]; - - ######### Storage Equations ############# # - s.t. S14_RateOfNetStorageActivity{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: (sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t], l in TIMESLICE:TechnologyToStorage[r,t,s,m]>0} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh]) - (sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t], l in TIMESLICE:TechnologyFromStorage[r,t,s,m]>0} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh]) = RateOfNetStorageActivity[r,s,ls,ld,lh,y]; s.t. S3_NetChargeWithinYear{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: sum{l in TIMESLICE:Conversionls[l,ls]>0&&Conversionld[l,ld]>0&&Conversionlh[l,lh]>0} (sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t]:TechnologyToStorage[r,t,s,m]>0} (RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh]) - (sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t]:TechnologyFromStorage[r,t,s,m]>0} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh])) * YearSplit[l,y] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh] = NetChargeWithinYear[r,s,ls,ld,lh,y]; s.t. S4_NetChargeWithinDay{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: ((sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t], l in TIMESLICE:TechnologyToStorage[r,t,s,m]>0} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh]) - (sum{t in TECHNOLOGY, m in MODEperTECHNOLOGY[t], l in TIMESLICE:TechnologyFromStorage[r,t,s,m]>0} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m] * Conversionls[l,ls] * Conversionld[l,ld] * Conversionlh[l,lh])) * DaySplit[lh,y] = NetChargeWithinDay[r,s,ls,ld,lh,y]; @@ -354,7 +318,6 @@ s.t. S39_StorageIntrayear{r in REGION, s in STORAGEINTRAYEAR, y in YEAR}: sum{ls #v.k. SC4_LowerLimit_BeginningOfDailyTimeBracketOfFirstInstanceOfDayTypeInLastWeekConstraint = SC4_LLBDFILW #v.k. SC4_UpperLimit_BeginningOfDailyTimeBracketOfFirstInstanceOfDayTypeInLastWeekConstraint = SC4_ULBDFILW - s.t. SC1_LLBDFIFW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: 0 <= (StorageLevelDayTypeStart[r,s,ls,ld,y]+sum{lhlh in DAILYTIMEBRACKET:lh-lhlh>0} (((sum{(m,t) in MODExTECHNOLOGYperSTORAGEto[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m]) - (sum{(m,t) in MODExTECHNOLOGYperSTORAGEfrom[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m])) * DaySplit[lhlh,y]))-MinStorageCharge[r,s,y]*(sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y]); s.t. SC1_ULBDFIFW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: @@ -365,19 +328,14 @@ s.t. SC3_LLEDLILW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in s.t. SC3_ULEDLILW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: (StorageLevelDayTypeFinish[r,s,ls,ld,y] - sum{lhlh in DAILYTIMEBRACKET:lh-lhlh<0} (((sum{(m,t) in MODExTECHNOLOGYperSTORAGEto[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m]) - (sum{(m,t) in MODExTECHNOLOGYperSTORAGEfrom[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m])) * DaySplit[lhlh,y]))-(sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y]) <= 0; s.t. SC4_LLBDFILW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: 0 <= if ld > min{ldld in DAYTYPE} min(ldld) then (StorageLevelDayTypeFinish[r,s,ls,ld-1,y]+sum{lhlh in DAILYTIMEBRACKET:lh-lhlh>0} (((sum{(m,t) in MODExTECHNOLOGYperSTORAGEto[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m]) - (sum{(m,t) in MODExTECHNOLOGYperSTORAGEfrom[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m])) * DaySplit[lhlh,y]))-MinStorageCharge[r,s,y]*(sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y]); s.t. SC4_ULBDFILW{r in REGION, s in STORAGE, ls in SEASON, ld in DAYTYPE, lh in DAILYTIMEBRACKET, y in YEAR}: if ld > min{ldld in DAYTYPE} min(ldld) then (StorageLevelDayTypeFinish[r,s,ls,ld-1,y]+sum{lhlh in DAILYTIMEBRACKET:lh-lhlh>0} (((sum{(m,t) in MODExTECHNOLOGYperSTORAGEto[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyToStorage[r,t,s,m]) - (sum{(m,t) in MODExTECHNOLOGYperSTORAGEfrom[s], l in TIMESLICEofSDB[ls,ld,lhlh]} RateOfActivity[r,l,t,m,y] * TechnologyFromStorage[r,t,s,m])) * DaySplit[lhlh,y]))-(sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y]) <= 0; - # ######### Storage Investments ############# # s.t. SI6_SalvageValueStorageAtEndOfPeriod1{r in REGION, s in STORAGE, y in YEAR: (y+OperationalLifeStorage[r,s]-1) <= (max{yy in YEAR} max(yy))}: 0 = SalvageValueStorage[r,s,y]; - - - s.t. SI7_SalvageValueStorageAtEndOfPeriod2{r in REGION, s in STORAGE, y in YEAR: (y+OperationalLifeStorage[r,s]-1) > (max{yy in YEAR} max(yy))}: CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y]*(1-(max{yy in YEAR} max(yy) - y+1)/OperationalLifeStorage[r,s]) = SalvageValueStorage[r,s,y]; # 20240625 vk ta DepreciationMethod default 2 #s.t. SI7_SalvageValueStorageAtEndOfPeriod2{r in REGION, s in STORAGE, y in YEAR: (DepreciationMethod[r]=1 && (y+OperationalLifeStorage[r,s]-1) > (max{yy in YEAR} max(yy)) && DiscountRate[r]=0) || (DepreciationMethod[r]=2 && (y+OperationalLifeStorage[r,s]-1) > (max{yy in YEAR} max(yy)))}: CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y]*(1-(max{yy in YEAR} max(yy) - y+1)/OperationalLifeStorage[r,s]) = SalvageValueStorage[r,s,y]; #s.t. SI8_SalvageValueStorageAtEndOfPeriod3{r in REGION, s in STORAGE, y in YEAR: DepreciationMethod[r]=1 && (y+OperationalLifeStorage[r,s]-1) > (max{yy in YEAR} max(yy)) && DiscountRate[r]>0}: CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y]*(1-(((1+DiscountRate[r])^(max{yy in YEAR} max(yy) - y+1)-1)/((1+DiscountRate[r])^OperationalLifeStorage[r,s]-1))) = SalvageValueStorage[r,s,y]; - #s.t. SI1_StorageUpperLimit{r in REGION, s in STORAGE, y in YEAR}: sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y] = StorageUpperLimit[r,s,y]; s.t. SI2_StorageLowerLimit{r in REGION, s in STORAGE, y in YEAR}: MinStorageCharge[r,s,y]*(sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]+ResidualStorageCapacity[r,s,y]) = StorageLowerLimit[r,s,y]; s.t. SI3_TotalNewStorage{r in REGION, s in STORAGE, y in YEAR}: sum{yy in YEAR: y-yy < OperationalLifeStorage[r,s] && y-yy>=0} NewStorageCapacity[r,s,yy]=AccumulatedNewStorageCapacity[r,s,y]; @@ -386,10 +344,6 @@ s.t. SI4_UndiscountedCapitalInvestmentStorage{r in REGION, s in STORAGE, y in YE s.t. SI9_SalvageValueStorageDiscountedToStartYear{r in REGION, s in STORAGE, y in YEAR}: SalvageValueStorage[r,s,y]/((1+DiscountRate[r])^(max{yy in YEAR} max(yy)-min{yy in YEAR} min(yy)+1)) = DiscountedSalvageValueStorage[r,s,y]; #s.t. SI10_TotalDiscountedCostByStorage{r in REGION, s in STORAGE, y in YEAR}: (CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y]/((1+DiscountRate[r])^(y-min{yy in YEAR} min(yy)))-CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y]/((1+DiscountRate[r])^(y-min{yy in YEAR} min(yy)))) = TotalDiscountedStorageCost[r,s,y]; # - - - - # User-defined constraints s.t. UDC1_UserDefinedConstraintInequality{r in REGION, u in UDC, y in YEAR: UDCTag[r,u] = 0}: sum{t in TECHNOLOGY}UDCMultiplierTotalCapacity[r,t,u,y]*TotalCapacityAnnual[r,t,y] + @@ -406,4 +360,4 @@ solve; # ##################### # -end; +end; \ No newline at end of file From a72d82c77dc3e8dde85d1a63ed33509a24db5ae0 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 31 Mar 2026 12:36:02 -0400 Subject: [PATCH 03/10] Sync v5.5 UI and results metadata --- .gitignore | 1 + WebAPP/App/Controller/DataFile.js | 119 +- WebAPP/App/Controller/ModelFile.js | 155 +++ WebAPP/App/Model/Config.Model.js | 5 +- WebAPP/App/Model/DataFile.Model.js | 3 +- WebAPP/App/Model/ModelFile.Model.js | 18 + WebAPP/App/View/DataFile.html | 77 +- WebAPP/App/View/ModelFile.html | 64 + WebAPP/App/View/Navbar.html | 4 +- WebAPP/App/View/Versions.html | 10 + WebAPP/AppResults/Controller/Pivot.js | 30 +- WebAPP/Classes/Const.Class.js | 25 +- WebAPP/Classes/DataModelResult.Class.js | 1467 ++++++----------------- WebAPP/Classes/Html.Class.js | 13 +- WebAPP/Classes/Osemosys.Class.js | 133 +- WebAPP/DataStorage/Parameters.json | 861 +------------ WebAPP/DataStorage/Variables.json | 445 +------ WebAPP/Routes/Routes.Class.js | 10 + WebAPP/index.html | 30 +- 19 files changed, 937 insertions(+), 2533 deletions(-) create mode 100644 WebAPP/App/Controller/ModelFile.js create mode 100644 WebAPP/App/Model/ModelFile.Model.js create mode 100644 WebAPP/App/View/ModelFile.html diff --git a/.gitignore b/.gitignore index 9bf55330a..4a7057074 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ assets/demo-data/CLEWs.Demo.zip Procfile .env .runtime/ +WebAPP/app.log dist/ build/ venv/ diff --git a/WebAPP/App/Controller/DataFile.js b/WebAPP/App/Controller/DataFile.js index c9c11d35c..427340258 100644 --- a/WebAPP/App/Controller/DataFile.js +++ b/WebAPP/App/Controller/DataFile.js @@ -19,11 +19,13 @@ export default class DataFile { promise.push(genData); const resData = Osemosys.getResultData(casename, 'resData.json'); promise.push(resData); + const modelFile = Osemosys.readModelFile(); + promise.push(modelFile); return Promise.all(promise); }) .then(data => { - let [casename, genData, resData] = data; - let model = new Model(casename, genData, resData, "DataFile"); + let [casename, genData, resData, modelFile] = data; + let model = new Model(casename, genData, resData, modelFile, "DataFile"); if (casename) { this.initPage(model); } else { @@ -43,6 +45,7 @@ export default class DataFile { //Navbar.initPage(model.casename, model.pageId); Html.title(model.casename, model.title, ""); Html.renderCases(model.cases); + Html.renderModelFile(model.modelFile); //potrebno je napraviti render svih scenarija (mozda je dodan novi scenario u medjuvremenu), on mora biti dodan u listu scenarija po case run samo sto nece biti aktivan // Html.renderScOrder(model.scBycs[model.cs]); //console.log('model.scenarios ',model.scenarios) @@ -69,11 +72,13 @@ export default class DataFile { promise.push(genData); const resData = Osemosys.getResultData(casename, 'resData.json'); promise.push(resData); + const modelFile = Osemosys.readModelFile(); + promise.push(modelFile); return Promise.all(promise); }) .then(data => { - let [casename, genData, resData] = data; - let model = new Model(casename, genData, resData, "DataFile"); + let [casename, genData, resData, modelFile] = data; + let model = new Model(casename, genData, resData, modelFile, "DataFile"); $(".DataFile").hide(); $("#osy-DataFile").empty(); $("#osy-runOutput").empty(); @@ -95,6 +100,9 @@ export default class DataFile { } static initEvents(model) { + const renderLogText = (selector, primary, secondary = '') => { + Html.renderPreformatted(selector, `${primary || ''}${secondary || ''}`); + }; $("#casePicker").off('click'); $("#casePicker").on('click', '.selectCS', function (e) { @@ -106,6 +114,21 @@ export default class DataFile { Message.smallBoxInfo("Case selection", casename + " is selected!", 3000); }); + $("#osy-logFile").off('click'); + $("#osy-logFile").on('click', function (event) { + Message.loaderStart('Generating log file!') + Osemosys.readLogFile() + .then(response => { + Message.loaderEnd(); + Html.renderPreformatted('#osy-logFiletxt', response, 'No runtime log available yet.\n'); + $("#osy-LogFileModal").modal("show"); + }) + .catch(error => { + Message.loaderEnd(); + Message.bigBoxDanger('Error message', error, null); + }) + }); + $("#osy-btnScOrder").off('click'); $("#osy-btnScOrder").on('click', function (event) { // console.log('model, ', model) @@ -143,10 +166,48 @@ export default class DataFile { }); + function setAllCheckboxes(state) { + // Targetiramo checkboxove SAMO u #osy-scOrder (SC_0 ostaje netaknut jer je disabled i u #osy-sc0) + $('#osy-scOrder input[type="checkbox"]:not(:disabled)') + .prop('checked', state) + .trigger('change'); // ako imaš logiku na change eventu + } + + $("#toggle-all").off('click'); + $('#toggle-all').on('click', function (e) { + + e.preventDefault(); + e.stopPropagation(); + + const $btn = $(this); + // Skup svih "normalnih" checkboxova (osim disabled i osim SC_0) + const $boxes = $('#osy-scOrder input[type="checkbox"]:not(:disabled)'); + // Provjera: ima li ijedan nečekiran? + const anyUnchecked = $boxes.is(':not(:checked)'); + + // Ako ima nečekiranih → čekiraj sve, inače → poništi sve + setAllCheckboxes(anyUnchecked); + + // Ažuriraj label dugmeta da bude intuitivna + //$btn.text(anyUnchecked ? 'Uncheck all' : 'Check all'); + const $icon = $btn.find('i.fa'); + if (anyUnchecked) { + // Sada su SVI čekirani -> prikaži "poništi" ikonu + $icon.removeClass('fa-square-o').addClass('fa-check-square-o'); + $btn.find('span').text('Uncheck all'); // ili ukloni ovu liniju ako želiš samo ikonu + } else { + // Sada su SVI odčekirani -> prikaži "odaberi" ikonu + $icon.removeClass('fa-check-square-o').addClass('fa-square-o'); + $btn.find('span').text('Check all'); // ili ukloni ovu liniju ako želiš samo ikonu + } + + }); + + $("#btnSaveOrder").off('click'); $("#btnSaveOrder").on('click', function (event) { Message.clearMessages(); - Message.bigBoxSuccess('Sceanario order', 'You have updated scenarios order data!', 3000); + Message.bigBoxWarning('Scenario Order Updated', 'You have updated the order of scenarios. To apply your changes, please click Update Case.', 4000); $('#osy-order').modal('toggle'); //nema potrebe da spasavmo scenario order jer se on ada nalazi u resData @@ -410,11 +471,27 @@ export default class DataFile { }) }); - $("#osy-run").off('click'); $("#osy-run").on('click', function (event) { Pace.restart(); Message.loaderStart('Optimization in process!') + + + // const logBox = document.getElementById("logBox"); + // const eventSource = new EventSource("http://127.0.0.1:5002/stream_logs"); + + // eventSource.onmessage = function (e) { + // console.log('e.data ', e.data) + // logBox.innerHTML += e.data + "
"; + // logBox.scrollTop = logBox.scrollHeight; // auto-scroll + // }; + + + + + + ////////////////////////////////////////////////////////////////////////////////////// + //promijenjeno da radimo samo sa cBCsolverom //let solver = $('input[name="solver"]:checked').val(); let solver = 'cbc'; @@ -430,9 +507,9 @@ export default class DataFile { $(".batchOutput").hide(); $("#osy-batchOutput").empty(); $("#osy-runOutput").empty(); - $("#osy-runOutput").html('
' + response.cbc_message, response.cbc_stdmsg+ '
'); + renderLogText('#osy-runOutput', response.cbc_message, response.cbc_stdmsg); $("#osy-lpOutput").empty(); - $("#osy-lpOutput").html('
' + response.glpk_message, response.glpk_stdmsg+ '
'); + renderLogText('#osy-lpOutput', response.glpk_message, response.glpk_stdmsg); Base.getResultCSV(model.casename, model.cs) .then(csvs => { Html.renderCSV(csvs, model.cs) @@ -450,9 +527,9 @@ export default class DataFile { $(".batchOutput").hide(); $("#osy-batchOutput").empty(); $("#osy-runOutput").empty(); - $("#osy-runOutput").html('
' + response.cbc_message, response.cbc_stdmsg+ '
'); + renderLogText('#osy-runOutput', response.cbc_message, response.cbc_stdmsg); $("#osy-lpOutput").empty(); - $("#osy-lpOutput").html('
' + response.glpk_message, response.glpk_stdmsg+ '
'); + renderLogText('#osy-lpOutput', response.glpk_message, response.glpk_stdmsg); Base.getResultCSV(model.casename, model.cs) .then(csvs => { Html.renderCSV(csvs, model.cs) @@ -469,9 +546,9 @@ export default class DataFile { $(".batchOutput").hide(); $("#osy-batchOutput").empty(); $("#osy-runOutput").empty(); - $("#osy-runOutput").html('
' + response.cbc_message, response.cbc_stdmsg+ '
'); + renderLogText('#osy-runOutput', response.cbc_message, response.cbc_stdmsg); $("#osy-lpOutput").empty(); - $("#osy-lpOutput").html('
' + response.glpk_message, response.glpk_stdmsg+ '
'); + renderLogText('#osy-lpOutput', response.glpk_message, response.glpk_stdmsg); Message.clearMessages(); // let errormsg = ''; // if (response.glpk_message != "" || response.glpk_stdmsg != "") { @@ -577,11 +654,11 @@ export default class DataFile { if (response.status_code == "success") { $('#osy-validation').modal('toggle'); $("#valCasrunname").text(caserunanme) - $("#valOutput").html('
' + response.msg+ '
') + Html.renderPreformatted('#valOutput', response.msg); } if (response.status_code == "warning") { $('#osy-validation').modal('toggle'); - $("#valOutput").html('
' + response.msg+ '
'); + Html.renderPreformatted('#valOutput', response.msg); } if (response.status_code == "error") { //Message.warningOsy(response.msg); @@ -734,6 +811,7 @@ export default class DataFile { $(".batchOutput").hide(); $("#osy-batchRun").hide(); + $("#osy-runCaseDiv").hide(); $('.checkbox').prop('checked', false); } //remove case from view json files @@ -764,7 +842,6 @@ export default class DataFile { e.stopImmediatePropagation(); }); - //$(".Cases").off('click'); $('#osy-Cases').on('click', '.checkbox', function(e){ // var val = $(this).val(); @@ -823,7 +900,7 @@ export default class DataFile { $(".batchOutput").show(); $("#osy-batchOutput").empty(); - $("#osy-batchOutput").html('
' + response.log+ '
'); + Html.renderPreformatted('#osy-batchOutput', response.log); }) .catch(error => { @@ -841,14 +918,15 @@ export default class DataFile { Osemosys.cleanUp(model.casename) .then(response => { Message.loaderEnd(); - //Message.smallBoxInfo('Generate message', response.message, 3000); - // console.log('response ', response.log); - // Message.bigBoxDefault("BATCH RUN!", response.log) $(".runOutput").hide(); + $(".DataFile").hide(); $(".lpOutput").hide(); $(".Results").hide(); + $(".batchOutput").hide(); + $("#osy-runCaseDiv").hide(); $("#osy-runOutput").empty(); $("#osy-lpOutput").empty(); + $('.Cases').tab('show'); console.log('response clean up ', response) @@ -875,6 +953,3 @@ export default class DataFile { } - - - diff --git a/WebAPP/App/Controller/ModelFile.js b/WebAPP/App/Controller/ModelFile.js new file mode 100644 index 000000000..33b2f33a3 --- /dev/null +++ b/WebAPP/App/Controller/ModelFile.js @@ -0,0 +1,155 @@ +import { Osemosys } from "../../Classes/Osemosys.Class.js"; + +export default class ModelFile { + + static onLoad() { + Osemosys.readModelFile() + .then(txt => { + if (!txt) { + document.getElementById("equations").innerHTML = + "
Unable to load model file.
"; + return; + } + const eqs = ModelFile.extractEquations(txt); + if (!eqs.length) { + document.getElementById("equations").innerHTML = + "
No equations could be parsed from the model file.
"; + return; + } + ModelFile.renderEquations(eqs); + }); + } + + // ----------------------------------------- + // Extract: objective + constraints + // ----------------------------------------- + static extractEquations(txt) { + const src = txt.replace(/\r/g, ""); + const eqs = []; + + // Objective + const objRe = /(minimize|maximize)\s+([A-Za-z_]\w*)\s*:\s*([\s\S]*?)\s*;/i; + const mObj = src.match(objRe); + if (mObj) { + eqs.push({ + section: "Objective", + name: mObj[2], + latex: ModelFile.gmplToLatex(mObj[3].trim()) + }); + } + + // Constraints + const consRe = /s\.t\.\s*([A-Za-z_]\w*)\s*(\{[^}]*\})?\s*:\s*([\s\S]*?)(?=;)/gi; + let m; + while ((m = consRe.exec(src)) !== null) { + eqs.push({ + section: ModelFile.detectSection(m[1]), + name: m[1], + latex: ModelFile.gmplToLatex(m[3].trim()) + }); + } + + return eqs; + } + + // ----------------------------------------- + // Section assignment + // ----------------------------------------- + static detectSection(name) { + const n = name.toUpperCase(); + + if (n.startsWith("EB")) return "Energy Balance"; + if (n.startsWith("E")) return "Emissions"; + if (n.startsWith("A") || n.startsWith("TAC") || n.startsWith("AAC")) return "Activity"; + if (n.startsWith("NC") || n.startsWith("TC") || n.startsWith("C")) return "Capacity"; + if (n.startsWith("S")) return "Storage"; + if (n.startsWith("UDC")) return "User-defined Constraints"; + return "Other"; + } + + // ----------------------------------------- + // GMPL --> LaTeX + // ----------------------------------------- + static gmplToLatex(expr) { + let s = expr; + + s = s.replace(/&&/g, " \\land "); + s = s.replace(/<=/g, "\\le ") + .replace(/>=/g, "\\ge ") + .replace(/\*/g, "\\cdot "); + + // sum{} + s = s.replace(/sum\s*\{([^}]*)\}/gi, (_, inside) => { + const cleaned = inside + .split(',') + .map(p => p.trim().replace(/\s+in\s+/i," \\in ")) + .join(', '); + return `\\sum_{${cleaned}}`; + }); + + // X[a,b] + s = s.replace(/([A-Za-z_]\w*)\s*\[([^\]]+)\]/g, "\\mathrm{$1}_{ $2 }"); + + // ukloni nove linije + s = s.replace(/\n+/g, " "); + + return s; + } + + // ----------------------------------------- + // FINAL RENDER + NUMERACIJA + LINIJE + // ----------------------------------------- + static renderEquations(eqs) { + const out = document.getElementById("equations"); + + let html = ""; + let lastSection = ""; + let counter = 1; + + eqs.forEach((eq, i) => { + const isNewSection = eq.section !== lastSection; + + // Deblja linija između sekcija (ali ne prije prve) + if (isNewSection && i !== 0) { + html += `
`; + } + + // Naslov sekcije ako je nova + if (isNewSection) { + html += ` +

+ ${eq.section} +

+ `; + lastSection = eq.section; + } + + // Jednadžba + ručna numeracija + tanka linija nakon svake + html += ` +
+
${eq.name}
+ +
+ $$ + + \\begin{align} + ${eq.latex} + \\end{align} + \\tag{${counter}} + + $$ +
+ +
+
+ `; + + counter++; + }); + + out.innerHTML = html; + if (window.MathJax?.typesetPromise) { + window.MathJax.typesetPromise([out]).catch(() => {}); + } + } +} diff --git a/WebAPP/App/Model/Config.Model.js b/WebAPP/App/Model/Config.Model.js index 627a2c06d..e4dc0f1de 100644 --- a/WebAPP/App/Model/Config.Model.js +++ b/WebAPP/App/Model/Config.Model.js @@ -20,9 +20,6 @@ export class Model { let varById = DataModelResult.getVarById(VARIABLES); let varNames = DataModelResult.AllVarName(VARIABLES); - console.log('unitsDef ', unitsDef) - console.log('PARAMETERS[group] ', PARAMETERS['RYS']) - let gridParamData = [] $.each(PARAMORDER, function (id, group) { $.each(PARAMETERS[group], function (id, obj) { @@ -142,4 +139,4 @@ export class Model { this.varNames = varNames; //this.unitIdByVal = unitIdByVal; } -} \ No newline at end of file +} diff --git a/WebAPP/App/Model/DataFile.Model.js b/WebAPP/App/Model/DataFile.Model.js index ad51ada27..d3d220b1e 100644 --- a/WebAPP/App/Model/DataFile.Model.js +++ b/WebAPP/App/Model/DataFile.Model.js @@ -1,6 +1,6 @@ export class Model { - constructor (casename, genData, resData, pageId) { + constructor (casename, genData, resData, modelFile, pageId) { if(casename){ let cases = resData['osy-cases']; @@ -54,6 +54,7 @@ export class Model { this.casename = casename; this.cs = cs; + this.modelFile = modelFile; this.scBycs = scBycs; this.title = "Run model"; this.scenarios = scenarios; diff --git a/WebAPP/App/Model/ModelFile.Model.js b/WebAPP/App/Model/ModelFile.Model.js new file mode 100644 index 000000000..df000813e --- /dev/null +++ b/WebAPP/App/Model/ModelFile.Model.js @@ -0,0 +1,18 @@ + +export class Model { + constructor (casename, modelFile, pageId) { + if(casename){ + + this.casename = casename || null; + this.title = "Model file"; + this.modelFile = modelFile || ""; + this.pageId = pageId; + + }else{ + this.casename = null; + this.title = "Model file"; + this.scenarios = null; + this.pageId = pageId; + } + } +} diff --git a/WebAPP/App/View/DataFile.html b/WebAPP/App/View/DataFile.html index fe3a92493..fdbe2da7a 100644 --- a/WebAPP/App/View/DataFile.html +++ b/WebAPP/App/View/DataFile.html @@ -175,6 +175,7 @@

Case runs

Cases +
  • MUIO ver.5.5.0, 20260331

    +
      +
    1. Func Added DataFile diagnostics for viewing the current model file and runtime log from the UI.
    2. +
    3. Func Added a standalone ModelFile page with MathJax-based equation rendering and readable fallback when MathJax is unavailable.
    4. +
    5. Func Scenario-order dialog now includes toggle-all selection without changing the locked base scenario behavior.
    6. +
    7. Code Cleanup and case-run bookkeeping were aligned with upstream v5.5 so saved view definitions are preserved and result metadata stays consistent.
    8. +
    9. Code Updated the solver model and synchronized frontend result metadata with upstream v5.5 definitions.
    10. +
    11. Tech Runtime logging now uses a backend-managed log path outside the static web tree, with read-only UI access through backend routes.
    12. +
    +

    MUIO ver.5.4.0, 20260131

    1. Code Default salvage value calculation has been changed from straight-line depreciation to sinking fund depreciation.
    2. diff --git a/WebAPP/AppResults/Controller/Pivot.js b/WebAPP/AppResults/Controller/Pivot.js index e5cd9b3a0..6693558f4 100644 --- a/WebAPP/AppResults/Controller/Pivot.js +++ b/WebAPP/AppResults/Controller/Pivot.js @@ -79,7 +79,6 @@ export default class Pivot { //this.initEvents(model); }) .catch(error => { - console.log('error ', error) setTimeout(function () { if (error.status_code == 'CaseError') { MessageSelect.init(Pivot.refreshPage.bind(Pivot)); @@ -384,7 +383,7 @@ export default class Pivot { static initEvents(model, app) { - console.log('model ', model) + //console.log('model ', model) $("#casePicker").off('click'); $("#casePicker").on('click', '.selectCS', function (e) { e.preventDefault(); @@ -429,7 +428,6 @@ export default class Pivot { let viewId = DefaultObj.getId('VIEW'); app.engine.fields.getField('Unit').isContentHtml = true; - console.log('param ', param, model.group) //ako nije demand jer ne zavisi od T if(param == 'D'){ app.engine.fields.getField('Comm').isContentHtml = true; @@ -554,23 +552,25 @@ export default class Pivot { Message.loaderStart('Preparing pivot data...') model.group = model.VARGROUPS[param]['group']; model.param = param; - Osemosys.getResultData(model.casename, model.group+'.json') .then(DATA => { - console.log('DATA ', DATA) + //console.log('DATA ', DATA, model.group) if (DATA !== null && model.param in DATA && Object.getOwnPropertyNames(DATA[model.param]).length != 0){ let pivotData = DataModelResult.getPivot(DATA, model.genData, model.VARIABLES, model.group, model.param); - console.log('pivotData ', pivotData) model.pivotData = pivotData; app.engine.itemsSource = model.pivotData; - console.log('pivot source ok') + //console.log('pivot source ok') if (model.group == 'R'){ app.engine.columnFields.push('Optimal'); app.engine.rowFields.push('Case'); app.engine.valueFields.push('Value'); } + else if(model.group == 'RY' ){ + app.engine.rowFields.push('Case','Year'); + app.engine.valueFields.push('Value'); + } else if(model.group == 'RYE' ){ app.engine.columnFields.push('Emi'); app.engine.rowFields.push('Case','Year'); @@ -593,14 +593,14 @@ export default class Pivot { } else if(model.group == "RYTC" || model.group == 'RYTCMTs' ){ - console.log(app.engine.columnFields) + //console.log(app.engine.columnFields) app.engine.columnFields.push('Comm'); - console.log('com tech added') + //console.log('com tech added') app.engine.rowFields.push('Case','Year'); app.engine.valueFields.push('Value'); } else{ - console.log('else') + //console.log('else') app.engine.columnFields.push('Tech'); app.engine.rowFields.push('Case', 'Year'); app.engine.valueFields.push('Value'); @@ -615,12 +615,12 @@ export default class Pivot { // } // }); - console.log('app.engine.valueFields ', app.engine.valueFields, model.stgDecimalPoints) + //console.log('app.engine.valueFields ', app.engine.valueFields, model.stgDecimalPoints) // app.engine.valueFields.format = model.stgDecimalPoints; // app.engine.refresh(); app.engine.fields.getField('Value').format = model.stgDecimalPoints; - console.log('fields ok') + //console.log('fields ok') //update defaul model model.DEFAULTVIEW = JSON.parse(JSON.stringify(app.engine.viewDefinition)); //model.DEFAULTVIEW = app.engine.viewDefinition; @@ -638,9 +638,9 @@ export default class Pivot { model.TriggerUpdate = true; Html.title(model.casename, model.VARNAMES[model.group][model.param], model.group+' - Default view'); } - console.log('view ok') + //console.log('view ok') app.engine.fields.getField('Unit').isContentHtml = true; - if(model.group != "RYS" && model.group != "RYCTs" && model.group != "RYC" && model.group != "RYE" && model.group != "RYCn"&& model.group != "R"){ + if(model.group != "RYS" && model.group != "RYCTs" && model.group != "RYC" && model.group != "RYE" && model.group != "RYCn" && model.group != "R" && model.group != "RY"){ app.engine.fields.getField('Tech').isContentHtml = true; app.engine.fields.getField('Tech Desc').isContentHtml = true; } @@ -748,4 +748,4 @@ export default class Pivot { }); } } -} \ No newline at end of file +} diff --git a/WebAPP/Classes/Const.Class.js b/WebAPP/Classes/Const.Class.js index 1a1040a13..7da88859a 100644 --- a/WebAPP/Classes/Const.Class.js +++ b/WebAPP/Classes/Const.Class.js @@ -65,8 +65,13 @@ export const PARAM_EMIS_GROUPS = ['RE', 'RYE', 'RYTE', 'RYTEM'] export const PARAM_STORAGE_GROUPS = ['RS', 'RYS', 'RTSM', 'RYTEM'] export const VAR_TECH_GROUPS = ['RT', 'RYT', 'RYTM', 'RYTC', 'RYTE', 'RYTEM', 'RYTMTs', 'RYTCMTs'] +//export const VAR_TECH_GROUPS = ['RT', 'RYT', 'RYTM', 'RYTMTs'] +//export const VAR_TECH_COM_GROUPS = ['RYTC','RYTCMTs'] + export const VAR_COMM_GROUPS = ['RYTC','RYCTs','RYTCMTs'] -export const VAR_EMIS_GROUPS = ['RYTE', 'RYTEM'] +//export const VAR_COMM_GROUPS = ['RYCTs'] +export const VAR_EMIS_GROUPS = ['RYE', 'RYTE', 'RYTEM'] + export const VAR_STORAGE_GROUPS = ['RYS'] export const GROUPNAMES = { @@ -82,6 +87,7 @@ export const GROUPNAMES = { "RYSeDt": "Region, year, season, daytype", "RYT": "Region, year, technology", "RYS": "Region, year, storage", + "RYCn": "Region, year, constraint", "RYTCn": "Region, year, technology, constraint", "RYTM": "Region, year, technology, mode of operation", "RYC": "Region, year, commodity", @@ -96,13 +102,17 @@ export const GROUPNAMES = { } export const RESULTGROUPNAMES = { - "RT" :"Region, technology", + "R" :"Region", + "RY" :"Region, year", + "RT" :"Region, technology", "RYT" :"Region, year, technology", "RYE" :"Region, year, emission", "RYS" :"Region, year, storage", - "RYTM" :"Region, year, technology, mode of operation", - "RYTC" :"Region, year, technology, commodity", - "RYTE" :"Region, year, technology, emission", + "RYC" :"Region, year, commodity", + "RYCn" :"Region, year, constraint", + "RYTM" :"Region, year, technology, mode of operation", + "RYTC" :"Region, year, technology, commodity", + "RYTE" :"Region, year, technology, emission", "RYTTs" :"Region, year, technology", "RYCTs" :"Region, year, commodity, timeslice", "RYTEM" :"Region, year, technology, emission, mode of operation", @@ -160,9 +170,11 @@ export const PARAMCOLORS = { export const RESULTPARAMORDER = [ "RT" , - "RYT" , + "RYT" , + "RYC" , "RYE" , "RYS" , + "RYCn" , "RYTM" , "RYTC" , "RYTE" , @@ -268,4 +280,3 @@ export const STORAGE_OPERATIONS = ["Yearly","Daily"]; - diff --git a/WebAPP/Classes/DataModelResult.Class.js b/WebAPP/Classes/DataModelResult.Class.js index cd21bdd6d..6fb22e51f 100644 --- a/WebAPP/Classes/DataModelResult.Class.js +++ b/WebAPP/Classes/DataModelResult.Class.js @@ -120,7 +120,7 @@ export class DataModelResult{ return stgData; } - static getUnitData(genData, parameters){ + static getUnitDataOLD(genData, parameters){ let unitData = {}; let techUnits = DataModel.getTechUnits(genData); let commUnits = DataModel.getCommUnits(genData); @@ -131,6 +131,25 @@ export class DataModelResult{ unitData[group] = {}; $.each(array, function (id, obj) { unitData[group][obj.id] = {}; + + //tech comm parameters + if(VAR_TECH_COM_GROUPS.includes(group)){ + $.each(genData['osy-tech'], function (id, tObj) { + unitData[group][obj.id][tObj.Tech] = {}; + unitData[group][obj.id][tObj.Tech]['years'] = 'years'; + unitData[group][obj.id][tObj.Tech]['percent'] = '%'; + unitData[group][obj.id][tObj.Tech]['divide'] = '/'; + unitData[group][obj.id][tObj.Tech]['multiply'] = '*'; + unitData[group][obj.id][tObj.Tech]['hundert'] = '100'; + unitData[group][obj.id][tObj.Tech]['thousand'] = '103'; + unitData[group][obj.id][tObj.Tech]['milion'] = '106'; + unitData[group][obj.id][tObj.Tech]['CapUnitId'] = techUnits[tObj.TechId]['CapUnitId']; + unitData[group][obj.id][tObj.Tech]['ActUnitId'] = techUnits[tObj.TechId]['ActUnitId']; + unitData[group][obj.id][tObj.Tech]['Currency'] = genData['osy-currency']; + }); + } + + //tech parameters if(VAR_TECH_GROUPS.includes(group)){ $.each(genData['osy-tech'], function (id, tObj) { @@ -206,9 +225,117 @@ export class DataModelResult{ return unitData; } + static getUnitData(genData, parameters){ + let unitData = {}; + let techUnits = DataModel.getTechUnits(genData); + let commUnits = DataModel.getCommUnits(genData); + let emiUnits = DataModel.getEmiUnits(genData); + let stgUnits = DataModel.getStgUnits(genData); + + $.each(parameters, function (group, array) { + unitData[group] = {}; + $.each(array, function (id, obj) { + unitData[group][obj.id] = {}; + + //tech comm parameters + + // if(VAR_TECH_COM_GROUPS.includes(group)){ + // $.each(genData['osy-tech'], function (id, tObj) { + // unitData[group][obj.id][tObj.Tech] = {}; + // unitData[group][obj.id][tObj.Tech]['years'] = 'years'; + // unitData[group][obj.id][tObj.Tech]['percent'] = '%'; + // unitData[group][obj.id][tObj.Tech]['divide'] = '/'; + // unitData[group][obj.id][tObj.Tech]['multiply'] = '*'; + // unitData[group][obj.id][tObj.Tech]['hundert'] = '100'; + // unitData[group][obj.id][tObj.Tech]['thousand'] = '103'; + // unitData[group][obj.id][tObj.Tech]['milion'] = '106'; + // unitData[group][obj.id][tObj.Tech]['CapUnitId'] = techUnits[tObj.TechId]['CapUnitId']; + // unitData[group][obj.id][tObj.Tech]['ActUnitId'] = techUnits[tObj.TechId]['ActUnitId']; + // unitData[group][obj.id][tObj.Tech]['Currency'] = genData['osy-currency']; + // }); + // $.each(genData['osy-comm'], function (id, cObj) { + // unitData[group][obj.id][cObj.Comm] = {}; + // unitData[group][obj.id][cObj.Comm]['years'] = 'years'; + // unitData[group][obj.id][cObj.Comm]['percent'] = '%'; + // unitData[group][obj.id][cObj.Comm]['divide'] = '/'; + // unitData[group][obj.id][cObj.Comm]['multiply'] = '*'; + // unitData[group][obj.id][cObj.Comm]['hundert'] = '100'; + // unitData[group][obj.id][cObj.Comm]['thousand'] = '103'; + // unitData[group][obj.id][cObj.Comm]['milion'] = '106'; + // unitData[group][obj.id][cObj.Comm]['CommUnit'] = commUnits[cObj.CommId]; + // unitData[group][obj.id][cObj.Comm]['Currency'] = genData['osy-currency']; + // }); + + // } + + //tech parameters + if(VAR_TECH_GROUPS.includes(group)){ + $.each(genData['osy-tech'], function (id, tObj) { + unitData[group][obj.id][tObj.Tech] = {}; + unitData[group][obj.id][tObj.Tech]['years'] = 'years'; + unitData[group][obj.id][tObj.Tech]['percent'] = '%'; + unitData[group][obj.id][tObj.Tech]['divide'] = '/'; + unitData[group][obj.id][tObj.Tech]['multiply'] = '*'; + unitData[group][obj.id][tObj.Tech]['hundert'] = '100'; + unitData[group][obj.id][tObj.Tech]['thousand'] = '103'; + unitData[group][obj.id][tObj.Tech]['milion'] = '106'; + unitData[group][obj.id][tObj.Tech]['CapUnitId'] = techUnits[tObj.TechId]['CapUnitId']; + unitData[group][obj.id][tObj.Tech]['ActUnitId'] = techUnits[tObj.TechId]['ActUnitId']; + unitData[group][obj.id][tObj.Tech]['Currency'] = genData['osy-currency']; + }); + } + //comm parameters + if(VAR_COMM_GROUPS.includes(group)){ + $.each(genData['osy-comm'], function (id, cObj) { + unitData[group][obj.id][cObj.Comm] = {}; + unitData[group][obj.id][cObj.Comm]['years'] = 'years'; + unitData[group][obj.id][cObj.Comm]['percent'] = '%'; + unitData[group][obj.id][cObj.Comm]['divide'] = '/'; + unitData[group][obj.id][cObj.Comm]['multiply'] = '*'; + unitData[group][obj.id][cObj.Comm]['hundert'] = '100'; + unitData[group][obj.id][cObj.Comm]['thousand'] = '103'; + unitData[group][obj.id][cObj.Comm]['milion'] = '106'; + unitData[group][obj.id][cObj.Comm]['CommUnit'] = commUnits[cObj.CommId]; + unitData[group][obj.id][cObj.Comm]['Currency'] = genData['osy-currency']; + }); + } + //emi parameters + if(VAR_EMIS_GROUPS.includes(group)){ + $.each(genData['osy-emis'], function (id, eObj) { + unitData[group][obj.id][eObj.Emis] = {}; + unitData[group][obj.id][eObj.Emis]['years'] = 'years'; + unitData[group][obj.id][eObj.Emis]['percent'] = '%'; + unitData[group][obj.id][eObj.Emis]['divide'] = '/'; + unitData[group][obj.id][eObj.Emis]['multiply'] = '*'; + unitData[group][obj.id][eObj.Emis]['hundert'] = '100'; + unitData[group][obj.id][eObj.Emis]['thousand'] = '103'; + unitData[group][obj.id][eObj.Emis]['milion'] = '106'; + unitData[group][obj.id][eObj.Emis]['EmiUnit'] = emiUnits[eObj.EmisId]; + unitData[group][obj.id][eObj.Emis]['Currency'] = genData['osy-currency']; + }); + } + if(VAR_STORAGE_GROUPS.includes(group)){ + $.each(genData['osy-stg'], function (id, eObj) { + unitData[group][obj.id][eObj.Stg] = {}; + unitData[group][obj.id][eObj.Stg]['years'] = 'years'; + unitData[group][obj.id][eObj.Stg]['percent'] = '%'; + unitData[group][obj.id][eObj.Stg]['divide'] = '/'; + unitData[group][obj.id][eObj.Stg]['multiply'] = '*'; + unitData[group][obj.id][eObj.Stg]['hundert'] = '100'; + unitData[group][obj.id][eObj.Stg]['thousand'] = '103'; + unitData[group][obj.id][eObj.Stg]['milion'] = '106'; + unitData[group][obj.id][eObj.Stg]['StgUnit'] = stgUnits[eObj.StgId]; + unitData[group][obj.id][eObj.Stg]['Currency'] = genData['osy-currency']; + }); + } + }); + }); + return unitData; + } + //////////////////////////////////////////////////////// P I V O T/////////////////////////////////////////////////////////////////////////////////// - static getPivot(DATA, genData, VARIABLES, group, param){ + static getPivot(DATA, genData, VARIABLES, group, param){ let unitData = this.getUnitData(genData, VARIABLES); let paramById = DataModel.getParamById(VARIABLES); @@ -221,8 +348,6 @@ export class DataModelResult{ let techGroupNames = DataModel.TechGroupName(genData); let years = genData['osy-years'] - //console.log('unitData ',unitData) - let pivotData = []; let dataT = {}; let dataC = {}; @@ -230,7 +355,7 @@ export class DataModelResult{ let dataS = {}; $.each(DATA[param], function (cs, array) { - //console.log('DATA ', DATA[param]) + //console.log('DATA[param] ', DATA[param], param) $.each(array, function (id, obj) { if(obj.ObjectiveValue){ @@ -245,10 +370,6 @@ export class DataModelResult{ $.each(years, function (idY, year) { let chunk = {}; chunk['Case'] = cs; - // if(obj.Tech){ - // chunk['Tech'] = obj.Tech; - // dataT = unitData[group][param][obj.Tech]; - // } //provjera da li u obj ima Comm, Tech, Stg, Emi... Redosljed je bitan za one varijable koje zavise vise od jednog seta, definicja //unita ce da zavisi od toga @@ -265,17 +386,9 @@ export class DataModelResult{ chunk['Value'] = obj[year]; } else{ - // console.log('Value ', obj[year], ' cs ', cs, ' year ', year , ' param ', param) - //console.log('Value ', obj[year]) chunk['Value'] = null; } - - // let rule = paramById[group][param]['unitRule']; - // const data = {...dataC, ...dataE, ...dataS}; - // chunk['Unit'] = jsonLogic.apply(rule, data); - - - if(obj.Comm){ + if(obj.Comm && !obj.Tech){ //uslov dodan vk 18072924 ako smo izbrisali commodity a postoji u resulttima if(obj.Comm in commData){ @@ -284,6 +397,7 @@ export class DataModelResult{ dataC = unitData[group][param][obj.Comm]; let rule = paramById[group][param]['unitRule']; + //console.log('COM ', dataC, rule) chunk['Unit'] = jsonLogic.apply(rule, {...dataC}); } else{ @@ -292,7 +406,6 @@ export class DataModelResult{ chunk['Unit'] = ' n/a'; } } - if(obj.Con){ if(obj.Con in conData){ @@ -307,7 +420,6 @@ export class DataModelResult{ chunk['Unit'] = ' n/a'; } } - if(obj.Stg){ if(obj.Stg in stgData){ @@ -338,9 +450,10 @@ export class DataModelResult{ chunk['Unit'] = ' n/a'; } } - if(obj.Tech){ + if(obj.Tech && !obj.Comm){ //console.log('techData ', obj.Tech, '------', techData[obj.Tech]) //DEMINDLFO - //vk 18972024 ovaj uslov dodat - ako korisnik izbrise tech on ce ostati u view json filovima i doci ce do greske, ovaj tech se mora ignorisati u pivotdata + //vk 18972024 ovaj uslov dodat - + // ako korisnik izbrise tech on ce ostati u view json filovima i doci ce do greske, ovaj tech se mora ignorisati u pivotdata if(obj.Tech in techData){ let rule = paramById[group][param]['unitRule']; if(techData[obj.Tech].TG.length != 0){ @@ -370,6 +483,7 @@ export class DataModelResult{ // console.log('unitData[group][param] ', unitData[group][param]) // console.log('dataT ', dataT) dataT = unitData[group][param][obj.Tech]; + //console.log('TECH ', dataT, rule) } else{ chunk['Tech'] = ' ' + obj.Tech; @@ -381,1122 +495,253 @@ export class DataModelResult{ } } + //ako je group koji ima i Tech i Comm + if(obj.Tech && obj.Comm){ + //console.log('techData ', obj.Tech, '------', techData[obj.Tech]) //DEMINDLFO + //vk 18972024 ovaj uslov dodat - + // ako korisnik izbrise tech on ce ostati u view json filovima i doci ce do greske, ovaj tech se mora ignorisati u pivotdata + if(obj.Tech in techData && obj.Comm in commData){ + let rule = paramById[group][param]['unitRule']; + dataT = unitData[group][param][obj.Tech]; + dataC = unitData[group][param][obj.Comm]; + + //console.log('dataT ', dataT, 'dataC ', dataC, 'rule ', rule, 'group ', group, 'param ', param) + + if(techData[obj.Tech].TG.length != 0){ + $.each(techData[obj.Tech].TG, function (id, tg) { + let tmp = {}; + tmp = JSON.parse(JSON.stringify(chunk)); + tmp['Tech'] = obj.Tech; + tmp['TechGroup'] = techGroupNames[tg]; + tmp['TechDesc'] = techData[obj.Tech]["Desc"]; + tmp['TechGroupDesc'] = techGroupData[tg]["Desc"]; + tmp['Unit'] = jsonLogic.apply(rule, {...dataT, ...dataC}); + + tmp['Comm'] = obj.Comm; + tmp['CommDesc'] = commData[obj.Comm]["Desc"]; + pivotData.push(tmp); + }) + } + else{ + chunk['Tech'] = obj.Tech; + chunk['TechGroup'] = 'No group'; + chunk['TechDesc'] = techData[obj.Tech]["Desc"]; + chunk['TechGroupDesc'] = 'No group'; + chunk['Unit'] = jsonLogic.apply(rule, {...dataT, ...dataC}); + //pivotData.push(chunk); + chunk['Comm'] = obj.Comm; + chunk['CommDesc'] = commData[obj.Comm]["Desc"]; + pivotData.push(chunk); + } + // chunk['Comm'] = obj.Comm; + // chunk['CommDesc'] = commData[obj.Comm]["Desc"]; + // pivotData.push(chunk); + // console.log('rule ', rule) + // console.log('unitData[group][param] ', unitData[group][param]) + // console.log('dataT ', dataT) + //dataT = unitData[group][param][obj.Tech]; + //console.log('TECH ', dataT, rule) + } + else{ + chunk['Tech'] = ' ' + obj.Tech; + chunk['TechGroup'] = 'No group'; + chunk['TechDesc'] = ' ' + obj.Tech + " deleted from model"; + chunk['TechGroupDesc'] = 'No group'; + chunk['Unit'] = ' n/a'; + + chunk['Comm'] = ' ' +obj.Comm; + chunk['CommDesc'] = ' ' +obj.Comm + " deleted from model"; + pivotData.push(chunk); + } + } if(!obj.Tech){ pivotData.push(chunk); - } - + } }); } - - }); }); return pivotData; } - // static getPivot(DATA, genData, VARIABLES, group, param){ - - // let unitData = this.getUnitData(genData, VARIABLES); - // let paramById = DataModel.getParamById(VARIABLES); - // let years = genData['osy-years'] - // let techData = this.getTechData(genData); - // let techGroupNames = DataModel.TechGroupName(genData); - - // let pivotData = []; - // let dataT = {}; - // let dataC = {}; - // let dataE = {}; - - // $.each(DATA[param], function (cs, array) { - // $.each(array, function (id, obj) { - - // let chunk = {}; - // chunk['Case'] = cs; - // if(obj.Tech){ - // if(techData[obj.Tech].TG.length != 0){ - // $.each(techData[obj.Tech].TG, function (id, tg) { - // chunk['Tech'] = obj.Tech; - // chunk['TechGroup'] = techGroupNames[tg]; - // dataT = unitData[group][param][obj.Tech]; - // }) - // }else{ - // chunk['Tech'] = obj.Tech; - // chunk['TechGroup'] = 'No group'; - // dataT = unitData[group][param][obj.Tech]; - // } - - // } - // if(obj.Comm){ - // chunk['Comm'] = obj.Comm; - // dataC = unitData[group][param][obj.Comm]; - // } - // if(obj.Emi){ - // chunk['Emi'] = obj.Emi; - // dataE = unitData[group][param][obj.Emi]; - // } - // if(obj.MoId){ - // chunk['MoId'] = obj.MoId; - // } - // if(obj.Ts){ - // chunk['Ts'] = obj.Ts; - // } - - // let rule = paramById[group][param]['unitRule']; - // const data = {...dataT, ...dataC, ...dataE}; - // chunk['Unit'] = jsonLogic.apply(rule, data); - - // $.each(years, function (idY, year) { - // let tmp = {}; - // tmp = JSON.parse(JSON.stringify(chunk)); - // tmp['Year'] = year; - // tmp['Value'] = obj[year]; - // pivotData.push(tmp); - // }); - - // }); - // }); - // return pivotData; - // } - - ////////////////////////////////////////////////////////JSON data structures -/* - static RT(RTdata){ - let RT = {}; - const cloneData = JSON.parse(JSON.stringify(RTdata)); - $.each(cloneData, function (param, obj1) { - RT[param] = {}; - $.each(obj1, function (sc, array) { - RT[param][sc] = {}; - $.each(array, function (id, obj) { - RT[param][sc] = obj - }); - }); - }); - return RT; - } - - static RTgrid(RTdata){ - // // let scName = this.ScName(genData); - // // let scData = this.getScData(genData); - // // let paramName = this.ParamName(PARAMETERS['RT']); - // // let unitData = this.getUnitData(genData, PARAMETERS); - // // let paramById = this.getParamById(PARAMETERS); - // const cloneData = JSON.parse(JSON.stringify(RTdata)); - // let RTgrid = {} - - // $.each(cloneData, function (param, paramObj) { - // RTgrid[param] = []; - // $.each(paramObj, function (sc, array) { - // $.each(array, function (id, obj) { - // // obj['ParamId'] = param; - // // obj['Param'] = paramName[param]; - // // // obj['ScId'] = sc; - // // // obj['Sc'] = scName[sc]; - // // obj['ScId'] = sc; - // // obj['Sc'] = scData[sc]['Scenario']; - // // obj['ScDesc'] = scData[sc]['Desc']; - // // $.each(genData['osy-tech'], function (id, tech) { - // // let rule = paramById['RT'][param]['unitRule']; - // // let data = unitData['RT'][param][tech.TechId]; - // // obj[tech.TechId+'_UnitId'] = jsonLogic.apply(rule, data); - // // }); - // RTgrid[param].push(obj); - // }); - // }); - // }); - return RTdata; - } - - static RTchart(RTdata){ - //let techName = this.TechName(genData); - // let RTchart = {}; - // //RYTdata[VC] = [{tech, Sc,2000, 2020...}] - // //let data = this.RT(RTdata); - // $.each(RTdata, function (param, obj) { - // let chartData = {}; - - // $.each(obj, function (cs, obj1) { - // chartData[cs] = []; - // $.each(obj1, function (tech, val) { - // let chunk = {}; - // chunk['Tech'] = tech - // // $.each(genData['osy-tech'], function (idT, tech) { - // // let chunk = {}; - // // chunk['TechId'] = tech.TechId; - // // chunk['Tech'] = techName[tech.TechId]; - // // $.each(genData['osy-scenarios'], function (idS, sc) { - // // if (typeof data[param][sc.ScenarioId][tech.TechId] !== "undefined" ){ - // // chunk[sc.ScenarioId] = data[param][sc.ScenarioId][tech.TechId]; - // // } - // // }); - // // RTchart[param].push(chunk); - // }); - // RTchart[param] = chartData; - // }); - // }); - return RTdata; - } - static RYT(RYTdata){ - let RYT = {}; - const cloneData = JSON.parse(JSON.stringify(RYTdata)); - $.each(cloneData, function (param, obj1) { - RYT[param] = {}; - $.each(obj1, function (cs, array) { - RYT[param][cs] = {}; - $.each(array, function (id, obj) { - RYT[param][cs][obj.Tech] = obj - delete obj.Tech; - }); - }); - }); - return RYT; - } + static getPivot_OLD(DATA, genData, VARIABLES, group, param){ - static RYTgrid(RYTdata, genData, PARAMETERS){ + let unitData = this.getUnitData(genData, VARIABLES); + let paramById = DataModel.getParamById(VARIABLES); let techData = this.getTechData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - const cloneData = JSON.parse(JSON.stringify(RYTdata)); - let RYTgrid = {}; - $.each(cloneData, function (param, paramObj) { - RYTgrid[param] = {}; - $.each(paramObj, function (cs, array) { - RYTgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = obj.Tech; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - let rule = paramById['RYT'][param]['unitRule']; - let data = unitData['RYT'][param][obj.Tech]; - - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTgrid[param][cs].push(obj); - }); - }); - }); - return RYTgrid; - } - - static RYTchart(genData, RYTdata){ - let RYTchart = {}; - let data = this.RYT(RYTdata); - $.each(RYTdata, function (param, obj) { - let chartData = {}; - $.each(obj, function (cs, obj1) { - chartData[cs] = []; - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - $.each(genData['osy-tech'], function (idT, tech) { - //$.each( data[param][cs], function (tech, obj2) { - - if (data[param][cs][tech.Tech]){ - chunk[tech.Tech] = data[param][cs][tech.Tech][year]; - }else{ - //chunk[tech.Tech] = null - } - //chunk[tech] = data[param][cs][tech][year] - }); - chartData[cs].push(chunk); - }); - }); - RYTchart[param] = chartData; - }); - return RYTchart; - } - - static RYE(RYEdata){ - let RYE = {}; - const cloneData = JSON.parse(JSON.stringify(RYEdata)); - $.each(cloneData, function (param, obj1) { - RYE[param] = {}; - $.each(obj1, function (cs, array) { - RYE[param][cs] = {}; - $.each(array, function (id, obj) { - RYE[param][cs][obj.Emi] = obj - delete obj.Emi; - // delete obj.Emis; - // delete obj.ScId; - // delete obj.Sc; - }); - }); - }); - return RYE; - } - - static RYEgrid(RYEdata, genData, PARAMETERS){ + let commData = this.getCommData(genData); + let conData = this.getConData(genData); let emiData = this.getEmiData(genData); - // let scData = this.getScData(genData); - // let unitData = this.getUnitData(genData, PARAMETERS); - // let paramById = this.getParamById(PARAMETERS); - const cloneData = JSON.parse(JSON.stringify(RYEdata)); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let RYEgrid = {}; - $.each(cloneData, function (param, paramObj) { - RYEgrid[param] = []; - $.each(paramObj, function (cs, array) { - RYEgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Emi'] = obj.Emi; - obj['EmiDesc'] = emiData[obj.Emi]['Desc']; - // obj['ScId'] = sc; - // obj['Sc'] = scData[sc]['Scenario']; - // obj['ScDesc'] = scData[sc]['Desc']; - let rule = paramById['RYE'][param]['unitRule']; - let data = unitData['RYE'][param][obj.Emi]; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYEgrid[param][cs].push(obj); - }); - }); - }); - return RYEgrid; - } - - static RYEchart(genData, RYEdata){ - let RYEchart = {}; - let data = this.RYE(RYEdata); - $.each(RYEdata, function (param, obj) { - let chartData = {}; - $.each(obj, function (cs, obj1) { - chartData[cs] = []; - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - //$.each(genData['osy-emis'], function (idC, emi) { - $.each( data[param][cs], function (emi, obj2) { - //chunk[emi.Emis] = data[param][cs][emi.Emis][year] - chunk[emi] = data[param][cs][emi][year] - }); - chartData[cs].push(chunk); - }); - }); - RYEchart[param] = chartData; - - }); - return RYEchart; - } - - static RYTM(RYTMdata){ - let RYTM = {}; - const cloneData = JSON.parse(JSON.stringify(RYTMdata)); - $.each(cloneData, function (param, obj1) { - RYTM[param] = {}; - $.each(obj1, function (cs, array) { - RYTM[param][cs] = {}; - $.each(array, function (id, obj) { - if(!RYTM[param][cs][obj.Tech]){ RYTM[param][cs][obj.Tech] = {}; } - RYTM[param][cs][obj.Tech][obj.MoId] = obj; - delete obj.Tech; - delete obj.MoId; - }); - }); - }); - return RYTM; - } + let stgData = this.getStgData(genData); + let techGroupData = this.getTechGroupData(genData); + let techGroupNames = DataModel.TechGroupName(genData); + let years = genData['osy-years'] - static RYTMgrid(RYTMdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - let cloneData = JSON.parse(JSON.stringify(RYTMdata)); - let RYTMgrid = {}; + console.log('unitData ',unitData) - $.each(cloneData, function (param, obj) { - RYTMgrid[param] = []; - $.each(obj, function (cs, array) { - RYTMgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = obj.Tech - obj['TechDesc'] = techData[obj.Tech]['Desc']; - let rule = paramById['RYTM'][param]['unitRule']; - let data = unitData['RYTM'][param][obj.Tech]; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTMgrid[param][cs].push(obj); - }); - }); - }); - return RYTMgrid; - } + let pivotData = []; + let dataT = {}; + let dataC = {}; + let dataE = {}; + let dataS = {}; - static RYTMchart(genData, RYTMdata){ - let RYTM = this.RYTM(RYTMdata); - let mods = DataModel.Mods(genData); - let RYTMchart = {}; + $.each(DATA[param], function (cs, array) { + //console.log('DATA[param] ', DATA[param], param) + $.each(array, function (id, obj) { - $.each(RYTMdata, function (param, obj) { - let chartData = {} - - $.each(obj, function (cs, obj1) { - chartData[cs] = {}; - $.each(mods, function (idS, mo) { - chartData[cs][mo] = []; - $.each(genData['osy-years'], function (idY, year) { + if(obj.ObjectiveValue){ + let chunkOv = {}; + chunkOv['Case'] = cs; + chunkOv['Unit'] = 'n/a'; + chunkOv['Optimal'] = 'Objective Value'; + chunkOv['Value'] = obj.ObjectiveValue; + pivotData.push(chunkOv); + } + else{ + $.each(years, function (idY, year) { let chunk = {}; + chunk['Case'] = cs; + + //provjera da li u obj ima Comm, Tech, Stg, Emi... Redosljed je bitan za one varijable koje zavise vise od jednog seta, definicja + //unita ce da zavisi od toga + + if(obj.MoId){ + chunk['MoId'] = obj.MoId; + } + if(obj.Ts){ + chunk['Ts'] = obj.Ts; + } chunk['Year'] = year; - $.each(genData['osy-tech'], function (idT, tech) { - if(RYTM[param][cs][tech.Tech]){ - chunk[tech.Tech] = RYTM[param][cs][tech.Tech][mo][year]; - }else{ - //chunk[tech.Tech] = null; - } - - // $.each(genData['osy-scenarios'], function (idS, sc) { - // chunk[sc.ScenarioId] = RYTM[param][sc.ScenarioId][tech.TechId][mo][year]; - // }); - }); - chartData[cs][mo].push(chunk); - }); - }); - }); - RYTMchart[param] = chartData; - }); - return RYTMchart; - } - - static RYTC(RYTCdata){ - let RYTC = {}; - const cloneData = JSON.parse(JSON.stringify(RYTCdata)); - $.each(cloneData, function (param, obj1) { - RYTC[param] = {}; - $.each(obj1, function (cs, array) { - RYTC[param][cs] = {}; - $.each(array, function (id, obj) { - if(!RYTC[param][cs][obj.Tech]){ RYTC[param][cs][obj.Tech] = {}; } - RYTC[param][cs][obj.Tech][obj.Comm] = obj; - delete obj.Tech; - delete obj.Comm; - }); - }); - }); - return RYTC; - } - - static RYTCTechs(RYTCdata){ - let RYTCTechs = {}; - let RYTC = this.RYTC(RYTCdata); - $.each(RYTC, function (param, obj1) { - RYTCTechs[param] = {}; - $.each(obj1, function (cs, array) { - RYTCTechs[param][cs] = Object.keys(array); - }); - }); - return RYTCTechs; - } - - static RYTCgrid(RYTCdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let commData = this.getCommData(genData); - // let unitData = this.getUnitData(genData, PARAMETERS); - // let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTCdata)); - let RYTCgrid = {}; - - $.each(cloneData, function (param, obj) { - RYTCgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTCgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - obj['Comm'] = commData[obj.Comm]['Comm']; - obj['CommDesc'] = commData[obj.Comm]['Desc']; - // let rule = paramById['RYTC'][param]['unitRule']; - // let data = unitData['RYTC'][param][obj.Tech]; - // obj['UnitId'] = jsonLogic.apply(rule, data); - RYTCgrid[param][cs].push(obj); - }); - }); - }); - return RYTCgrid; - } - - static RYTCchart(genData, RYTCdata){ - let RYTCchart = {}; - let RYTC = this.RYTC(RYTCdata); - $.each(RYTC, function (param, obj1) { - RYTCchart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTCchart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - - if(!RYTCchart[param][cs][tech]){ RYTCchart[param][cs][tech] = []; } - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - - $.each(RYTC[param][cs][tech], function (comm, obj) { - chunk[comm] = obj[year] - }); - RYTCchart[param][cs][tech].push(chunk); - }); - }); - } - }); - }); - return RYTCchart; - } - - static RYTCchartAll(genData, RYTCdata){ - let RYTCchart = {}; - let RYTC = this.RYTC(RYTCdata); - $.each(RYTC, function (param, obj1) { - RYTCchart[param] = {}; - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTCchart[param][cs] = []; - - $.each(obj2, function (tech, obj3) { - - //if(!RYTCchart[param][cs][tech]){ RYTCchart[param][cs][tech] = []; } - - - - $.each(RYTC[param][cs][tech], function (comm, obj) { - chunk[tech+'_'+comm] = obj[year] - }); - RYTCchart[param][cs].push(chunk); - }); - } - }); - - }); - }); - return RYTCchart; - } - - static RYTE(RYTEdata){ - let RYTE = {}; - const cloneData = JSON.parse(JSON.stringify(RYTEdata)); - $.each(cloneData, function (param, obj1) { - RYTE[param] = {}; - $.each(obj1, function (sc, array) { - RYTE[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYTE[param][sc][obj.Tech]){ RYTE[param][sc][obj.Tech] = {}; } - RYTE[param][sc][obj.Tech][obj.Emi] = obj; - delete obj.Tech; - delete obj.Emis; - }); - }); - }); - return RYTE; - } - - static RYTEgrid(RYTEdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let emiData = this.getEmiData(genData); - - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTEdata)); - let RYTEgrid = {}; - $.each(cloneData, function (param, obj) { - RYTEgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTEgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - obj['Emis'] = emiData[obj.Emi]['Emis']; - obj['EmiDesc'] = emiData[obj.Emi]['Desc']; - let rule = paramById['RYTE'][param]['unitRule']; - let data = unitData['RYTE'][param][obj.Emi]; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTEgrid[param][cs].push(obj); - }); - }); - }); - return RYTEgrid; - } - - static RYTETechs(RYTEdata){ - let RYTETechs = {}; - let RYTE = this.RYTE(RYTEdata); - $.each(RYTE, function (param, obj1) { - RYTETechs[param] = {}; - $.each(obj1, function (cs, array) { - RYTETechs[param][cs] = Object.keys(array); - }); - }); - return RYTETechs; - } - - static RYTEchart(genData, RYTEdata){ - let RYTEchart = {}; - let RYTE = this.RYTE(RYTEdata); - $.each(RYTE, function (param, obj1) { - RYTEchart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTEchart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - - if(!RYTEchart[param][cs][tech]){ RYTEchart[param][cs][tech] = []; } - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - - $.each(obj3, function (comm, obj) { - chunk[comm] = obj[year] - }); - RYTEchart[param][cs][tech].push(chunk); - }); - }); - } - }); - }); - return RYTEchart; - } - - static RYTTs(RYTTsdata){ - let RYTTs = {}; - const cloneData = JSON.parse(JSON.stringify(RYTTsdata)); - $.each(cloneData, function (param, obj1) { - RYTTs[param] = {}; - $.each(obj1, function (sc, array) { - RYTTs[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYTTs[param][sc][obj.Tech]){ RYTTs[param][sc][obj.Tech] = {}; } - RYTTs[param][sc][obj.Tech][obj.Ts] = obj; - delete obj.Tech; - delete obj.Ts; - }); - }); - }); - return RYTTs; - } - - static RYTTsgrid(RYTTsdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let cloneData = JSON.parse(JSON.stringify(RYTTsdata)); - - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let RYTTsgrid = {}; - - $.each(cloneData, function (param, obj) { - RYTTsgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTTsgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - let rule = paramById['RYTTs'][param]['unitRule']; - let data = unitData['RYTTs'][param][obj.Tech]; - obj['UnitId'] = jsonLogic.apply(rule, data); - - RYTTsgrid[param][cs].push(obj); - }); - }); - }); - return RYTTsgrid; - } - - static RYTTschart(genData, RYTTsdata){ - let RYTTs = this.RYTTs(RYTTsdata); - //let timeslices = this.Timeslices(genData); - let RYTTschart = {}; - $.each(RYTTs, function (param, obj1) { - RYTTschart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTTschart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - - if(!RYTTschart[param][cs][tech]){ RYTTschart[param][cs][tech] = []; } - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - $.each(obj3, function (ts, obj) { - chunk[ts] = obj[year] - }); - RYTTschart[param][cs][tech].push(chunk); - }); - }); - } - }); - }); - return RYTTschart; - } - - static RYCTs(RYCTsdata){ - let RYCTs = {}; - const cloneData = JSON.parse(JSON.stringify(RYCTsdata)); - $.each(cloneData, function (param, obj1) { - RYCTs[param] = {}; - $.each(obj1, function (sc, array) { - RYCTs[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYCTs[param][sc][obj.Comm]){ RYCTs[param][sc][obj.Comm] = {}; } - RYCTs[param][sc][obj.Comm][obj.Ts] = obj; - delete obj.Comm; - delete obj.Ts; - }); - }); - }); - return RYCTs; - } - - static RYCTsgrid(RYCTsdata, genData, PARAMETERS){ - let commData = this.getCommData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - let cloneData = JSON.parse(JSON.stringify(RYCTsdata)); - let RYCTsgrid = {}; - - $.each(cloneData, function (param, obj) { - RYCTsgrid[param] = {}; - $.each(obj, function (cs, array) { - RYCTsgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Comm'] = commData[obj.Comm]['Comm']; - obj['CommDesc'] = commData[obj.Comm]['Desc']; - let rule = paramById['RYCTs'][param]['unitRule']; - let data = unitData['RYCTs'][param][obj.Comm]; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYCTsgrid[param][cs].push(obj); - }); - }); - }); - return RYCTsgrid; - } - - static RYCTschart(genData, RYCTsdata){ - let RYCTs = this.RYCTs(RYCTsdata); - let RYCTschart = {}; - $.each(RYCTs, function (param, obj1) { - RYCTschart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYCTschart[param][cs] = {}; - $.each(obj2, function (comm, obj3) { - if(!RYCTschart[param][cs][comm]){ RYCTschart[param][cs][comm] = []; } - $.each(genData['osy-years'], function (idY, year) { - let chunk = {}; - chunk['Year'] = year; - $.each(obj3, function (ts, obj) { - chunk[ts] = obj[year] - }); - RYCTschart[param][cs][comm].push(chunk); - }); - }); - } - }); - }); - return RYCTschart; - } - - static RYTEM(RYTEMdata){ - let RYTEM = {}; - const cloneData = JSON.parse(JSON.stringify(RYTEMdata)); - $.each(cloneData, function (param, obj1) { - RYTEM[param] = {}; - $.each(obj1, function (sc, array) { - RYTEM[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYTEM[param][sc][obj.Tech]){ RYTEM[param][sc][obj.Tech] = {}; } - if(!RYTEM[param][sc][obj.Tech][obj.Emi]){ RYTEM[param][sc][obj.Tech][obj.Emi] = {}; } - RYTEM[param][sc][obj.Tech][obj.Emi][obj.MoId] = obj; - delete obj.Tech; - delete obj.Emi; - delete obj.MoId; - }); - }); - }); - return RYTEM; - } - - static RYTEMTechs(RYTEMdata){ - let RYTEMTechs = {}; - let RYTEM = this.RYTEM(RYTEMdata); - $.each(RYTEM, function (param, obj1) { - RYTEMTechs[param] = {}; - $.each(obj1, function (cs, array) { - RYTEMTechs[param][cs] = Object.keys(array); - }); - }); - return RYTEMTechs; - } - - static RYTEMEmis(RYTEMdata){ - let RYTEMemis = {}; - let RYTEM = this.RYTEM(RYTEMdata); - $.each(RYTEM, function (param, obj1) { - RYTEMemis[param] = {}; - $.each(obj1, function (cs, obj2) { - RYTEMemis[param][cs] = {}; - $.each(obj2, function (tech, array) { - RYTEMemis[param][cs][tech] = Object.keys(array); - }); - }); - }); - return RYTEMemis; - } - - static RYTEMgrid(RYTEMdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let emiData = this.getEmiData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTEMdata)); - let RYTEMgrid = {}; - $.each(cloneData, function (param, obj) { - RYTEMgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTEMgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - obj['Emis'] = emiData[obj.Emi]['Emis']; - obj['EmiDesc'] = emiData[obj.Emi]['Desc']; - - let rule = paramById['RYTEM'][param]['unitRule']; - let data1 = unitData['RYTEM'][param][obj.Emi]; - let data2 = unitData['RYTEM'][param][obj.Tech]; - const data = {...data1, ...data2}; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTEMgrid[param][cs].push(obj); - }); - }); - }); - return RYTEMgrid; - } - - static RYTEMchart(genData, RYTEMdata){ - let RYTEMchart = {}; - let RYTEM = this.RYTEM(RYTEMdata); - $.each(RYTEM, function (param, obj1) { - RYTEMchart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTEMchart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - if(!RYTEMchart[param][cs][tech]){ RYTEMchart[param][cs][tech] = {}; } - $.each(genData['osy-years'], function (idY, year) { - $.each(obj3, function (emi, obj4) { - if(!RYTEMchart[param][cs][tech][emi]){ RYTEMchart[param][cs][tech][emi] = []; } - let chunk = {}; - chunk['Year'] = year; - $.each(obj4, function (mod, obj) { - chunk[mod] = obj[year] - }); - RYTEMchart[param][cs][tech][emi].push(chunk); - }); - }); - }); - } - }); - }); - return RYTEMchart; - } - - static RYTCTs(RYTCTsMdata){ - let RYTCTs = {}; - const cloneData = JSON.parse(JSON.stringify(RYTCTsMdata)); - $.each(cloneData, function (param, obj1) { - RYTCTs[param] = {}; - $.each(obj1, function (sc, array) { - RYTCTs[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYTCTs[param][sc][obj.Tech]){ RYTCTs[param][sc][obj.Tech] = {}; } - if(!RYTCTs[param][sc][obj.Tech][obj.Comm]){ RYTCTs[param][sc][obj.Tech][obj.Comm] = {}; } - RYTCTs[param][sc][obj.Tech][obj.Comm][obj.Ts] = obj; - delete obj.Tech; - delete obj.Comm; - delete obj.Ts; - }); - }); - }); - return RYTCTs; - } - - static RYTCTsTechs(RYTCTsdata){ - let RYTCTsTechs = {}; - let RYTCTs = this.RYTCTs(RYTCTsdata); - $.each(RYTCTs, function (param, obj1) { - RYTCTsTechs[param] = {}; - $.each(obj1, function (cs, array) { - RYTCTsTechs[param][cs] = Object.keys(array); - }); - }); - return RYTCTsTechs; - } - - static RYTCTsComms(RYTCTsdata){ - let RYTCTsComms = {}; - let RYTCTs = this.RYTCTs(RYTCTsdata); - $.each(RYTCTs, function (param, obj1) { - RYTCTsComms[param] = {}; - $.each(obj1, function (cs, obj2) { - RYTCTsComms[param][cs] = {}; - $.each(obj2, function (comm, array) { - RYTCTsComms[param][cs][comm] = Object.keys(array); - }); - }); - }); - return RYTCTsComms; - } - - static RYTCTsgrid(RYTCTsdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let commData = this.getCommData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTCTsdata)); - let RYTCTsgrid = {}; - $.each(cloneData, function (param, obj) { - RYTCTsgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTCTsgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - obj['Comm'] = commData[obj.Comm]['Comm']; - obj['commDesc'] = commData[obj.Comm]['Desc']; - - let rule = paramById['RYTCTs'][param]['unitRule']; - let data1 = unitData['RYTCTs'][param][obj.Comm]; - let data2 = unitData['RYTCTs'][param][obj.Tech]; - const data = {...data1, ...data2}; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTCTsgrid[param][cs].push(obj); - }); - }); - }); - return RYTCTsgrid; - } - - static RYTCTschart(genData, RYTCTsdata){ - let RYTCTschart = {}; - let RYTCTs = this.RYTCTs(RYTCTsdata); - $.each(RYTCTs, function (param, obj1) { - RYTCTschart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTCTschart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - if(!RYTCTschart[param][cs][tech]){ RYTCTschart[param][cs][tech] = {}; } - $.each(genData['osy-years'], function (idY, year) { - - $.each(obj3, function (comm, obj4) { - if(!RYTCTschart[param][cs][tech][comm]){ RYTCTschart[param][cs][tech][comm] = []; } - let chunk = {}; - chunk['Year'] = year; - $.each(obj4, function (ts, obj) { - chunk[ts] = obj[year] - }); - RYTCTschart[param][cs][tech][comm].push(chunk); - }); + if(year in obj){ + chunk['Value'] = obj[year]; + } + else{ + chunk['Value'] = null; + } + + if(obj.Comm){ - - }); - }); - } - }); - }); - return RYTCTschart; - } - - static RYTMTs(RYTMTsMdata){ - let RYTMTs = {}; - const cloneData = JSON.parse(JSON.stringify(RYTMTsMdata)); - $.each(cloneData, function (param, obj1) { - RYTMTs[param] = {}; - $.each(obj1, function (sc, array) { - RYTMTs[param][sc] = {}; - $.each(array, function (id, obj) { - if(!RYTMTs[param][sc][obj.Tech]){ RYTMTs[param][sc][obj.Tech] = {}; } - if(!RYTMTs[param][sc][obj.Tech][obj.MoId]){ RYTMTs[param][sc][obj.Tech][obj.MoId] = {}; } - RYTMTs[param][sc][obj.Tech][obj.MoId][obj.Ts] = obj; - delete obj.Tech; - delete obj.MoId; - delete obj.Ts; - }); - }); - }); - return RYTMTs; - } - - static RYTMTsgrid(RYTMTsdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - // let mods = DataModel.Mods(genData); - // let ts = DataModel.Timeslices(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTMTsdata)); - let RYTMTsgrid = {}; - $.each(cloneData, function (param, obj) { - RYTMTsgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTMTsgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - - let rule = paramById['RYTMTs'][param]['unitRule']; - let data = unitData['RYTMTs'][param][obj.Tech]; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTMTsgrid[param][cs].push(obj); - }); - }); - }); - return RYTMTsgrid; - } - - static RYTMTschart(genData, RYTMTsdata){ - let RYTMTschart = {}; - let RYTMTs = this.RYTMTs(RYTMTsdata); - $.each(RYTMTs, function (param, obj1) { - RYTMTschart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTMTschart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - if(!RYTMTschart[param][cs][tech]){ RYTMTschart[param][cs][tech] = {}; } - $.each(genData['osy-years'], function (idY, year) { - $.each(obj3, function (mod, obj4) { - if(!RYTMTschart[param][cs][tech][mod]){ RYTMTschart[param][cs][tech][mod] = []; } - let chunk = {}; - chunk['Year'] = year; - $.each(obj4, function (ts, obj) { - chunk[ts] = obj[year] - }); - RYTMTschart[param][cs][tech][mod].push(chunk); - }); - }); + //uslov dodan vk 18072924 ako smo izbrisali commodity a postoji u resulttima + if(obj.Comm in commData){ + chunk['Comm'] = obj.Comm; + chunk['CommDesc'] = commData[obj.Comm]["Desc"]; + + dataC = unitData[group][param][obj.Comm]; + let rule = paramById[group][param]['unitRule']; + //console.log('COM ', dataC, rule) + chunk['Unit'] = jsonLogic.apply(rule, {...dataC}); + } + else{ + chunk['Comm'] = ' ' +obj.Comm; + chunk['CommDesc'] = ' ' +obj.Comm + " deleted from model"; + chunk['Unit'] = ' n/a'; + } + } + + if(obj.Con){ + + if(obj.Con in conData){ + chunk['Con'] = obj.Con; + chunk['ConDesc'] = conData[obj.Con]["Desc"]; + chunk['Unit'] = 'n/a'; + + } + else{ + chunk['Con'] = ' ' +obj.Con; + chunk['ConDesc'] = ' ' +obj.Con + " deleted from model"; + chunk['Unit'] = ' n/a'; + } + } + + if(obj.Stg){ + + if(obj.Stg in stgData){ + chunk['Stg'] = obj.Stg; + chunk['StgDesc'] = stgData[obj.Stg]["Desc"]; + dataS = unitData[group][param][obj.Stg]; + let rule = paramById[group][param]['unitRule']; + chunk['Unit'] = jsonLogic.apply(rule, {...dataS}); + } + else{ + chunk['Stg'] = ' ' +obj.Stg; + chunk['StgDesc'] = ' ' + obj.Stg + " deleted from model"; + chunk['Unit'] = ' n/a'; + } + } + if(obj.Emi){ + + if(obj.Emi in emiData){ + chunk['Emi'] = obj.Emi; + chunk['EmiDesc'] = emiData[obj.Emi]["Desc"]; + dataE = unitData[group][param][obj.Emi]; + let rule = paramById[group][param]['unitRule']; + chunk['Unit'] = jsonLogic.apply(rule, {...dataE}); + } + else{ + chunk['Emi'] = ' ' +obj.Emi; + chunk['EmiDesc'] = ' ' + obj.Emi+' deleted from model'; + chunk['Unit'] = ' n/a'; + } + } + if(obj.Tech){ + //console.log('techData ', obj.Tech, '------', techData[obj.Tech]) //DEMINDLFO + //vk 18972024 ovaj uslov dodat - + // ako korisnik izbrise tech on ce ostati u view json filovima i doci ce do greske, ovaj tech se mora ignorisati u pivotdata + if(obj.Tech in techData){ + let rule = paramById[group][param]['unitRule']; + if(techData[obj.Tech].TG.length != 0){ + $.each(techData[obj.Tech].TG, function (id, tg) { + let tmp = {}; + tmp = JSON.parse(JSON.stringify(chunk)); + tmp['Tech'] = obj.Tech; + tmp['TechGroup'] = techGroupNames[tg]; + tmp['TechDesc'] = techData[obj.Tech]["Desc"]; + tmp['TechGroupDesc'] = techGroupData[tg]["Desc"]; + dataT = unitData[group][param][obj.Tech]; + tmp['Unit'] = jsonLogic.apply(rule, {...dataT}); + pivotData.push(tmp); + }) + }else{ + chunk['Tech'] = obj.Tech; + chunk['TechGroup'] = 'No group'; + chunk['TechDesc'] = techData[obj.Tech]["Desc"]; + chunk['TechGroupDesc'] = 'No group'; + dataT = unitData[group][param][obj.Tech]; + + chunk['Unit'] = jsonLogic.apply(rule, {...dataT}); + pivotData.push(chunk); + } + + // console.log('rule ', rule) + // console.log('unitData[group][param] ', unitData[group][param]) + // console.log('dataT ', dataT) + dataT = unitData[group][param][obj.Tech]; + //console.log('TECH ', dataT, rule) + } + else{ + chunk['Tech'] = ' ' + obj.Tech; + chunk['TechGroup'] = 'No group'; + chunk['TechDesc'] = ' ' + obj.Tech + " deleted from model"; + chunk['TechGroupDesc'] = 'No group'; + chunk['Unit'] = ' n/a'; + pivotData.push(chunk); + } + + } + if(!obj.Tech){ + pivotData.push(chunk); + } + }); } - }); - }); - return RYTMTschart; - } - - static RYTCMTs(RYTCMTsMdata){ - let RYTCMTs = {}; - const cloneData = JSON.parse(JSON.stringify(RYTCMTsMdata)); - $.each(cloneData, function (param, obj1) { - RYTCMTs[param] = {}; - $.each(obj1, function (cs, array) { - RYTCMTs[param][cs] = {}; - $.each(array, function (id, obj) { - if(!RYTCMTs[param][cs][obj.Tech]){ RYTCMTs[param][cs][obj.Tech] = {}; } - if(!RYTCMTs[param][cs][obj.Tech][obj.Comm]){ RYTCMTs[param][cs][obj.Tech][obj.Comm] = {}; } - if(!RYTCMTs[param][cs][obj.Tech][obj.Comm][obj.MoId]){ RYTCMTs[param][cs][obj.Tech][obj.Comm][obj.MoId] = {}; } - - RYTCMTs[param][cs][obj.Tech][obj.Comm][obj.MoId][obj.Ts] = obj; - delete obj.Tech; - delete obj.Comm; - delete obj.MoId; - delete obj.Ts; - }); - }); - }); - return RYTCMTs; - } - static RYTCMTsTechs(RYTCMTsdata){ - let RYTCTsTechs = {}; - let RYTCMTs = this.RYTCMTs(RYTCMTsdata); - $.each(RYTCMTs, function (param, obj1) { - RYTCTsTechs[param] = {}; - $.each(obj1, function (cs, array) { - RYTCTsTechs[param][cs] = Object.keys(array); - }); - }); - return RYTCTsTechs; - } - - static RYTCMTsComms(RYTCMTsdata){ - let RYTCTsComms = {}; - let RYTCMTs = this.RYTCMTs(RYTCMTsdata); - $.each(RYTCMTs, function (param, obj1) { - RYTCTsComms[param] = {}; - $.each(obj1, function (cs, obj2) { - RYTCTsComms[param][cs] = {}; - $.each(obj2, function (comm, array) { - RYTCTsComms[param][cs][comm] = Object.keys(array); - }); - }); - }); - return RYTCTsComms; - } - - static RYTCMTsgrid(RYTCMTsdata, genData, PARAMETERS){ - let techData = this.getTechData(genData); - let commData = this.getCommData(genData); - let unitData = this.getUnitData(genData, PARAMETERS); - let paramById = DataModel.getParamById(PARAMETERS); - - let cloneData = JSON.parse(JSON.stringify(RYTCMTsdata)); - let RYTCMTsgrid = {}; - $.each(cloneData, function (param, obj) { - RYTCMTsgrid[param] = {}; - $.each(obj, function (cs, array) { - RYTCMTsgrid[param][cs] = []; - $.each(array, function (id, obj) { - obj['Tech'] = techData[obj.Tech]['Tech']; - obj['TechDesc'] = techData[obj.Tech]['Desc']; - obj['Comm'] = commData[obj.Comm]['Comm']; - obj['commDesc'] = commData[obj.Comm]['Desc']; - let rule = paramById['RYTCMTs'][param]['unitRule']; - let data1 = unitData['RYTCMTs'][param][obj.Comm]; - let data2 = unitData['RYTCMTs'][param][obj.Tech]; - const data = {...data1, ...data2}; - obj['UnitId'] = jsonLogic.apply(rule, data); - RYTCMTsgrid[param][cs].push(obj); - }); }); }); - return RYTCMTsgrid; + return pivotData; } - static RYTCMTschart(genData, RYTCMTsdata){ - let RYTCTschart = {}; - let RYTCMTs = this.RYTCMTs(RYTCMTsdata); - $.each(RYTCMTs, function (param, obj1) { - RYTCTschart[param] = {}; - $.each(obj1, function (cs, obj2) { - if (obj2.length !== 0){ - RYTCTschart[param][cs] = {}; - $.each(obj2, function (tech, obj3) { - if(!RYTCTschart[param][cs][tech]){ RYTCTschart[param][cs][tech] = {}; } - $.each(genData['osy-years'], function (idY, year) { - - $.each(obj3, function (comm, obj4) { - if(!RYTCTschart[param][cs][tech][comm]){ RYTCTschart[param][cs][tech][comm] = []; } - $.each(obj4, function (mod, obj5) { - if(!RYTCTschart[param][cs][tech][comm][mod]){ RYTCTschart[param][cs][tech][comm][mod] = []; } - let chunk = {}; - chunk['Year'] = year; - $.each(obj5, function (ts, obj) { - chunk[ts] = obj[year] - }); - RYTCTschart[param][cs][tech][comm][mod].push(chunk); - }); - }); - - - }); - }); - } - }); - }); - return RYTCTschart; - } - */ -} \ No newline at end of file +} diff --git a/WebAPP/Classes/Html.Class.js b/WebAPP/Classes/Html.Class.js index abb984ec1..75f32be2f 100644 --- a/WebAPP/Classes/Html.Class.js +++ b/WebAPP/Classes/Html.Class.js @@ -2,6 +2,13 @@ import { CURRENCY, UNITDEFINITION } from './Const.Class.js'; import { Message } from "./Message.Class.js"; export class Html { + static renderPreformatted(target, text, emptyMessage = '') { + const $target = $(target); + const content = text == null || text === '' ? emptyMessage : String(text); + + $target.empty(); + $target.append($('
      ', { class: 'log-output' }).text(content));
      +    }
       
           static renderModels(cases, selectedCS) {
               $('#cases').empty();
      @@ -198,7 +205,7 @@ export class Html {
               //     $("#osy-DataFile").html('Data file is to large for preview.');
               // }
       
      -        $("#osy-DataFile").html('
      '+DataFile+'
      '); + Html.renderPreformatted('#osy-DataFile', DataFile, 'Data file preview unavailable.'); $('#tabs a[href="#tabDataFile"]').tab('show'); @@ -209,6 +216,10 @@ export class Html { } + static renderModelFile(ModelFile){ + Html.renderPreformatted('#osy-ModelFile', ModelFile, 'Model file preview unavailable.'); + } + static appendCasePicker(value, selectedCS, pageId) { let res = `
    3. diff --git a/WebAPP/Classes/Osemosys.Class.js b/WebAPP/Classes/Osemosys.Class.js index 602802e44..2b82cfdfd 100644 --- a/WebAPP/Classes/Osemosys.Class.js +++ b/WebAPP/Classes/Osemosys.Class.js @@ -288,6 +288,61 @@ export class Osemosys { }); } + // static readModelFile() { + // return fetch(Base.apiUrl() + "readModelFile", {cache: "no-store"}) + // .then((response) => { + // if (response.ok) { + // // console.log('response1 ', response) + // // console.log('data ', response.text()) + // return response; + // } + // throw new Error('No casename selecetd'); + // }) + // .then(response => response.text()) + // .catch(error => null); + // } + + + static readModelFile() { + return fetch(Base.apiUrl() + "readModelFile", { + method: "GET", + cache: "no-store" + }) + .then(response => { + if (!response.ok) { + throw new Error("Could not load model file"); + } + return response.text(); // ✔ return the real text + }) + .then(text => { + return text; // ✔ pass the text forward + }) + .catch(error => { + console.error("readModelFile error:", error); + return null; // ✔ only null on real error + }); + } + + static readLogFile() { + return fetch(Base.apiUrl() + "readLogFile", { + method: "GET", + cache: "no-store" + }) + .then(response => { + if (!response.ok) { + throw new Error("Could not load model file"); + } + return response.text(); // ✔ return the real text + }) + .then(text => { + return text; // ✔ pass the text forward + }) + .catch(error => { + console.error("readLogFile error:", error); + return null; // ✔ only null on real error + }); + } + static readDataFile(casename, caserunname) { return new Promise((resolve, reject) => { $.ajax({ @@ -360,35 +415,19 @@ export class Osemosys { // .catch(error => error); // } - // static getData(casename, dataJson) { - // // return fetch('../../DataStorage/'+casename+'/'+dataJson, {cache: "no-store"}) - // // .then(response => response.json()) - // // .catch(error => error); - - // return fetch('../../DataStorage/'+casename+'/'+dataJson, {cache: "no-store"}) - // .then((response) => { - // if (response.ok) { - // //console.log('response1 ', response) - // //console.log('data ', response.json()) - // return response; - // } - // throw new Error('No casename selecetd'); - // }) - // .then(response => response.json()) - // .catch(error => null); - // } - static getData(casename, dataJson) { - if (!casename || casename === "null") { - console.warn("getData called without valid casename"); - return Promise.resolve(null); - } + // return fetch('../../DataStorage/'+casename+'/'+dataJson, {cache: "no-store"}) + // .then(response => response.json()) + // .catch(error => error); + return fetch('../../DataStorage/'+casename+'/'+dataJson, {cache: "no-store"}) .then((response) => { if (response.ok) { - return response; + //console.log('response1 ', response) + //console.log('data ', response.json()) + return response; } - throw new Error('Invalid casename'); + throw new Error('No casename selecetd'); }) .then(response => response.json()) .catch(error => null); @@ -412,44 +451,28 @@ export class Osemosys { }); } - // static getResultData(casename, dataJson) { - // // return new Promise((resolve, reject) => { - // // fetch('../../DataStorage/'+casename+'/view/' +dataJson, {cache: "no-store"}) - // // .then(DATA => { - // // DATA = DATA.json(); - // // resolve(DATA); - // // }) - // // .catch(error => { - // // if(error == 'UNKNOWN'){ error = xhr.responseJSON.message } - // // reject(error); - // // }); - // // }); - - // return fetch('../../DataStorage/'+casename+'/view/' +dataJson, {cache: "no-store"}) - // .then((response) => { - // if (response.ok) { - // //console.log('response1 ', response) - // return response; - // } - // throw new Error('No casename selecetd'); - // }) - // .then(response => response.json()) - // .catch(error => null); - // } - static getResultData(casename, dataJson) { - if (!casename || casename === "null") { - console.warn("getResultData called without valid casename"); - return Promise.resolve(null); - } + // return new Promise((resolve, reject) => { + // fetch('../../DataStorage/'+casename+'/view/' +dataJson, {cache: "no-store"}) + // .then(DATA => { + // DATA = DATA.json(); + // resolve(DATA); + // }) + // .catch(error => { + // if(error == 'UNKNOWN'){ error = xhr.responseJSON.message } + // reject(error); + // }); + // }); + return fetch('../../DataStorage/'+casename+'/view/' +dataJson, {cache: "no-store"}) .then((response) => { if (response.ok) { + //console.log('response1 ', response) return response; } - throw new Error('Invalid casename'); + throw new Error('No casename selecetd'); }) - .then(response => response.json()) + .then(response => response.json()) .catch(error => null); } diff --git a/WebAPP/DataStorage/Parameters.json b/WebAPP/DataStorage/Parameters.json index e6caac7a0..cfe0ae54e 100644 --- a/WebAPP/DataStorage/Parameters.json +++ b/WebAPP/DataStorage/Parameters.json @@ -1,860 +1 @@ -{ - "R": [ - { - "id": "DR", - "value": "Discount Rate", - "default": 0.05, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RT": [ - { - "id": "DRI", - "value": "Discount Rate Idv", - "default": 0.05, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - }, - { - "id": "CAU", - "value": "Capacity To Activity Unit", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - }, - { - "var": "divide" - }, - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "OL", - "value": "Operational Life", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "years" - } - ] - } - }, - { - "id": "TMPAL", - "value": "Total Technology Model Period Activity Lower Limit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "TMPAU", - "value": "Total Technology Model Period Activity Upper Limit", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - } - ], - "RE": [ - { - "id": "MPEL", - "value": "Model Period Emission Limit", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - } - ], - "RYCn": [ - { - "id": "UCC", - "value": "UDC Constant", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - } - ], - "RYTCn": [ - { - "id": "CCM", - "value": "UDC Multiplier Total Capacity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - }, - { - "id": "CNCM", - "value": "UDC Multiplier New Capacity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - }, - { - "id": "CAM", - "value": "UDC Multiplier Activity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - } - ], - "RYTs": [ - { - "id": "YS", - "value": "Year Split", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYDtb": [ - { - "id": "DS", - "value": "Day Split", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYSeDt": [ - { - "id": "DIDT", - "value": "Days In Day Type", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "number" - } - ] - } - } - ], - "RYT": [ - { - "id": "AF", - "value": "Availability Factor", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - }, - { - "id": "CC", - "value": "Capital Cost", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "multiply" - }, - { - "var": "Currency" - }, - { - "var": "divide" - }, - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "FC", - "value": "Fixed Cost", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - }, - { - "var": "divide" - }, - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "RC", - "value": "Residual Capacity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TAMaxC", - "value": "Total Annual Max Capacity", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TAMaxCI", - "value": "Total Annual Max Capacity Investment", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TAMinC", - "value": "Total Annual Min Capacity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TAMinCI", - "value": "Total Annual Min Capacity Investment", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TAL", - "value": "Total Technology Annual Activity Lower Limit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "TAU", - "value": "Total Technology Annual Activity Upper Limit", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "COTU", - "value": "Capacity Of One Technology Unit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - } - ], - "RYTM": [ - { - "id": "VC", - "value": "Variable Cost", - "default": 0.0001, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "TAMLL", - "value": "Technology Activity By Mode Lower Limit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "TAMUL", - "value": "Technology Activity By Mode Upper Limit", - "default": 99999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "TADML", - "value": "Technology Activity Decrease By Mode Limit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - }, - { - "id": "TAIML", - "value": "Technology Activity Increase By Mode Limit", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYTC": [ - { - "id": "INCR", - "value": "Input To New Capacity Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "ITCR", - "value": "Input To Total Capacity Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - } - ], - "RYTCM": [ - { - "id": "IAR", - "value": "Input Activity Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "OAR", - "value": "Output Activity Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - } - ], - "RS": [ - { - "id": "OLS", - "value": "Operational Life Storage", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "years" - } - ] - } - }, - { - "id": "SLS", - "value": "Storage Level Start", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYS": [ - { - "id": "CCS", - "value": "Capital Cost Storage", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - }, - { - "var": "divide" - }, - { - "var": "StgUnit" - } - ] - } - }, - { - "id": "RSC", - "value": "Residual Storage Capacity", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "StgUnit" - } - ] - } - }, - { - "id": "MSC", - "value": "Min Storage Charge", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RTSM": [ - { - "id": "TTS", - "value": "Technology To Storage", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - }, - { - "id": "TFS", - "value": "Technology From Storage", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [] - } - } - ], - "RYTTs": [ - { - "id": "CF", - "value": "Capacity Factor", - "default": 1, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYC": [ - { - "id": "AAD", - "value": "Accumulated Annual Demand", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - }, - { - "id": "SAD", - "value": "Specified Annual Demand", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - } - ], - "RYCTs": [ - { - "id": "SDP", - "value": "Specified Demand Profile", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "percent" - }, - { - "var": "divide" - }, - { - "var": "hundert" - } - ] - } - } - ], - "RYE": [ - { - "id": "AEL", - "value": "Annual Emission Limit", - "default": 999999, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - }, - { - "id": "EP", - "value": "Emissions Penalty", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - }, - { - "var": "divide" - }, - { - "var": "EmiUnit" - } - ] - } - } - ], - "RYTEM": [ - { - "id": "EAR", - "value": "Emission Activity Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - }, - { - "id": "EACR", - "value": "Emission To Activity Change Ratio", - "default": 0, - "enable": true, - "menu": 1, - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - }, - { - "var": "divide" - }, - { - "var": "ActUnitId" - } - ] - } - } - ] -} \ No newline at end of file +{"R": [{"id": "DR", "value": "Discount Rate", "default": 0.05, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RT": [{"id": "DRI", "value": "Discount Rate Idv", "default": 0.05, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}, {"id": "CAU", "value": "Capacity To Activity Unit", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}, {"var": "divide"}, {"var": "CapUnitId"}]}}, {"id": "OL", "value": "Operational Life", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "years"}]}}, {"id": "TMPAL", "value": "Total Technology Model Period Activity Lower Limit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}, {"id": "TMPAU", "value": "Total Technology Model Period Activity Upper Limit", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}], "RE": [{"id": "MPEL", "value": "Model Period Emission Limit", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "EmiUnit"}]}}], "RYCn": [{"id": "UCC", "value": "UDC Constant", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}], "RYTCn": [{"id": "CCM", "value": "UDC Multiplier Total Capacity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}, {"id": "CNCM", "value": "UDC Multiplier New Capacity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}, {"id": "CAM", "value": "UDC Multiplier Activity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}], "RYTs": [{"id": "YS", "value": "Year Split", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYDtb": [{"id": "DS", "value": "Day Split", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYSeDt": [{"id": "DIDT", "value": "Days In Day Type", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "number"}]}}], "RYT": [{"id": "AF", "value": "Availability Factor", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}, {"id": "CC", "value": "Capital Cost", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "milion"}, {"var": "multiply"}, {"var": "Currency"}, {"var": "divide"}, {"var": "CapUnitId"}]}}, {"id": "FC", "value": "Fixed Cost", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "CapUnitId"}]}}, {"id": "RC", "value": "Residual Capacity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TAMaxC", "value": "Total Annual Max Capacity", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TAMaxCI", "value": "Total Annual Max Capacity Investment", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TAMinC", "value": "Total Annual Min Capacity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TAMinCI", "value": "Total Annual Min Capacity Investment", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TAL", "value": "Total Technology Annual Activity Lower Limit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}, {"id": "TAU", "value": "Total Technology Annual Activity Upper Limit", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}, {"id": "COTU", "value": "Capacity Of One Technology Unit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CapUnitId"}]}}], "RYTM": [{"id": "VC", "value": "Variable Cost", "default": 0.0001, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "ActUnitId"}]}}, {"id": "TAMLL", "value": "Technology Activity By Mode Lower Limit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}, {"id": "TAMUL", "value": "Technology Activity By Mode Upper Limit", "default": 99999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "ActUnitId"}]}}, {"id": "TADML", "value": "Technology Activity Decrease By Mode Limit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}, {"id": "TAIML", "value": "Technology Activity Increase By Mode Limit", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYTC": [{"id": "INCR", "value": "Input To New Capacity Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}, {"id": "ITCR", "value": "Input To Total Capacity Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}], "RYTCM": [{"id": "IAR", "value": "Input Activity Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}, {"id": "OAR", "value": "Output Activity Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}], "RS": [{"id": "OLS", "value": "Operational Life Storage", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "years"}]}}, {"id": "SLS", "value": "Storage Level Start", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYS": [{"id": "CCS", "value": "Capital Cost Storage", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "StgUnit"}]}}, {"id": "RSC", "value": "Residual Storage Capacity", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "StgUnit"}]}}, {"id": "MSC", "value": "Min Storage Charge", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RTSM": [{"id": "TTS", "value": "Technology To Storage", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}, {"id": "TFS", "value": "Technology From Storage", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": []}}], "RYTTs": [{"id": "CF", "value": "Capacity Factor", "default": 1, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYC": [{"id": "AAD", "value": "Accumulated Annual Demand", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}]}}, {"id": "SAD", "value": "Specified Annual Demand", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "CommUnit"}]}}], "RYCTs": [{"id": "SDP", "value": "Specified Demand Profile", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "percent"}, {"var": "divide"}, {"var": "hundert"}]}}], "RYE": [{"id": "AEL", "value": "Annual Emission Limit", "default": 999999, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "EmiUnit"}]}}, {"id": "EP", "value": "Emissions Penalty", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "EmiUnit"}]}}], "RYTEM": [{"id": "EAR", "value": "Emission Activity Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "EmiUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}, {"id": "EACR", "value": "Emission To Activity Change Ratio", "default": 0, "enable": true, "menu": 1, "unitRule": {"cat": [{"var": "EmiUnit"}, {"var": "divide"}, {"var": "ActUnitId"}]}}]} \ No newline at end of file diff --git a/WebAPP/DataStorage/Variables.json b/WebAPP/DataStorage/Variables.json index c15ae6630..b489cf218 100644 --- a/WebAPP/DataStorage/Variables.json +++ b/WebAPP/DataStorage/Variables.json @@ -1,444 +1 @@ -{ - "R": [ - { - "id": "OV", - "value": "Objective Value", - "name": "ObjectiveValue", - "unitRule": { - "cat": [ - { - "var": "" - } - ] - } - } - ], - "RT": [ - { - "id": "TTMPA", - "value": "Total Technology Model Period Activity", - "name": "TotalTechnologyModelPeriodActivity", - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - } - ], - "RYT": [ - { - "id": "ANC", - "value": "Accumulated New Capacity", - "name": "AccumulatedNewCapacity", - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "AIC", - "value": "Annualized Investment Cost ", - "name": "AnnualizedInvestmentCost", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "CI", - "value": "Capital Investment", - "name": "CapitalInvestment", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "AFOC", - "value": "Annual Fixed Operating Cost", - "name": "AnnualFixedOperatingCost", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "AVOC", - "value": "Annual Variable Operating Cost", - "name": "AnnualVariableOperatingCost", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "NC", - "value": "New Capacity", - "name": "NewCapacity", - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "NONTU", - "value": "Number Of New Technology Units", - "name": "NumberOfNewTechnologyUnits", - "unitRule": { - "cat": [] - } - }, - { - "id": "SV", - "value": "Salvage Value", - "name": "SalvageValue", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "TCA", - "value": "Total Capacity By Technology", - "name": "TotalCapacityAnnual", - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - }, - { - "id": "TEP", - "value": "Technology Emissions Penalty", - "name": "TechnologyEmissionsPenalty", - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - } - ], - "RYC": [ - { - "id": "EB_d", - "value": "Shadow price - Annual Commodity Balance", - "name": "EBb4_EnergyBalanceEachYear4_ICR", - "unitRule": { - "cat": [ - { - "var": "CapUnitId" - } - ] - } - } - ], - "RYE": [ - { - "id": "AEL_d", - "value": "Shadow price - Emissions Limit", - "name": "E8_AnnualEmissionsLimit", - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - } - ], - "RYCn": [ - { - "id": "UDCI_d", - "value": "Shadow price - UDC Inequality", - "name": "UDC1_UserDefinedConstraintInequality", - "unitRule": { - "cat": [ - { - "var": "" - } - ] - } - }, - { - "id": "UDCE_d", - "value": "Shadow price - UDC Equality", - "name": "UDC2_UserDefinedConstraintEquality", - "unitRule": { - "cat": [ - { - "var": "" - } - ] - } - } - ], - "RYS": [ - { - "id": "NSC", - "value": "New Storage Capacity", - "name": "NewStorageCapacity", - "unitRule": { - "cat": [ - { - "var": "StgUnit" - } - ] - } - }, - { - "id": "ANSC", - "value": "Accumulated New Storage Capacity", - "name": "AccumulatedNewStorageCapacity", - "unitRule": { - "cat": [ - { - "var": "StgUnit" - } - ] - } - }, - { - "id": "TSC", - "value": "Total Storage Capacity", - "name": "TotalStorageCapacity", - "unitRule": { - "cat": [ - { - "var": "StgUnit" - } - ] - } - }, - - { - "id": "SVS", - "value": "Salvage Value Storage", - "name": "SalvageValueStorage", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - }, - { - "id": "CIS", - "value": "Capital Investment Storage", - "name": "CapitalInvestmentStorage", - "unitRule": { - "cat": [ - { - "var": "milion" - }, - { - "var": "Currency" - } - ] - } - } - ], - "RYTM": [ - { - "id": "TATABM", - "value": "Total Annual Technology Activity By Mode", - "name": "TotalAnnualTechnologyActivityByMode", - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - } - ], - "RYTC": [ - { - "id": "ITNC", - "value": "Input To New Capacity", - "name": "InputToNewCapacity", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - }, - { - "id": "ITTC", - "value": "Input To Total Capacity", - "name": "InputToTotalCapacity", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - } - ], - "RYTE": [ - { - "id": "ATE", - "value": "Annual Technology Emission", - "name": "AnnualTechnologyEmission", - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - } - ], - "RYCTs": [ - { - "id": "D", - "value": "Demand", - "name": "Demand", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - } - ], - "RYTEM": [ - { - "id": "ATEBM", - "value": "Annual Technology Emission By Mode", - "name": "AnnualTechnologyEmissionByMode", - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - }, - { - "id": "EBAC", - "value": "Emission By Activity Change", - "name": "EmissionByActivityChange", - "unitRule": { - "cat": [ - { - "var": "EmiUnit" - } - ] - } - } - ], - "RYTMTs": [ - { - "id": "ROA", - "value": "Rate Of Activity", - "name": "RateOfActivity", - "unitRule": { - "cat": [ - { - "var": "ActUnitId" - } - ] - } - } - ], - "RYTCMTs": [ - { - "id": "PBT", - "value": "Production By Technology By Mode", - "name": "ProductionByTechnologyByMode", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - }, - { - "id": "ROPBT", - "value": "Rate Of Production By Technology By Mode", - "name": "RateOfProductionByTechnologyByMode", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - }, - { - "id": "ROUBT", - "value": "Rate Of Use By Technology By Mode", - "name": "RateOfUseByTechnologyByMode", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - }, - { - "id": "UBT", - "value": "Use By Technology By Mode", - "name": "UseByTechnologyByMode", - "unitRule": { - "cat": [ - { - "var": "CommUnit" - } - ] - } - } - ] -} \ No newline at end of file +{"R": [{"id": "OV", "value": "Objective Value", "name": "ObjectiveValue", "unitRule": {"cat": [{"var": "number"}]}}], "RT": [{"id": "TTMPA", "value": "Total Technology Model Period Activity", "name": "TotalTechnologyModelPeriodActivity", "unitRule": {"cat": [{"var": "ActUnitId"}]}}], "RYT": [{"id": "ANC", "value": "Accumulated New Capacity", "name": "AccumulatedNewCapacity", "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "AIC", "value": "Annualized Investment Cost ", "name": "AnnualizedInvestmentCost", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "CI", "value": "Capital Investment", "name": "CapitalInvestment", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "AFOC", "value": "Annual Fixed Operating Cost", "name": "AnnualFixedOperatingCost", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "AVOC", "value": "Annual Variable Operating Cost", "name": "AnnualVariableOperatingCost", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "NC", "value": "New Capacity", "name": "NewCapacity", "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "NONTU", "value": "Number Of New Technology Units", "name": "NumberOfNewTechnologyUnits", "unitRule": {"cat": [{"var": "number"}]}}, {"id": "SV", "value": "Salvage Value", "name": "SalvageValue", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "TCA", "value": "Total Capacity By Technology", "name": "TotalCapacityAnnual", "unitRule": {"cat": [{"var": "CapUnitId"}]}}, {"id": "TEP", "value": "Technology Emissions Penalty", "name": "TechnologyEmissionsPenalty", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}], "RYE": [{"id": "AEL_d", "value": "Shadow price - Emissions Limit", "name": "E8_AnnualEmissionsLimit", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "EmiUnit"}]}}], "RYS": [{"id": "NSC", "value": "New Storage Capacity", "name": "NewStorageCapacity", "unitRule": {"cat": [{"var": "StgUnit"}]}}, {"id": "ANSC", "value": "Accumulated New Storage Capacity", "name": "AccumulatedNewStorageCapacity", "unitRule": {"cat": [{"var": "StgUnit"}]}}, {"id": "TSC", "value": "Total Storage Capacity", "name": "TotalStorageCapacity", "unitRule": {"cat": [{"var": "StgUnit"}]}}, {"id": "SVS", "value": "Salvage Value Storage", "name": "SalvageValueStorage", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}, {"id": "CIS", "value": "Capital Investment Storage", "name": "CapitalInvestmentStorage", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}]}}], "RYC": [{"id": "EB_d", "value": "Shadow price - Energy Balance", "name": "EBb4_EnergyBalanceEachYear4_ICR", "unitRule": {"cat": [{"var": "milion"}, {"var": "Currency"}, {"var": "divide"}, {"var": "EmiUnit"}]}}], "RYCn": [{"id": "UDCI_d", "value": "Shadow price - UDC Inequality", "name": "UDC1_UserDefinedConstraintInequality", "unitRule": {"cat": [{"var": "number"}]}}, {"id": "UDCE_d", "value": "Shadow price - UDC Equality", "name": "UDC2_UserDefinedConstraintEquality", "unitRule": {"cat": [{"var": "number"}]}}], "RYTM": [{"id": "TATABM", "value": "Total Annual Technology Activity By Mode", "name": "TotalAnnualTechnologyActivityByMode", "unitRule": {"cat": [{"var": "ActUnitId"}]}}], "RYTC": [{"id": "ITNC", "value": "Input To New Capacity", "name": "InputToNewCapacity", "unitRule": {"cat": [{"var": "CommUnit"}]}}, {"id": "ITTC", "value": "Input To Total Capacity", "name": "InputToTotalCapacity", "unitRule": {"cat": [{"var": "CommUnit"}]}}], "RYTE": [{"id": "ATE", "value": "Annual Technology Emission", "name": "AnnualTechnologyEmission", "unitRule": {"cat": [{"var": "EmiUnit"}]}}], "RYCTs": [{"id": "D", "value": "Demand", "name": "Demand", "unitRule": {"cat": [{"var": "CommUnit"}]}}], "RYTEM": [{"id": "ATEBM", "value": "Annual Technology Emission By Mode", "name": "AnnualTechnologyEmissionByMode", "unitRule": {"cat": [{"var": "EmiUnit"}]}}, {"id": "EBAC", "value": "Emission By Activity Change", "name": "EmissionByActivityChange", "unitRule": {"cat": [{"var": "EmiUnit"}]}}], "RYTMTs": [{"id": "ROA", "value": "Rate Of Activity", "name": "RateOfActivity", "unitRule": {"cat": [{"var": "ActUnitId"}, {"var": "divide"}, {"var": "years"}]}}], "RYTCMTs": [{"id": "PBT", "value": "Production By Technology By Mode", "name": "ProductionByTechnologyByMode", "unitRule": {"cat": [{"var": "CommUnit"}]}}, {"id": "ROPBT", "value": "Rate Of Production By Technology By Mode", "name": "RateOfProductionByTechnologyByMode", "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "years"}]}}, {"id": "ROUBT", "value": "Rate Of Use By Technology By Mode", "name": "RateOfUseByTechnologyByMode", "unitRule": {"cat": [{"var": "CommUnit"}, {"var": "divide"}, {"var": "years"}]}}, {"id": "UBT", "value": "Use By Technology By Mode", "name": "UseByTechnologyByMode", "unitRule": {"cat": [{"var": "CommUnit"}]}}]} \ No newline at end of file diff --git a/WebAPP/Routes/Routes.Class.js b/WebAPP/Routes/Routes.Class.js index bc7d7f755..ce6cef24a 100644 --- a/WebAPP/Routes/Routes.Class.js +++ b/WebAPP/Routes/Routes.Class.js @@ -110,6 +110,16 @@ export class Routes { }); }); }); + crossroads.addRoute('/ModelFile', function() { + $('#content').html('

      Loading...

      '); + import('../App/Controller/ModelFile.js') + .then(ModelFile => { + $( ".osy-content" ).load( 'App/View/ModelFile.html', function() { + localStorage.setItem("osy-pageId", "ModelFile"); + ModelFile.default.onLoad(); + }); + }); + }); crossroads.addRoute('/Versions', function() { $('#content').html('

      Loading...

      '); $( ".osy-content" ).load( 'App/View/Versions.html'); diff --git a/WebAPP/index.html b/WebAPP/index.html index c3d56fe99..476ad51ef 100644 --- a/WebAPP/index.html +++ b/WebAPP/index.html @@ -2,7 +2,7 @@ - MUIO 5.4 + MUIO 5.5 @@ -31,13 +31,29 @@ + - + + + + @@ -49,16 +65,13 @@ - - - - + @@ -83,6 +96,9 @@ + + + @@ -124,7 +140,7 @@

        @@ -61,4 +61,3 @@

        Model Equations

        } - diff --git a/WebAPP/Classes/Osemosys.Class.js b/WebAPP/Classes/Osemosys.Class.js index 2b82cfdfd..58cd9958e 100644 --- a/WebAPP/Classes/Osemosys.Class.js +++ b/WebAPP/Classes/Osemosys.Class.js @@ -310,7 +310,7 @@ export class Osemosys { }) .then(response => { if (!response.ok) { - throw new Error("Could not load model file"); + throw new Error("Could not load log file"); } return response.text(); // ✔ return the real text }) @@ -612,4 +612,4 @@ export class Osemosys { }); }); } -} \ No newline at end of file +} From dc8f3c60a9cc4366752b134e2ec4c2fe7cd00207 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 17:21:09 -0400 Subject: [PATCH 05/10] Add path traversal guards to DataFile routes and constructor --- API/Classes/Case/OsemosysClass.py | 1 + API/Routes/DataFile/DataFileRoute.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/API/Classes/Case/OsemosysClass.py b/API/Classes/Case/OsemosysClass.py index 5ef3a5e53..93ba262fc 100644 --- a/API/Classes/Case/OsemosysClass.py +++ b/API/Classes/Case/OsemosysClass.py @@ -7,6 +7,7 @@ class Osemosys(): def __init__(self, case): + Config.validate_path(Config.DATA_STORAGE, case) self.case = case self.PARAMETERS = File.readParamFile(Path(Config.DATA_STORAGE, 'Parameters.json')) self.VARIABLES = File.readParamFile(Path(Config.DATA_STORAGE, 'Variables.json')) diff --git a/API/Routes/DataFile/DataFileRoute.py b/API/Routes/DataFile/DataFileRoute.py index c4cb89e9c..3d53405a4 100644 --- a/API/Routes/DataFile/DataFileRoute.py +++ b/API/Routes/DataFile/DataFileRoute.py @@ -66,6 +66,7 @@ def deleteCaseRun(): if not casename: return jsonify({'message': 'No model selected.', 'status_code': 'error'}), 400 + Config.validate_path(Config.DATA_STORAGE, Path(casename, 'res', caserunname)) casePath = Path(Config.DATA_STORAGE, casename, 'res', caserunname) if not resultsOnly: shutil.rmtree(casePath) @@ -199,9 +200,12 @@ def downloadDataFile(): #path = "/Examples.pdf" case = session.get('osycase', None) caserunname = request.args.get('caserunname') + Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname)) dataFile = Path(Config.DATA_STORAGE,case, 'res',caserunname, 'data.txt') return send_file(dataFile.resolve(), as_attachment=True, max_age=0) - + + except PermissionError: + return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400 except(IOError): return jsonify('No existing cases!'), 404 @@ -210,9 +214,12 @@ def downloadFile(): try: case = session.get('osycase', None) file = request.args.get('file') + Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', 'csv', file)) dataFile = Path(Config.DATA_STORAGE,case,'res','csv',file) return send_file(dataFile.resolve(), as_attachment=True, max_age=0) - + + except PermissionError: + return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400 except(IOError): return jsonify('No existing cases!'), 404 @@ -222,9 +229,12 @@ def downloadCSVFile(): case = session.get('osycase', None) file = request.args.get('file') caserunname = request.args.get('caserunname') + Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname, 'csv', file)) dataFile = Path(Config.DATA_STORAGE,case,'res',caserunname,'csv',file) return send_file(dataFile.resolve(), as_attachment=True, max_age=0) - + + except PermissionError: + return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400 except(IOError): return jsonify('No existing cases!'), 404 @@ -233,9 +243,12 @@ def downloadResultsFile(): try: case = session.get('osycase', None) caserunname = request.args.get('caserunname') + Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname)) dataFile = Path(Config.DATA_STORAGE,case, 'res', caserunname,'results.txt') return send_file(dataFile.resolve(), as_attachment=True, max_age=0) - + + except PermissionError: + return jsonify({'message': 'Invalid path.', 'status_code': 'error'}), 400 except(IOError): return jsonify('No existing cases!'), 404 From 0c8cb120648d9fc68371888ad03600088aa28dee Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 22:19:44 -0400 Subject: [PATCH 06/10] Fix None handling in path traversal guards for download routes --- API/Routes/DataFile/DataFileRoute.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/API/Routes/DataFile/DataFileRoute.py b/API/Routes/DataFile/DataFileRoute.py index 3d53405a4..a89b69e61 100644 --- a/API/Routes/DataFile/DataFileRoute.py +++ b/API/Routes/DataFile/DataFileRoute.py @@ -66,7 +66,7 @@ def deleteCaseRun(): if not casename: return jsonify({'message': 'No model selected.', 'status_code': 'error'}), 400 - Config.validate_path(Config.DATA_STORAGE, Path(casename, 'res', caserunname)) + Config.validate_path(Config.DATA_STORAGE, os.path.join(casename, 'res', caserunname or '')) casePath = Path(Config.DATA_STORAGE, casename, 'res', caserunname) if not resultsOnly: shutil.rmtree(casePath) @@ -200,7 +200,7 @@ def downloadDataFile(): #path = "/Examples.pdf" case = session.get('osycase', None) caserunname = request.args.get('caserunname') - Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname)) + Config.validate_path(Config.DATA_STORAGE, os.path.join(case or '', 'res', caserunname or '')) dataFile = Path(Config.DATA_STORAGE,case, 'res',caserunname, 'data.txt') return send_file(dataFile.resolve(), as_attachment=True, max_age=0) @@ -214,7 +214,7 @@ def downloadFile(): try: case = session.get('osycase', None) file = request.args.get('file') - Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', 'csv', file)) + Config.validate_path(Config.DATA_STORAGE, os.path.join(case or '', 'res', 'csv', file or '')) dataFile = Path(Config.DATA_STORAGE,case,'res','csv',file) return send_file(dataFile.resolve(), as_attachment=True, max_age=0) @@ -229,7 +229,7 @@ def downloadCSVFile(): case = session.get('osycase', None) file = request.args.get('file') caserunname = request.args.get('caserunname') - Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname, 'csv', file)) + Config.validate_path(Config.DATA_STORAGE, os.path.join(case or '', 'res', caserunname or '', 'csv', file or '')) dataFile = Path(Config.DATA_STORAGE,case,'res',caserunname,'csv',file) return send_file(dataFile.resolve(), as_attachment=True, max_age=0) @@ -243,7 +243,7 @@ def downloadResultsFile(): try: case = session.get('osycase', None) caserunname = request.args.get('caserunname') - Config.validate_path(Config.DATA_STORAGE, Path(case, 'res', caserunname)) + Config.validate_path(Config.DATA_STORAGE, os.path.join(case or '', 'res', caserunname or '')) dataFile = Path(Config.DATA_STORAGE,case, 'res', caserunname,'results.txt') return send_file(dataFile.resolve(), as_attachment=True, max_age=0) From 72870193483d57348e0132d1d9a983901e14ae9d Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 22:48:01 -0400 Subject: [PATCH 07/10] Add CodeQL sanitizer model for validate_path --- .../extensions/muiogo-model-pack/codeql-pack.yml | 7 +++++++ .../muiogo-model-pack/models/config-sanitizer.yml | 11 +++++++++++ API/Classes/Base/Config.py | 4 +++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .github/codeql/extensions/muiogo-model-pack/codeql-pack.yml create mode 100644 .github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml diff --git a/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml b/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml new file mode 100644 index 000000000..09ed7b555 --- /dev/null +++ b/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml @@ -0,0 +1,7 @@ +name: muiogo/python-model-pack +version: 0.0.1 +library: true +extensionTargets: + codeql/python-all: "*" +dataExtensions: + - models/**/*.yml diff --git a/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml b/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml new file mode 100644 index 000000000..587bbb889 --- /dev/null +++ b/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml @@ -0,0 +1,11 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: barrierModel + data: + # validate_path checks that the resolved path stays inside DATA_STORAGE, + # raising PermissionError on traversal. Its return value is the safe + # absolute path. CodeQL does not recognise this as a built-in sanitizer, + # so we declare it here. Any change to validate_path's return-value + # semantics must be reflected in this file. + - ["Classes.Base.Config", "Member[validate_path].ReturnValue", "path-injection"] diff --git a/API/Classes/Base/Config.py b/API/Classes/Base/Config.py index 23e59f3a5..557fa7131 100644 --- a/API/Classes/Base/Config.py +++ b/API/Classes/Base/Config.py @@ -4,7 +4,9 @@ from dotenv import load_dotenv import platform -# Central path validation utility (prevents path traversal) +# Central path validation utility (prevents path traversal). +# Declared as a CodeQL sanitizer barrier in .github/codeql/extensions/. +# If the return-value semantics of this function change, update that file too. def validate_path(base_dir, user_input): base_raw = os.fspath(base_dir) user_raw = "" if user_input is None else os.fspath(user_input) From ef0feaa3bc810b749bf2bfc1213c5786d1c94325 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 22:49:14 -0400 Subject: [PATCH 08/10] Add tests for validate_path sanitizer --- tests/test_app_smoke.py | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/test_app_smoke.py b/tests/test_app_smoke.py index 79f7591cf..a77696bfc 100644 --- a/tests/test_app_smoke.py +++ b/tests/test_app_smoke.py @@ -11,6 +11,62 @@ API_DIR = PROJECT_ROOT / "API" +class ValidatePathTests(unittest.TestCase): + """Tests for Config.validate_path — the CodeQL sanitizer barrier. + + This function is declared as a sanitizer in .github/codeql/extensions/. + If its behaviour changes, both this test and that model file must be updated. + """ + + @classmethod + def setUpClass(cls): + sys.path.insert(0, str(API_DIR)) + from Classes.Base import Config + cls.Config = Config + + def setUp(self): + self.base = os.path.realpath(tempfile.mkdtemp()) + + def tearDown(self): + import shutil + shutil.rmtree(self.base, ignore_errors=True) + + def test_valid_path_returns_absolute(self): + result = self.Config.validate_path(self.base, "casename") + self.assertTrue(os.path.isabs(result)) + self.assertTrue(result.startswith(self.base)) + + def test_traversal_dotdot_is_blocked(self): + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, "../outside") + + def test_traversal_absolute_path_is_blocked(self): + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, "/etc/passwd") + + def test_traversal_encoded_dotdot_is_blocked(self): + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, "case/../../outside") + + def test_null_byte_is_blocked(self): + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, "case\x00evil") + + def test_none_input_is_blocked(self): + # None resolves to the base dir itself, which is rejected + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, None) + + def test_base_dir_itself_is_blocked(self): + # Pointing exactly at the base is not a valid case path + with self.assertRaises(PermissionError): + self.Config.validate_path(self.base, "") + + def test_nested_path_is_allowed(self): + result = self.Config.validate_path(self.base, os.path.join("case", "res", "run1")) + self.assertTrue(result.startswith(self.base)) + + class AppSmokeTests(unittest.TestCase): @classmethod def setUpClass(cls): From 0588b56cfe6be549c62aead12b82e7daf516cf28 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 22:53:21 -0400 Subject: [PATCH 09/10] Try both module paths for CodeQL sanitizer model --- .../muiogo-model-pack/models/config-sanitizer.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml b/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml index 587bbb889..934f45163 100644 --- a/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml +++ b/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml @@ -8,4 +8,10 @@ extensions: # absolute path. CodeQL does not recognise this as a built-in sanitizer, # so we declare it here. Any change to validate_path's return-value # semantics must be reflected in this file. + # + # Two module paths are listed because CodeQL may resolve the local module + # relative to the repo root (API.Classes.Base.Config) or relative to the + # API/ source root (Classes.Base.Config) depending on how it infers + # PYTHONPATH. Only one will match; the other is a no-op. - ["Classes.Base.Config", "Member[validate_path].ReturnValue", "path-injection"] + - ["API.Classes.Base.Config", "Member[validate_path].ReturnValue", "path-injection"] From 48aa8b13884b1ce0cff3be1fa62647ca0bd4e515 Mon Sep 17 00:00:00 2001 From: SeaCelo Date: Tue, 7 Apr 2026 22:59:18 -0400 Subject: [PATCH 10/10] Remove CodeQL model pack (barrierModel does not resolve local modules) --- .../muiogo-model-pack/codeql-pack.yml | 7 ------- .../models/config-sanitizer.yml | 17 ----------------- API/Classes/Base/Config.py | 2 -- 3 files changed, 26 deletions(-) delete mode 100644 .github/codeql/extensions/muiogo-model-pack/codeql-pack.yml delete mode 100644 .github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml diff --git a/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml b/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml deleted file mode 100644 index 09ed7b555..000000000 --- a/.github/codeql/extensions/muiogo-model-pack/codeql-pack.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: muiogo/python-model-pack -version: 0.0.1 -library: true -extensionTargets: - codeql/python-all: "*" -dataExtensions: - - models/**/*.yml diff --git a/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml b/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml deleted file mode 100644 index 934f45163..000000000 --- a/.github/codeql/extensions/muiogo-model-pack/models/config-sanitizer.yml +++ /dev/null @@ -1,17 +0,0 @@ -extensions: - - addsTo: - pack: codeql/python-all - extensible: barrierModel - data: - # validate_path checks that the resolved path stays inside DATA_STORAGE, - # raising PermissionError on traversal. Its return value is the safe - # absolute path. CodeQL does not recognise this as a built-in sanitizer, - # so we declare it here. Any change to validate_path's return-value - # semantics must be reflected in this file. - # - # Two module paths are listed because CodeQL may resolve the local module - # relative to the repo root (API.Classes.Base.Config) or relative to the - # API/ source root (Classes.Base.Config) depending on how it infers - # PYTHONPATH. Only one will match; the other is a no-op. - - ["Classes.Base.Config", "Member[validate_path].ReturnValue", "path-injection"] - - ["API.Classes.Base.Config", "Member[validate_path].ReturnValue", "path-injection"] diff --git a/API/Classes/Base/Config.py b/API/Classes/Base/Config.py index 557fa7131..c5b254045 100644 --- a/API/Classes/Base/Config.py +++ b/API/Classes/Base/Config.py @@ -5,8 +5,6 @@ import platform # Central path validation utility (prevents path traversal). -# Declared as a CodeQL sanitizer barrier in .github/codeql/extensions/. -# If the return-value semantics of this function change, update that file too. def validate_path(base_dir, user_input): base_raw = os.fspath(base_dir) user_raw = "" if user_input is None else os.fspath(user_input)