psr-factory 5.0.0b50__py3-none-win_amd64.whl → 5.0.0b52__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of psr-factory might be problematic. Click here for more details.

psr/execqueue/client.py CHANGED
@@ -28,6 +28,18 @@ def upload_case_file(zip_path, server_url):
28
28
  print("Upload failed:", response.text)
29
29
  return None
30
30
 
31
+ def run_module(case_id: str, module_name: str, server_url: str) -> Optional[str]:
32
+ """Add a module to the execution queue. Returns the execution id."""
33
+ data = {"case_id": case_id, "module_name": module_name}
34
+ response = requests.post(f"{server_url}/run_module",data=data)
35
+
36
+ if response.status_code == 200:
37
+ print("Added to execution queue successfully!")
38
+ print("Execution ID:", response.json().get('execution_id'))
39
+ return response.json().get('execution_id')
40
+ else:
41
+ print("Module enqueue failed:", response.status_code, response.text)
42
+ return None
31
43
 
32
44
  def run_case(case_id: str, server_url: str, cloud_execution: bool = False) -> Optional[str]:
33
45
  """Add a case to the execution queue. For server-local run,
@@ -45,7 +57,20 @@ def run_case(case_id: str, server_url: str, cloud_execution: bool = False) -> Op
45
57
  print("Cloud upload ID:", response.json().get('cloud_upload_id'))
46
58
  return response.json().get('cloud_upload_id')
47
59
  else:
48
- print("Upload failed:", response.text)
60
+ print("Run case failed:", response.status_code, response.text)
61
+ return None
62
+
63
+
64
+ def get_module_log(case_id: str, server_url: str, module_name: Optional[str] = None) -> Optional[str]:
65
+ """Fetch the content of a module's fixed log file. If module_name is None, returns last module run log."""
66
+ params = {}
67
+ if module_name:
68
+ params['module'] = module_name
69
+ response = requests.get(f"{server_url}/module_log/{case_id}", params=params)
70
+ if response.status_code == 200:
71
+ return response.text
72
+ else:
73
+ print("Fetch module log failed:", response.text)
49
74
  return None
50
75
 
51
76
 
psr/execqueue/config.py CHANGED
@@ -43,3 +43,10 @@ CLOUD_RESULTS_FOLDER = os.path.join(STORAGE_PATH, 'cloud_results')
43
43
  # Where temporary extracted case files will be stored
44
44
  TEMPORARY_UPLOAD_FOLDER = os.path.join(STORAGE_PATH, 'tmp')
45
45
 
46
+ # Optional: modules configuration
47
+ # Expected format in server_settings.toml:
48
+ # [modules.<name>]
49
+ # command = "python some_script.py --case \"{case_path}\""
50
+ # log_file = "<optional fixed log file name>"
51
+ MODULES = settings.get("modules", {})
52
+
psr/execqueue/db.py CHANGED
@@ -62,7 +62,10 @@ class LocalExecution(Base):
62
62
  case_id = Column(String(26), ForeignKey('cases.case_id'))
63
63
  start_time = Column(TIMESTAMP, default=datetime.datetime.utcnow)
64
64
  finish_time = Column(TIMESTAMP)
65
- status = Column(Integer, default=CloudStatus.RUNNING.value)
65
+ status = Column(Integer, default=LOCAL_EXECUTION_RUNNING)
66
+ # Module-related fields
67
+ is_module = Column(Integer, default=0) # 0 = no, 1 = yes
68
+ module = Column(String(128)) # optional module name
66
69
 
67
70
  case = relationship("Case", back_populates="local_executions")
68
71
 
@@ -103,6 +106,23 @@ def initialize():
103
106
  if _create_db:
104
107
  Base.metadata.create_all(engine)
105
108
  _first_time_setup(session)
109
+ else:
110
+ # Basic migration: add columns if missing
111
+ # Note: SQLite supports limited ALTERs; use simple try/except for idempotency
112
+ from sqlalchemy import inspect
113
+ inspector = inspect(engine)
114
+ cols = {c['name'] for c in inspector.get_columns('local_executions')}
115
+ with engine.connect() as conn:
116
+ if 'is_module' not in cols:
117
+ try:
118
+ conn.execute("ALTER TABLE local_executions ADD COLUMN is_module INTEGER DEFAULT 0")
119
+ except Exception:
120
+ pass
121
+ if 'module' not in cols:
122
+ try:
123
+ conn.execute("ALTER TABLE local_executions ADD COLUMN module VARCHAR(128)")
124
+ except Exception:
125
+ pass
106
126
 
