simstadt 0.2.2__tar.gz → 0.2.3__tar.gz

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.
Files changed (88) hide show
  1. {simstadt-0.2.2 → simstadt-0.2.3}/PKG-INFO +6 -2
  2. {simstadt-0.2.2 → simstadt-0.2.3}/README.md +5 -1
  3. {simstadt-0.2.2 → simstadt-0.2.3}/pyproject.toml +1 -1
  4. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/cli.py +6 -1
  5. simstadt-0.2.3/src/simstadt/install.py +117 -0
  6. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/base.py +30 -12
  7. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/energy_grid.py +1 -3
  8. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/solar_potential.py +2 -4
  9. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/runner.py +3 -2
  10. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/__init__.py +0 -0
  11. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/__init__.py +0 -0
  12. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/green_water.py +0 -0
  13. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/heat_demand.py +0 -0
  14. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/kpi.py +0 -0
  15. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/load_profile.py +0 -0
  16. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/photovoltaic.py +0 -0
  17. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/providers.py +0 -0
  18. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/results/unknown_workflow.py +0 -0
  19. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/00_ImportCityGml.step/params.xml +0 -0
  20. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/01_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  21. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/01_Preprocessing.step/params.xml +0 -0
  22. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/02_WeatherProcessor.step/params.xml +0 -0
  23. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/03_IrradianceProcessor.step/params.xml +0 -0
  24. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/04_Visualization.step/params.xml +0 -0
  25. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/100_Solar.flow/params.xml +0 -0
  26. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/00_ImportCityGml.step/params.xml +0 -0
  27. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/01_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  28. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/01_Preprocessing.step/01_GeometricEstimator.step/params.xml +0 -0
  29. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/01_Preprocessing.step/02_PhysicsPreprocessor.step/params.xml +0 -0
  30. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/01_Preprocessing.step/03_UsagePreprocessor.step/params.xml +0 -0
  31. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/01_Preprocessing.step/params.xml +0 -0
  32. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/02_WeatherProcessor.step/params.xml +0 -0
  33. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/03_IrradianceProcessor.step/params.xml +0 -0
  34. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/04_RefurbishmentScenarioMaker.step/params.xml +0 -0
  35. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/05_MonthlyEnergyBalance.step/params.xml +0 -0
  36. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/101_HeatDemand.flow/params.xml +0 -0
  37. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/102_GreenWater.flow/00_ImportCityGml.step/params.xml +0 -0
  38. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/102_GreenWater.flow/01_ShadowProcessing.step/params.xml +0 -0
  39. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/102_GreenWater.flow/02_WeatherProcessor.step/params.xml +0 -0
  40. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/102_GreenWater.flow/03_GreenWater.step/params.xml +0 -0
  41. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/102_GreenWater.flow/params.xml +0 -0
  42. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/.pytest_cache/.gitignore +0 -0
  43. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/.pytest_cache/CACHEDIR.TAG +0 -0
  44. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/.pytest_cache/README.md +0 -0
  45. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/.pytest_cache/v/cache/nodeids +0 -0
  46. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/.pytest_cache/v/cache/stepwise +0 -0
  47. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/00_ImportCityGml.step/params.xml +0 -0
  48. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/01_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  49. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/01_Preprocessing.step/params.xml +0 -0
  50. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/02_WeatherProcessor.step/params.xml +0 -0
  51. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/03_IrradianceProcessor.step/params.xml +0 -0
  52. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/04_PhotovoltaicEconomics.step/params.xml +0 -0
  53. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/05_PhotovoltaicPotential.step/params.xml +0 -0
  54. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/103_PV.flow/params.xml +0 -0
  55. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/00_ImportCityGml.step/params.xml +0 -0
  56. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/01_ShadowProcessing.step/params.xml +0 -0
  57. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/02_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  58. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/02_Preprocessing.step/01_GeometricEstimator.step/params.xml +0 -0
  59. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/02_Preprocessing.step/02_PhysicsPreprocessor.step/params.xml +0 -0
  60. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/02_Preprocessing.step/03_UsagePreprocessor.step/params.xml +0 -0
  61. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/02_Preprocessing.step/params.xml +0 -0
  62. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/03_WeatherProcessor.step/params.xml +0 -0
  63. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/04_IrradianceShadow.step/params.xml +0 -0
  64. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/05_RefurbishmentScenarioMaker.step/params.xml +0 -0
  65. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/06_MonthlyEnergyBalance.step/params.xml +0 -0
  66. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/104_HeatDemandWithShadow.flow/params.xml +0 -0
  67. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/00_ImportCityGml.step/params.xml +0 -0
  68. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/01_ShadowProcessing.step/params.xml +0 -0
  69. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/02_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  70. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/02_Preprocessing.step/params.xml +0 -0
  71. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/03_WeatherProcessor.step/params.xml +0 -0
  72. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/04_IrradianceShadow.step/params.xml +0 -0
  73. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/05_PhotovoltaicEconomics.step/params.xml +0 -0
  74. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/06_PhotovoltaicPotential.step/params.xml +0 -0
  75. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/105_PVWithShadow.flow/params.xml +0 -0
  76. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/00_ImportCityGml.step/params.xml +0 -0
  77. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/01_Preprocessing.step/00_GeometricPreprocessor.step/params.xml +0 -0
  78. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/01_Preprocessing.step/01_GeometricEstimator.step/params.xml +0 -0
  79. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/01_Preprocessing.step/02_PhysicsPreprocessor.step/params.xml +0 -0
  80. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/01_Preprocessing.step/03_UsagePreprocessor.step/params.xml +0 -0
  81. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/01_Preprocessing.step/params.xml +0 -0
  82. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/02_RefurbishmentScenarioMaker.step/params.xml +0 -0
  83. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/03_WeatherProcessor.step/params.xml +0 -0
  84. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/04_IrradianceProcessor.step/params.xml +0 -0
  85. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/05_MonthlyEnergyBalance.step/params.xml +0 -0
  86. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/templates/106_HeatDemandWithRefurbishment.flow/params.xml +0 -0
  87. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/utils.py +0 -0
  88. {simstadt-0.2.2 → simstadt-0.2.3}/src/simstadt/workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: simstadt
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Python library for using and testing SimStadt workflows.
5
5
  Author: Eric Duminil