107
127
  return session, engine
108
128
 
@@ -127,12 +147,15 @@ def register_case(session, case_id, checksum):
127
147
  return case
128
148
 
129
149
 
130
- def register_local_execution(session, case_id: str, execution_id: str):
150
+ def register_local_execution(session, case_id: str, execution_id: str, *, is_module: int = 0, module: Optional[str] = None):
131
151
  case = session.query(Case).filter(Case.case_id == case_id).first()
132
- local_execution = LocalExecution(execution_id=execution_id,
133
- case_id=case_id,
134
- start_time=datetime.datetime.utcnow()
135
- )
152
+ local_execution = LocalExecution(
153
+ execution_id=execution_id,
154
+ case_id=case_id,
155
+ start_time=datetime.datetime.utcnow(),
156
+ is_module=is_module,
157
+ module=module,
158
+ )
136
159
  case.local_executions.append(local_execution)
137
160
  session.commit()
138
161
  return local_execution
@@ -168,6 +191,38 @@ def get_local_execution_status(session, execution_id: str) -> Optional[int]:
168
191
  local_execution = session.query(LocalExecution).filter(LocalExecution.execution_id == execution_id).first()
169
192
  return local_execution.status if local_execution else None
170
193
 
194
+ def any_running_modules_for_case(session, case_id: str) -> bool:
195
+ return session.query(LocalExecution).filter(
196
+ LocalExecution.case_id == case_id,
197
+ LocalExecution.is_module == 1,
198
+ LocalExecution.status == LOCAL_EXECUTION_RUNNING
199
+ ).count() > 0
200
+
201
+ def any_failed_modules_for_case(session, case_id: str) -> bool:
202
+ return session.query(LocalExecution).filter(
203
+ LocalExecution.case_id == case_id,
204
+ LocalExecution.is_module == 1,
205
+ LocalExecution.status == LOCAL_EXECUTION_ERROR
206
+ ).count() > 0
207
+
208
+ def last_module_execution_for_case(session, case_id: str, module: Optional[str] = None) -> Optional[LocalExecution]:
209
+ q = session.query(LocalExecution).filter(
210
+ LocalExecution.case_id == case_id,
211
+ LocalExecution.is_module == 1
212
+ )
213
+ if module:
214
+ q = q.filter(LocalExecution.module == module)
215
+ return q.order_by(LocalExecution.start_time.desc()).first()
216
+
217
+ def get_distinct_module_names_for_case(session, case_id: str) -> List[str]:
218
+ rows = session.query(LocalExecution.module).filter(
219
+ LocalExecution.case_id == case_id,
220
+ LocalExecution.is_module == 1,
221
+ LocalExecution.module.isnot(None)
222
+ ).distinct().all()
223
+ # rows is a list of tuples [(module,), ...]
224
+ return [r[0] for r in rows if r and r[0]]
225
+
171
226
 
172
227
  def register_cloud_upload(session, case_id: str, cloud_upload_id: str):
173
228
  case = session.query(Case).filter(Case.case_id == case_id).first()
psr/execqueue/server.py CHANGED
@@ -1,10 +1,13 @@
1
1
  import hashlib
2
+ import os
2
3
  import queue
3
4
  import shutil
4
5
  import sys
5
6
  import threading
6
7
  import time
7
8
  import zipfile
9
+ import subprocess
10
+ import shlex
8
11
  from dotenv import load_dotenv