6
6
  Author-email: Eric Duminil <eric.duminil@hft-stuttgart.de>
@@ -20,7 +20,8 @@ SimStadt is a city simulation tool for energy and urban analysis developed at HF
20
20
  ## Requirements
21
21
 
22
22
  - Python 3.10+
23
- - [SimStadt](https://simstadt.hft-stuttgart.de/download/InstallFiles/SimStadt2_latest.zip) installed separately
23
+ - Java 17+
24
+ - [SimStadt](https://simstadt.hft-stuttgart.de/download/InstallFiles/SimStadt2_latest.zip) installed separately (or use `simstadt --install`)
24
25
 
25
26
  ## Installation
26
27
 
@@ -73,6 +74,9 @@ If no project path is specified, workflows are run in a temporary repository und
73
74
  # Print detected SimStadt installation path and version
74
75
  simstadt
75
76
 
77
+ # Download and install the latest SimStadt release to ~/Desktop
78
+ simstadt --install
79
+
76
80
  # Launch the SimStadt GUI
77
81
  simstadt --gui
78
82
 
@@ -7,7 +7,8 @@ SimStadt is a city simulation tool for energy and urban analysis developed at HF
7
7
  ## Requirements
8
8
 
9
9
  - Python 3.10+
10
- - [SimStadt](https://simstadt.hft-stuttgart.de/download/InstallFiles/SimStadt2_latest.zip) installed separately
10
+ - Java 17+
11
+ - [SimStadt](https://simstadt.hft-stuttgart.de/download/InstallFiles/SimStadt2_latest.zip) installed separately (or use `simstadt --install`)
11
12
 
12
13
  ## Installation
13
14
 
@@ -60,6 +61,9 @@ If no project path is specified, workflows are run in a temporary repository und
60
61
  # Print detected SimStadt installation path and version
61
62
  simstadt
62
63
 
64
+ # Download and install the latest SimStadt release to ~/Desktop
65
+ simstadt --install
66
+
63
67
  # Launch the SimStadt GUI
64
68
  simstadt --gui
65
69
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simstadt"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Python library for using and testing SimStadt workflows."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -6,10 +6,10 @@ import re
6
6
  import subprocess
7
7
  import sys
8
8
  from pathlib import Path
9
+ from .install import cmd_install
9
10
  from .utils import chdir
10
11
 
11
12
 
12
-
13
13
  def cmd_info(args: argparse.Namespace) -> None:
14
14
  from .runner import get_simstadt_folder, get_simstadt_output, _simstadt_script
15
15
 
@@ -82,6 +82,7 @@ def build_parser() -> argparse.ArgumentParser:
82
82
  description="simstadt - Python library for SimStadt workflows.",
83
83
  )
84
84
  parser.add_argument("--gui", action="store_true", help="Launch the SimStadt GUI.")
85
+ parser.add_argument("--install", action="store_true", help="Download and install the latest SimStadt release to ~/Desktop.")
85
86
  parser.add_argument("template", nargs="?", help="Template name (from SIMSTADT_TEMPLATE_PATH) or path to a .flow directory.")
86
87
  parser.add_argument("citygml", nargs="?", type=Path, help="Path to the CityGML input file.")
87
88
  parser.add_argument("-d", "--description", help="Human-readable label for the result.")
@@ -96,6 +97,10 @@ def build_parser() -> argparse.ArgumentParser:
96
97
  def app() -> None:
97
98
  parser = build_parser()
98
99
  args = parser.parse_args()
100
+
101
+ if args.install:
102
+ cmd_install()
103
+
99
104
  if args.template and args.citygml:
100
105
  cmd_run(args)
101
106
  else:
@@ -0,0 +1,117 @@
1
+ """Download and install SimStadt from the official release ZIP."""
2
+
3
+ import io
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import urllib.request
10
+ import zipfile
11
+ from pathlib import Path
12
+
13
+ from .runner import SIMSTADT_VERSION_FORMAT
14
+
15
+
16
+ def _parse_simstadt_name(manifest: str) -> str:
17
+ m = re.search(f"Implementation-Version: *{SIMSTADT_VERSION_FORMAT}", manifest)
18
+ if not m:
19
+ raise ValueError("Cannot parse Implementation-Version from MANIFEST.MF")
20
+ version, branch, rev, date = m.groups()
21
+ return f"SimStadt2_{version}_{branch}_{date}_{rev}"
22
+
23
+
24
+ def _get_java_version() -> int | None:
25
+ try:
26
+ result = subprocess.run(["java", "-version"], capture_output=True, text=True)
27
+ m = re.search(r'version "(\d+)', result.stderr)
28
+ return int(m.group(1)) if m else None
29
+ except FileNotFoundError:
30
+ return None
31
+
32
+
33
+ def cmd_install() -> None:
34
+ url = "https://simstadt.hft-stuttgart.de/download/InstallFiles/SimStadt2_latest.zip"
35
+ desktop = Path.home() / "Desktop"
36
+
37
+ if not desktop.exists():
38
+ print(f"Error: {desktop} not found", file=sys.stderr)
39
+ raise SystemExit(1)
40
+
41
+ print("Downloading SimStadt...", file=sys.stderr)
42
+ tmp = None
43
+ try:
44
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as f:
45
+ tmp = Path(f.name)
46
+ try:
47
+ urllib.request.urlretrieve(url, tmp)
48
+ except Exception as e:
49
+ print(f"Error: Download failed: {e}", file=sys.stderr)
50
+ raise SystemExit(1)
51
+
52
+ try:
53
+ with zipfile.ZipFile(tmp) as zf:
54
+ jar_entry = next(
55
+ (
56
+ n
57
+ for n in zf.namelist()
58
+ if re.match(r"lib/simstadt-desktop-.*\.jar$", n)
59
+ ),
60
+ None,
61
+ )
62
+ if not jar_entry:
63
+ print(
64
+ "Error: simstadt-desktop JAR not found in ZIP", file=sys.stderr
65
+ )
66
+ raise SystemExit(1)
67
+ with zipfile.ZipFile(io.BytesIO(zf.read(jar_entry))) as jar:
68
+ if "META-INF/MANIFEST.MF" not in jar.namelist():
69
+ print("Error: MANIFEST.MF not found in JAR", file=sys.stderr)
70
+ raise SystemExit(1)
71
+ manifest = jar.read("META-INF/MANIFEST.MF").decode(errors="replace")
72
+ try:
73
+ name = _parse_simstadt_name(manifest)
74
+ except ValueError as e:
75
+ print(f"Error: {e}", file=sys.stderr)
76
+ raise SystemExit(1)
77
+ dest = desktop / name
78
+ if dest.exists():
79
+ print(f"Already installed: {name}")
80
+ return
81
+ dest.mkdir()
82
+ try:
83
+ for member in zf.infolist():
84
+ member_path = (dest / member.filename).resolve()
85
+ if not member_path.is_relative_to(dest.resolve()):
86
+ print(
87
+ f"Error: ZIP contains unsafe path: {member.filename}",
88
+ file=sys.stderr,
89
+ )
90
+ raise SystemExit(1)
91
+ zf.extractall(dest)
92
+ for member in zf.infolist():
93
+ mode = member.external_attr >> 16
94
+ if mode:
95
+ (dest / member.filename).chmod(mode)
96
+ except BaseException:
97
+ shutil.rmtree(dest, ignore_errors=True)
98
+ raise
99
+ print(f"Installed: {dest}")
100
+ except zipfile.BadZipFile as e:
101
+ print(f"Error: Invalid ZIP file: {e}", file=sys.stderr)
102
+ raise SystemExit(1)
103
+ finally:
104
+ if tmp and tmp.exists():
105
+ tmp.unlink()
106
+
107
+ java_version = _get_java_version()
108
+ if java_version is None:
109
+ print(
110
+ "Warning: Java not found. SimStadt requires Java 17. Download: https://bell-sw.com/pages/downloads/#jdk-17-lts",
111
+ file=sys.stderr,
112
+ )
113
+ elif java_version < 17:
114
+ print(
115
+ f"Warning: Java {java_version} found but SimStadt requires Java 17. Download: https://bell-sw.com/pages/downloads/#jdk-17-lts",
116
+ file=sys.stderr,
117
+ )
@@ -119,15 +119,22 @@ class SimStadtResults(ABC):
119
119
 
120
120
  @property
121
121
  def name(self) -> str:
122
- return self._params_element(".//void[@property='name']/string").text or "Unknown"
122
+ return (
123
+ self._params_element(".//void[@property='name']/string").text or "Unknown"
124
+ )
123
125
 
124
126
  @property
125
127
  def short_name(self) -> str:
126
- return self._params_element(".//void[@property='shortName']/string").text or "Unknown"
128
+ return (
129
+ self._params_element(".//void[@property='shortName']/string").text
130
+ or "Unknown"
131
+ )
127
132
 
128
133
  @property
129
134
  def workflow_provider(self) -> str:
130
- return self._params_element(".//void[@property='workflowProvider']/object").get("class", "Unknown provider")
135
+ return self._params_element(".//void[@property='workflowProvider']/object").get(
136
+ "class", "Unknown provider"
137
+ )
131
138
 
132
139
  @property
133
140
  def workflow_path(self) -> Path:
@@ -136,7 +143,10 @@ class SimStadtResults(ABC):
136
143
 
137
144
  @property
138
145
  def citygml(self) -> str:
139
- return self._params_element(".//void[@property='cityGmlFileNames']//string").text or "Unknown CityGML"
146
+ return (
147
+ self._params_element(".//void[@property='cityGmlFileNames']//string").text
148
+ or "Unknown CityGML"
149
+ )
140
150
 
141
151
  @property
142
152
  def project_path(self) -> Path:
@@ -170,13 +180,7 @@ class SimStadtResults(ABC):
170
180
 
171
181
  @property
172
182
  def csv_path(self) -> Path:
173
- csvs = self.get_all_by_extension(".csv")
174
- relevant_csvs = [csv for csv in csvs if self.csv_identifier in csv.name]
175
- if len(relevant_csvs) == 0:
176
- raise ValueError("Workflow didn't seem to have returned any CSV file!")
177
- if len(relevant_csvs) > 1:
178
- logger.warning("Too many CSV results found for %s", self)
179
- return relevant_csvs[-1]
183
+ return self.get_unique_by_extension('.csv', self.csv_identifier)
180
184
 
181
185
  def csv_content(self) -> bytes:
182
186
  with open(self.csv_path, "rb") as f:
@@ -185,6 +189,16 @@ class SimStadtResults(ABC):
185
189
  def get_all_by_extension(self, ext: str) -> list[Path]:
186
190
  return [f for f in sorted(self.output_files) if f.suffix == ext]
187
191
 
192
+ def get_unique_by_extension(self, ext: str, filter: str | None = None) -> Path:
193
+ files_by_extension = self.get_all_by_extension(ext)
194
+ if filter:
195
+ files_by_extension = [f for f in files_by_extension if filter in f.name]
196
+ if len(files_by_extension) == 0:
197
+ raise ValueError(f"Workflow didn't seem to have returned any {ext} file!")
198
+ if len(files_by_extension) > 1:
199
+ raise ValueError(f"Too many {ext} results found for {self}!")
200
+ return files_by_extension[0]
201
+
188
202
  @property
189
203
  def diagrams(self) -> dict[str, Path]:
190
204
  return {}
@@ -243,6 +257,10 @@ class SimStadtResults(ABC):
243
257
  def create(cls, provider: str, **kwargs) -> "SimStadtResults":
244
258
  if provider not in cls._registry:
245
259
  from .unknown_workflow import UnknownWorkflowResults
246
- logger.warning("Unknown workflow provider: %s. Returning UnknownWorkflowResults.", provider)
260
+
261
+ logger.warning(
262
+ "Unknown workflow provider: %s. Returning UnknownWorkflowResults.",
263
+ provider,
264
+ )
247
265
  return UnknownWorkflowResults(**kwargs)
248
266
  return cls._registry[provider](**kwargs)
@@ -19,9 +19,7 @@ class EnergyGridResults(SimStadtResults):
19
19
  csv_identifier = "json_only"
20
20
 
21
21
  def _parse_results(self) -> pd.DataFrame:
22
- jsons = self.get_all_by_extension(".json")
23
- assert len(jsons) == 1, f"Exactly one JSON file should have been written: {len(jsons)} were found."
24
- json_output = jsons[0]
22
+ json_output = self.get_unique_by_extension(".json")
25
23
  with open(json_output, encoding="utf-8") as out:
26
24
  content = json.load(out)
27
25
  buildings = content["buildings"]
@@ -20,11 +20,9 @@ class SolarPotentialResults(SimStadtResults):
20
20
  csv_identifier = "_solar_potential" # no CSV output; csv_path will raise if called
21
21
 
22
22
  def _parse_results(self) -> pd.DataFrame:
23
- prn_paths = self.get_all_by_extension(".prn")
24
- if len(prn_paths) != 1:
25
- raise ValueError(f"Expected exactly one .prn file, found {len(prn_paths)}")
23
+ prn_path = self.get_unique_by_extension(".prn")
26
24
  return pd.read_csv(
27
- prn_paths[0], sep=r"\s+", names=["GHI", "DHI", "Ta"], header=None
25
+ prn_path, sep=r"\s+", names=["GHI", "DHI", "Ta"], header=None
28
26
  )
29
27
 
30
28
  @property
@@ -26,6 +26,7 @@ from .utils import chdir, random_id
26
26
  PARAMS = "params.xml"
27
27
  SIMSTADT2_GLOB = "SimStadt2_0.*/"
28
28
  SIMSTADT_TEMP_REPO = Path(tempfile.gettempdir()) / "simstadt_repo"
29
+ SIMSTADT_VERSION_FORMAT = r"(\S+)\s*\((\w+), rev. (\w+), (\d+)\s*\)"
29
30
 
30
31
  load_dotenv()
31
32
 
@@ -69,9 +70,9 @@ def get_template_path() -> Path:
69
70
  )
70
71
 
71
72
  def get_version_and_date() -> tuple[str, str]:
72
- SIMSTADT_VERSION_FORMAT = re.compile(r"Launching SimStadt (\S+) \((\w+), rev. (\w+), (\d+)\)")
73
+ SIMSTADT_VERSION_PATTERN = re.compile(f"Launching SimStadt {SIMSTADT_VERSION_FORMAT}")
73
74
  output = get_simstadt_output(get_simstadt_folder())
74
- m = SIMSTADT_VERSION_FORMAT.search(output)
75
+ m = SIMSTADT_VERSION_PATTERN.search(output)
75
76
  if not m:
76
77
  raise ValueError(f"No SimStadt version string found in output:\n{output}")
77
78
  version, _branch, _commit, date = m.groups()
File without changes