9
12
  from flask import (
10
13
  Flask,
@@ -64,6 +67,104 @@ def run_local_case(execution_id: str, case_path: str):
64
67
  db.update_local_execution_status(session, execution_id, status)
65
68
 
66
69
 
70
+ def _ensure_case_workdir(case_id: str) -> str:
71
+ """Ensure a working directory exists at uploads/<case_id> with extracted contents.
72
+ If it does not exist or is empty, extract the uploaded zip there.
73
+ Returns the absolute path to the working directory.
74
+ """
75
+ workdir = os.path.join(UPLOADS_FOLDER, case_id)
76
+ zip_upload_path = os.path.join(UPLOADS_FOLDER, f"{case_id}.zip")
77
+ os.makedirs(workdir, exist_ok=True)
78
+
79
+ # If directory is empty or looks incomplete, (re)extract
80
+ try:
81
+ if not os.listdir(workdir):
82
+ with zipfile.ZipFile(zip_upload_path, 'r') as zip_ref:
83
+ zip_ref.extractall(workdir)
84
+ except FileNotFoundError:
85
+ # If there's no zip, still return folder (may be pre-populated)
86
+ pass
87
+ return workdir
88
+
89
+
90
+ def run_local_module(execution_id: str, case_id: str, module_name: str) -> int:
91
+ """Run a configured module locally inside the case's upload workdir.
92
+ Returns process return code (0=success, non-zero=failure).
93
+ Updates LocalExecution status accordingly.
94
+ """
95
+ global session
96
+ # Fetch module configuration
97
+ module_cfg = MODULES.get(module_name) if isinstance(MODULES, dict) else None
98
+ if not module_cfg or 'command' not in module_cfg:
99
+ print(f"Module '{module_name}' not configured.")
100
+ db.update_local_execution_status(session, execution_id, db.LOCAL_EXECUTION_ERROR)
101
+ return 1
102
+
103
+ workdir = _ensure_case_workdir(case_id)
104
+
105
+ # Build command and log file path
106
+ cmd_tmpl = module_cfg.get('command')
107
+ # Allow placeholders
108
+ cmd = cmd_tmpl.format(case_path=workdir, case_id=case_id, module=module_name)
109
+ log_name = module_cfg.get('log_file', f"module_{module_name}.log")
110
+ log_path = os.path.join(workdir, log_name)
111
+
112
+ print(f"Running module '{module_name}' for case {case_id} in {workdir}")
113
+ print(f"Command: {cmd}")
114
+
115
+ rc = 1
116
+ try:
117
+ # Prefer to run without shell to avoid platform-specific exit code mappings
118
+ # If the command starts with 'python' or references .py, build argv accordingly
119
+ argv = None
120
+ # Heuristic: if command contains .py, run with current Python executable
121
+ if '.py' in cmd:
122
+ parts = shlex.split(cmd)
123
+ # If the command already starts with python, use as-is; else prepend sys.executable
124
+ if parts[0].endswith('python') or parts[0].endswith('python.exe'):
125
+ argv = parts
126
+ else:
127
+ argv = [sys.executable] + parts
128
+ else:
129
+ argv = shlex.split(cmd)
130
+
131
+ with open(log_path, 'a', encoding='utf-8', errors='ignore') as logf:
132
+ proc = subprocess.Popen(
133
+ argv,
134
+ cwd=workdir,
135
+ stdout=logf,
136
+ stderr=logf,
137
+ )
138
+ rc = proc.wait()
139
+
140
+ # Now rc follows the subprocess return code semantics: 0 success, non-zero failure
141
+ status = db.LOCAL_EXECUTION_FINISHED if rc == 0 else db.LOCAL_EXECUTION_ERROR
142
+ db.update_local_execution_status(session, execution_id, status)
143
+ except Exception as e:
144
+ print(f"Error running module {module_name} for case {case_id}: {e}")
145
+ db.update_local_execution_status(session, execution_id, db.LOCAL_EXECUTION_ERROR)
146
+ rc = 1
147
+ return rc
148
+
149
+
150
+ def _copy_tree(src: str, dst: str):
151
+ os.makedirs(dst, exist_ok=True)
152
+ for root, dirs, files in os.walk(src):
153
+ rel = os.path.relpath(root, src)
154
+ target_root = os.path.join(dst, rel) if rel != '.' else dst
155
+ os.makedirs(target_root, exist_ok=True)
156
+ for d in dirs:
157
+ os.makedirs(os.path.join(target_root, d), exist_ok=True)
158
+ for f in files:
159
+ s = os.path.join(root, f)
160
+ t = os.path.join(target_root, f)
161
+ try:
162
+ shutil.copy2(s, t)
163
+ except Exception:
164
+ # Best-effort copy; skip problematic files
165
+ pass
166
+
167
+
67
168
  def initialize_db():
68
169
  session, engine = db.initialize()
69
170
  return session
@@ -91,22 +192,59 @@ def run_cloud_case(execution_id: str, case_path: str):
91
192
  def process_local_execution_queue():
92
193
  global session
93
194
  while True:
94
- execution_id, case_id = _execution_queue.get()
195
+ item = _execution_queue.get()
95
196
  try:
96
- print(f"Processing {execution_id}...")
97
- # Unzip the file
98
- execution_extraction_path = os.path.join(LOCAL_RESULTS_FOLDER, execution_id)
99
- os.makedirs(execution_extraction_path, exist_ok=True)
100
-
101
- zip_upload_path = os.path.join(UPLOADS_FOLDER, case_id + ".zip")
102
-
103
- with zipfile.ZipFile(zip_upload_path, 'r') as zip_ref:
104
- zip_ref.extractall(execution_extraction_path)
105
- # Run SDDP
106
- run_local_case(execution_id, execution_extraction_path)
197
+ # Detect item type (backward compatibility for tuple)
198
+ if isinstance(item, dict) and item.get('type') == 'module':
199
+ execution_id = item['execution_id']
200
+ case_id = item['case_id']
201
+ module_name = item['module']
202
+ print(f"Processing module {module_name} for case {case_id} (exec {execution_id})...")
203
+ run_local_module(execution_id, case_id, module_name)
204
+ else:
205
+ if isinstance(item, (list, tuple)):
206
+ execution_id, case_id = item
207
+ else:
208
+ execution_id = item.get('execution_id')
209
+ case_id = item.get('case_id')
210
+
211
+ print(f"Processing case execution {execution_id} for case {case_id}...")
212
+
213
+ # Wait for running modules to finish; abort if any failed
214
+ wait_loops = 0
215
+ while db.any_running_modules_for_case(session, case_id):
216
+ print(f"Case {case_id} has running modules; waiting...")
217
+ time.sleep(5)
218
+ wait_loops += 1
219
+ # Safety: avoid infinite wait in worker
220
+ if wait_loops > 240: # ~20 minutes
221
+ break
222
+
223
+ # Check last execution per distinct module: if any last module execution failed, mark error
224
+ failing_modules = []
225
+ for mname in db.get_distinct_module_names_for_case(session, case_id):
226
+ last = db.last_module_execution_for_case(session, case_id, mname)
227
+ if last and last.status == db.LOCAL_EXECUTION_ERROR:
228
+ failing_modules.append(mname)
229
+
230
+ if failing_modules:
231
+ print(f"Case {case_id} has failed modules {failing_modules}; marking local execution {execution_id} as error and skipping run")
232
+ db.update_local_execution_status(session, execution_id, db.LOCAL_EXECUTION_ERROR)
233
+ else:
234
+ # Prepare a dedicated results folder copying the current working directory (with module changes)
235
+ execution_extraction_path = os.path.join(LOCAL_RESULTS_FOLDER, execution_id)
236
+ os.makedirs(execution_extraction_path, exist_ok=True)
237
+ workdir = _ensure_case_workdir(case_id)
238
+ _copy_tree(workdir, execution_extraction_path)
239
+ # Run SDDP
240
+ run_local_case(execution_id, execution_extraction_path)
107
241
 
108
242
  except Exception as e:
109
- print(f"Error processing {execution_id}: {e}")
243
+ # Use safe prints in case execution_id isn't available
244
+ try:
245
+ print(f"Error processing {execution_id}: {e}")
246
+ except Exception:
247
+ print(f"Error processing item: {e}")
110
248
  finally:
111
249
  _execution_queue.task_done()
112
250
 
@@ -121,13 +259,31 @@ def process_cloud_execution_queue():
121
259
  cloud_upload_id, case_id = _cloud_upload_queue.get()
122
260
  try:
123
261
  print(f"Processing {cloud_upload_id}...")
124
- # Unzip the file
125
- zip_upload_path = os.path.join(UPLOADS_FOLDER, case_id + ".zip")
262
+ # Wait for running modules to finish; abort if any failed
263
+ wait_loops = 0
264
+ while db.any_running_modules_for_case(session, case_id):
265
+ print(f"Case {case_id} has running modules; waiting before cloud run...")
266
+ time.sleep(5)
267
+ wait_loops += 1
268
+ if wait_loops > 240: # ~20 minutes
269
+ break
270
+ # Block if the last execution of any distinct module failed
271
+ failing_modules = []
272
+ for mname in db.get_distinct_module_names_for_case(session, case_id):
273
+ last = db.last_module_execution_for_case(session, case_id, mname)
274
+ if last and last.status == db.LOCAL_EXECUTION_ERROR:
275
+ failing_modules.append(mname)
276
+ if failing_modules:
277
+ print(f"Case {case_id} has failing modules in last execution {failing_modules}; skipping cloud run for upload {cloud_upload_id}")
278
+ # Nothing else to do; do not run in the cloud
279
+ continue
280
+ # Prepare temp folder by copying current working directory (with module changes)
126
281
  tmp_extraction_path = os.path.join(TEMPORARY_UPLOAD_FOLDER, cloud_upload_id)
127
-
282
+ workdir = _ensure_case_workdir(case_id)
283
+ if os.path.isdir(tmp_extraction_path):
284
+ shutil.rmtree(tmp_extraction_path, ignore_errors=True)
128
285
  os.makedirs(tmp_extraction_path, exist_ok=True)
129
- with zipfile.ZipFile(zip_upload_path, 'r') as zip_ref:
130
- zip_ref.extractall(tmp_extraction_path)
286
+ _copy_tree(workdir, tmp_extraction_path)
131
287
 
132
288
  # Run SDDP
133
289
  repository_id = run_cloud_case(cloud_upload_id, tmp_extraction_path)
@@ -136,9 +292,10 @@ def process_cloud_execution_queue():
136
292
  shutil.rmtree(tmp_extraction_path)
137
293
 
138
294
  execution_extraction_path = os.path.join(CLOUD_RESULTS_FOLDER, repository_id)
295
+ if os.path.isdir(execution_extraction_path):
296
+ shutil.rmtree(execution_extraction_path, ignore_errors=True)
139
297
  os.makedirs(execution_extraction_path, exist_ok=True)
140
- with zipfile.ZipFile(zip_upload_path, 'r') as zip_ref:
141
- zip_ref.extractall(execution_extraction_path)
298
+ _copy_tree(workdir, execution_extraction_path)
142
299
 
143
300
  db.register_cloud_execution(session, repository_id, cloud_upload_id, case_id)
144
301
 
@@ -234,6 +391,15 @@ def run_endpoint():
234
391
  if not os.path.exists(zip_case_path):
235
392
  return jsonify({'error': 'Upload file for this case ID not found'}), 404
236
393
 
394
+ # Pre-check: for each distinct module, if the last execution failed, block the run
395
+ failing_modules = []
396
+ for mname in db.get_distinct_module_names_for_case(session, case_id):
397
+ last = db.last_module_execution_for_case(session, case_id, mname)
398
+ if last and last.status == db.LOCAL_EXECUTION_ERROR:
399
+ failing_modules.append(mname)
400
+ if failing_modules:
401
+ return jsonify({'error': 'Case has failed modules in last execution', 'modules': failing_modules}), 409
402
+
237
403
  if cloud_execution:
238
404
  cloud_upload_id = str(ulid.ULID())
239
405
  _cloud_upload_queue.put((cloud_upload_id, case_id))
@@ -246,10 +412,40 @@ def run_endpoint():
246
412
  _execution_queue.put((execution_id, case_id))
247
413
 
248
414
  db.register_local_execution(session, case_id, execution_id)
415
+ # Mark as running explicitly
416
+ db.update_local_execution_status(session, execution_id, db.LOCAL_EXECUTION_RUNNING)
249
417
 
250
418
  return jsonify({'case_id': case_id, 'execution_id': execution_id}), 200
251
419
 
252
420
 
421
+ @app.route('/run_module', methods=['POST'])
422
+ def run_module_endpoint():
423
+ global session
424
+ case_id = request.form.get('case_id')
425
+ module_name = request.form.get('module') or request.form.get('module_name')
426
+
427
+ if not case_id or not module_name:
428
+ return jsonify({'error': 'case_id and module are required'}), 400
429
+
430
+ # Validate case zip exists
431
+ zip_case_path = os.path.join(UPLOADS_FOLDER, f"{case_id}.zip")
432
+ workdir = os.path.join(UPLOADS_FOLDER, case_id)
433
+ if not os.path.exists(zip_case_path) and not os.path.isdir(workdir):
434
+ return jsonify({'error': 'Upload file or working directory for this case ID not found'}), 404
435
+
436
+ # Validate module exists in config
437
+ module_cfg = MODULES.get(module_name) if isinstance(MODULES, dict) else None
438
+ if not module_cfg or 'command' not in module_cfg:
439
+ return jsonify({'error': f"Module '{module_name}' not configured"}), 400
440
+
441
+ execution_id = str(ulid.ULID())
442
+ _execution_queue.put({'type': 'module', 'execution_id': execution_id, 'case_id': case_id, 'module': module_name})
443
+ db.register_local_execution(session, case_id, execution_id, is_module=1, module=module_name)
444
+ db.update_local_execution_status(session, execution_id, db.LOCAL_EXECUTION_RUNNING)
445
+
446
+ return jsonify({'case_id': case_id, 'module': module_name, 'execution_id': execution_id}), 200
447
+
448
+
253
449
  @app.route('/upload_and_run', methods=['POST'])
254
450
  def upload_and_run_file():
255
451
  global session
@@ -371,6 +567,41 @@ def get_results(execution_id: str):
371
567
  return jsonify({'execution_id': execution_id, 'files': result_files}), 200
372
568
 
373
569
 
570
+ @app.route('/module_log/<case_id>', methods=['GET'])
571
+ def get_module_log(case_id: str):
572
+ """Return the content of the module's fixed log file for the last module run of the case,
573
+ or for a specific module if provided as query parameter ?module=<name>.
574
+ """
575
+ global session
576
+ module_name = request.args.get('module') or request.args.get('module_name')
577
+
578
+ # Determine module and log file name
579
+ if not module_name:
580
+ last = db.last_module_execution_for_case(session, case_id)
581
+ if not last or not last.module:
582
+ return jsonify({'error': 'No module execution found for this case'}), 404
583
+ module_name = last.module
584
+ module_cfg = MODULES.get(module_name) if isinstance(MODULES, dict) else None
585
+ if not module_cfg:
586
+ return jsonify({'error': f"Module '{module_name}' not configured"}), 400
587
+ log_name = module_cfg.get('log_file', f"module_{module_name}.log")
588
+
589
+ workdir = os.path.join(UPLOADS_FOLDER, case_id)
590
+ if not os.path.isdir(workdir):
591
+ # Ensure workdir is created (may extract zip if needed)
592
+ workdir = _ensure_case_workdir(case_id)
593
+ log_path = os.path.join(workdir, log_name)
594
+ if not os.path.exists(log_path):
595
+ return jsonify({'error': 'Log file not found', 'module': module_name, 'log': log_name}), 404
596
+
597
+ try:
598
+ with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
599
+ content = f.read()
600
+ return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
601
+ except Exception as e:
602
+ return jsonify({'error': str(e)}), 500
603
+
604
+
374
605
  @app.route('/results/<execution_id>/<file>', methods=['GET'])
375
606
  def download_file(execution_id: str, file):
376
607
  global session
psr/factory/__init__.py CHANGED
@@ -2,6 +2,6 @@
2
2
  # Unauthorized copying of this file, via any medium is strictly prohibited
3
3
  # Proprietary and confidential
4
4
 
5
- __version__ = "5.0.0b50"
5
+ __version__ = "5.0.0b52"
6
6
 
7
7
  from .api import *
psr/factory/factory.dll CHANGED
Binary file
psr/factory/factory.pmd CHANGED
@@ -5065,7 +5065,6 @@ DEFINE_MODEL MODL:SDDP_ConsumoCombustivel
5065
5065
  DIMENSION block
5066
5066
  DIMENSION segment
5067
5067
  VETOR DATE Data
5068
- VETOR REAL GerMax INDEX Data
5069
5068
  VETOR REAL O&MCost INDEX Data
5070
5069
  VETOR REAL G DIM(segment) INDEX Data
5071
5070
  VETOR REAL CTransp INDEX Data
@@ -5681,6 +5680,7 @@ DEFINE_MODEL MODL:SDDP_Execution_Options
5681
5680
  PARM INTEGER GAME
5682
5681
  PARM INTEGER TRUP
5683
5682
  PARM INTEGER HINS
5683
+ PARM INTEGER HOTM
5684
5684
 
5685
5685
  PARM REAL NCPL_MIPR DEFAULT 0.005
5686
5686
  PARM REAL NCPL_LTOL
Binary file
psr/runner/runner.py CHANGED
@@ -595,6 +595,20 @@ def run_tslconsole(tsl_path: Union[str, pathlib.Path], script_path: Union[str, p
595
595
  cmd = f'TimeSeriesConsole.exe "{str(script_path)}"'
596
596
  exec_cmd(cmd, **kwargs)
597
597
 
598
+ def run_tsl_generate_inflow_from_external_natural(case_path: Union[str, pathlib.Path], tsl_path: Union[str, pathlib.Path], **kwargs):
599
+ commands = ["generate_inflow_from_external_natural"]
600
+ case_path = os.path.abspath(str(case_path))
601
+ tsl_path = str(tsl_path)
602
+ _run_tslconsole_command(tsl_path, case_path, commands)
603
+
604
+
605
+ def run_tsl_generate_inflow_from_external_incremental(case_path: Union[str, pathlib.Path], tsl_path: Union[str, pathlib.Path], **kwargs):
606
+ commands = ["generate_inflow_from_external_incremental"]
607
+ case_path = os.path.abspath(str(case_path))
608
+ tsl_path = str(tsl_path)
609
+ _run_tslconsole_command(tsl_path, case_path, commands)
610
+
611
+
598
612
  def run_tsl(case_path: Union[str, pathlib.Path], tsl_path: Union[str, pathlib.Path], base_type: str, **kwargs):
599
613
  if os.name != 'nt':
600
614
  raise NotImplementedError("Running TimeSeriesLab is only available on Windows")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psr-factory
3
- Version: 5.0.0b50
3
+ Version: 5.0.0b52
4
4
  Summary: PSR database management module.
5
5
  Author-email: "PSR Inc." <psrfactory@psr-inc.com>
6
6
  License-Expression: MIT
@@ -11,18 +11,18 @@ psr/cloud/status.py,sha256=vcI4B9S6wCt9maT5NNrVwYaEgGIvy6kkC1UVpJjYbtw,3607
11
11
  psr/cloud/tempfile.py,sha256=1IOeye0eKWnmBynK5K5FMWiTaEVhn4GbQ8_y0THEva0,3893
12
12
  psr/cloud/version.py,sha256=-oC5DNaS_iYTfpsusfmeG-WGktZ5b-x8xT-4zszWvz8,193
13
13
  psr/cloud/xml.py,sha256=ac2lyflOQm8khPvJn0zmI26I4sfUDY6A_OTsxzbMQEs,1896
14
- psr/execqueue/client.py,sha256=P89Yt76W2GqRXaG_MLsa0kXf0jPp-weBd3aSTRcDzcs,4443
15
- psr/execqueue/config.py,sha256=rUOzO5dtTkwWoZlZfk06K9RE94xCx53T1bJ1h5JaDUo,1446
16
- psr/execqueue/db.py,sha256=sNr_StNEgZZQCKcyCWiB1WrQJIhE9UvLUxPA2tWiXGs,8498
17
- psr/execqueue/server.py,sha256=LolYERWRt96P_ip4yKU7DsN7M_n9d_pbflbT0ckUV0E,15782
14
+ psr/execqueue/client.py,sha256=xw08nwLEsfEXe5TvTRonWDWnhfvn1PW1CwIpVZzro2o,5617
15
+ psr/execqueue/config.py,sha256=F8sp-JGeoRspQRR63SjSKV5wDz0OVGnA-cNm1UDYHBY,1693
16
+ psr/execqueue/db.py,sha256=UvGjex6DpnVrfTtq1z78ZKS4tHkzp08cn8wdrImYRaM,10796
17
+ psr/execqueue/server.py,sha256=aeNzdZabXR5MSE0KDPxTWzr78PHklUVpvzDQRFNus0I,26872
18
18
  psr/execqueue/watcher.py,sha256=R1dyXJ-OYn_QjqdItBwbLJZQ2LcbtdHqnRaYkyphi4w,5637
19
- psr/factory/__init__.py,sha256=-L7Nsdfu8QUH_LuI-u2r0qi7QF9BmNSVxKAL8XN5zNo,219
19
+ psr/factory/__init__.py,sha256=akpsaiZNbTA_pC27H6hXlTPZ_E7kYLlYlWbF5pbVPpQ,219
20
20
  psr/factory/api.py,sha256=tezioW1Dqv6m7Ozvc0AOTpZcb1q7rLsICqB58twEG5c,106100
21
- psr/factory/factory.dll,sha256=skIvvA5KlKhUOZp1GA8CwStyGyHsBFJ7kCdhzNpbXsM,18727248
22
- psr/factory/factory.pmd,sha256=b9yPyDmkfwMC1BVnLwcM8UqV3jJTuMkfhWdMFwbFlEc,252219
21
+ psr/factory/factory.dll,sha256=wOq4KWgelawH1G3pd64Y3WQBOfH0mhEknfm-d0zEshg,18719744
22
+ psr/factory/factory.pmd,sha256=frnwgnN0daxVlKdJTiILYTVH1m-2VXVhDGxGZWo41C0,252201
23
23
  psr/factory/factory.pmk,sha256=gM05-6muZmJJhrceHhlDIIaMMM6FJe6y1ERaHaWJQJc,601130
24
24
  psr/factory/factorylib.py,sha256=HCjHezBrzf2hTrHpszhXOREsQ022I0CZvPjigYFxWeE,28799
25
- psr/factory/libcurl-x64.dll,sha256=bWNvOzKyZjLmn6tMZ_Om6NCwYLKkj0WB__-cNHQmW6Q,5317968
25
+ psr/factory/libcurl-x64.dll,sha256=6WGBmqX4q_eD8Vc0E2VpCvVrFV3W7TQoaKqSdbhXBu0,5313096
26
26
  psr/factory/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  psr/factory/samples/__init__.py,sha256=xxOch5Fokzjy571a6OHD87FWM17qKgvfcbr8xn-n36I,80
28
28
  psr/factory/samples/sddp_case01.py,sha256=h8MDJ6ajXMH_ngiUFSCVFG3XCKDRys6y78vFYyfCMmE,5038
@@ -34,10 +34,10 @@ psr/psrfcommon/__init__.py,sha256=WXR560XQllIjtFpWd0jiJEbUAQIyh5-6lwj-42_J95c,20
34
34
  psr/psrfcommon/psrfcommon.py,sha256=NABM5ahvyfSizDC9c0Vu9dVK1pD_vOzIGFHL1oz2E1o,1464
35
35
  psr/psrfcommon/tempfile.py,sha256=5S13wa2DCLYTUdwbLm_KMBRnDRJ0WDlu8GO2BmZoNdg,3939
36
36
  psr/runner/__init__.py,sha256=kI9HDX-B_LMQJUHHylFHas2rNpWfNNa0pZXoIvX_Alw,230
37
- psr/runner/runner.py,sha256=hCVH62HAZK_M9YUiHQgqCkMevN17utegjfRIw49MdvM,27542
37
+ psr/runner/runner.py,sha256=qs7Gabo531-GWxgU1MneJYCiio8A2QXUYmct29b7e18,28222
38
38
  psr/runner/version.py,sha256=mch2Y8anSXGMn9w72Z78PhSRhOyn55EwaoLAYhY4McE,194
39
- psr_factory-5.0.0b50.dist-info/licenses/LICENSE.txt,sha256=N6mqZK2Ft3iXGHj-by_MHC_dJo9qwn0URjakEPys3H4,1089
40
- psr_factory-5.0.0b50.dist-info/METADATA,sha256=KBExsLitxgZIySxowtsO3jvGcKf417CsSja0rTd94-M,3486
41
- psr_factory-5.0.0b50.dist-info/WHEEL,sha256=ZjXRCNaQ9YSypEK2TE0LRB0sy2OVXSszb4Sx1XjM99k,97
42
- psr_factory-5.0.0b50.dist-info/top_level.txt,sha256=Jb393O96WQk3b5D1gMcrZBLKJJgZpzNjTPoldUi00ck,4
43
- psr_factory-5.0.0b50.dist-info/RECORD,,
39
+ psr_factory-5.0.0b52.dist-info/licenses/LICENSE.txt,sha256=N6mqZK2Ft3iXGHj-by_MHC_dJo9qwn0URjakEPys3H4,1089
40
+ psr_factory-5.0.0b52.dist-info/METADATA,sha256=rdCDq-KIHR1eTccNUCRllq5xSDlqzLWqEyyqV6CjVZU,3486
41
+ psr_factory-5.0.0b52.dist-info/WHEEL,sha256=ZjXRCNaQ9YSypEK2TE0LRB0sy2OVXSszb4Sx1XjM99k,97
42
+ psr_factory-5.0.0b52.dist-info/top_level.txt,sha256=Jb393O96WQk3b5D1gMcrZBLKJJgZpzNjTPoldUi00ck,4
43
+ psr_factory-5.0.0b52.dist-info/RECORD,,