validibot-shared 0.1.0__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.
@@ -0,0 +1,89 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ **/__pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Environments
54
+ .env
55
+ .venv
56
+ env/
57
+ venv/
58
+ ENV/
59
+ env.bak/
60
+ venv.bak/
61
+
62
+ # IDE
63
+ .idea/
64
+ .vscode/
65
+ *.swp
66
+ *.swo
67
+ *~
68
+
69
+ # mypy
70
+ .mypy_cache/
71
+ .dmypy.json
72
+ dmypy.json
73
+
74
+ # ruff
75
+ .ruff_cache/
76
+
77
+ # OS
78
+ .DS_Store
79
+ Thumbs.db
80
+ Desktop.ini
81
+
82
+ # Temporary files
83
+ tmp/
84
+
85
+ # Local config that may contain secrets
86
+ .envs/
87
+
88
+ # AI generated files
89
+ .claude
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 McQuillen Interactive Pty. Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ Validibot Shared Library
2
+ Copyright (c) 2024-2026 McQuillen Interactive Pty. Ltd.
3
+
4
+ This product is licensed under the MIT License.
5
+ See the LICENSE file for the full license text.
6
+
7
+ TRADEMARKS
8
+
9
+ "Validibot", the Validibot logo, and the Validibot robot character are
10
+ trademarks of McQuillen Interactive Pty. Ltd. The MIT license grants rights
11
+ to the source code only, not to the trademarks.
12
+
13
+ You may use the Validibot name to accurately describe that your software
14
+ uses or integrates with Validibot, but not in a way that suggests official
15
+ endorsement or affiliation beyond that relationship.
16
+
17
+ THIRD-PARTY COMPONENTS
18
+
19
+ This software includes the following third-party components:
20
+
21
+ - Pydantic (MIT License) - https://docs.pydantic.dev/
22
+
23
+ Each component is subject to its own license terms.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: validibot-shared
3
+ Version: 0.1.0
4
+ Summary: Shared library for data interchange between Validibot and validator containers
5
+ Project-URL: Homepage, https://validibot.com
6
+ Project-URL: Documentation, https://docs.validibot.com
7
+ Project-URL: Repository, https://github.com/danielmcquillen/validibot-shared
8
+ Project-URL: Issues, https://github.com/danielmcquillen/validibot-shared/issues
9
+ Author-email: Daniel McQuillen <daniel@validibot.com>
10
+ Maintainer-email: Daniel McQuillen <daniel@validibot.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ License-File: NOTICE
14
+ Keywords: data-interchange,energyplus,fmu,simulation,validation,validibot
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: pydantic>=2.8.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # validibot-shared
34
+
35
+ Shared Pydantic models for [Validibot](https://validibot.com) validator containers.
36
+
37
+ This library defines the data interchange types used between Validibot and its advanced validator services, ensuring type safety and contract consistency.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install validibot-shared
43
+ ```
44
+
45
+ Or with uv:
46
+
47
+ ```bash
48
+ uv add validibot-shared
49
+ ```
50
+
51
+ ## Package Structure
52
+
53
+ ```
54
+ validibot_shared/
55
+ ├── validations/ # Base validation envelope schemas
56
+ │ └── envelopes.py # Input/output envelopes for all validators
57
+ ├── energyplus/ # EnergyPlus-specific models and envelopes
58
+ │ ├── models.py # Simulation output models
59
+ │ └── envelopes.py # Typed envelope subclasses
60
+ └── fmi/ # FMI/FMU-specific models
61
+ ├── models.py # Probe result models
62
+ └── envelopes.py # FMI envelope subclasses
63
+ ```
64
+
65
+ ## Core Concepts
66
+
67
+ ### Validation Envelopes
68
+
69
+ The library provides base envelope classes for validator communication:
70
+
71
+ - `ValidationInputEnvelope` - Standard input format for validation jobs
72
+ - `ValidationOutputEnvelope` - Standard output format with results
73
+ - `ValidationCallback` - Callback payload for async job completion
74
+
75
+ Supporting classes include `InputFileItem`, `ValidatorInfo`, `ExecutionContext`, `ValidationMessage`, `ValidationMetric`, and `ValidationArtifact`.
76
+
77
+ ### Typed Subclassing Pattern
78
+
79
+ Domain-specific validators extend the base envelopes with typed fields:
80
+
81
+ ```python
82
+ from validibot_shared.energyplus import EnergyPlusInputEnvelope, EnergyPlusInputs
83
+
84
+ # The envelope has typed inputs instead of dict[str, Any]
85
+ envelope = EnergyPlusInputEnvelope(
86
+ run_id="abc-123",
87
+ inputs=EnergyPlusInputs(timestep_per_hour=4),
88
+ # ... other fields
89
+ )
90
+
91
+ # IDE autocomplete and type checking work
92
+ timestep = envelope.inputs.timestep_per_hour
93
+ ```
94
+
95
+ This provides:
96
+
97
+ - **Type safety** - mypy/pyright catch errors at compile time
98
+ - **Runtime validation** - Pydantic validates all data
99
+ - **IDE support** - Full autocomplete for domain-specific fields
100
+
101
+ ## Usage Examples
102
+
103
+ ### Creating an Input Envelope
104
+
105
+ ```python
106
+ from validibot_shared.energyplus import EnergyPlusInputEnvelope, EnergyPlusInputs
107
+ from validibot_shared.validations.envelopes import (
108
+ InputFileItem,
109
+ ValidatorInfo,
110
+ ExecutionContext,
111
+ )
112
+
113
+ envelope = EnergyPlusInputEnvelope(
114
+ run_id="run-123",
115
+ validator=ValidatorInfo(id="v1", type="energyplus", version="24.2.0"),
116
+ input_files=[
117
+ InputFileItem(
118
+ name="model.idf",
119
+ mime_type="application/vnd.energyplus.idf",
120
+ role="primary-model",
121
+ uri="gs://bucket/model.idf",
122
+ ),
123
+ ],
124
+ inputs=EnergyPlusInputs(timestep_per_hour=4),
125
+ context=ExecutionContext(
126
+ callback_url="https://api.example.com/callback",
127
+ execution_bundle_uri="gs://bucket/run-123/",
128
+ ),
129
+ )
130
+ ```
131
+
132
+ ### Deserializing Results
133
+
134
+ ```python
135
+ from validibot_shared.energyplus import EnergyPlusOutputEnvelope
136
+
137
+ # Parse JSON response from validator
138
+ envelope = EnergyPlusOutputEnvelope.model_validate_json(response_json)
139
+
140
+ # Access typed outputs
141
+ if envelope.outputs:
142
+ print(f"EUI: {envelope.outputs.metrics.eui_kbtu_per_sqft}")
143
+ ```
144
+
145
+ ### FMI Probe Results
146
+
147
+ ```python
148
+ from validibot_shared.fmi.models import FMIProbeResult, FMIVariableMeta
149
+
150
+ # Create a successful probe result
151
+ result = FMIProbeResult.success(
152
+ variables=[
153
+ FMIVariableMeta(name="temperature", causality="output", value_type="Real"),
154
+ ],
155
+ execution_seconds=0.5,
156
+ )
157
+
158
+ # Create a failure result
159
+ result = FMIProbeResult.failure(errors=["Invalid FMU: missing modelDescription.xml"])
160
+ ```
161
+
162
+ ## Dependencies
163
+
164
+ - `pydantic>=2.8.0`
165
+
166
+ ## License
167
+
168
+ MIT License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,136 @@
1
+ # validibot-shared
2
+
3
+ Shared Pydantic models for [Validibot](https://validibot.com) validator containers.
4
+
5
+ This library defines the data interchange types used between Validibot and its advanced validator services, ensuring type safety and contract consistency.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install validibot-shared
11
+ ```
12
+
13
+ Or with uv:
14
+
15
+ ```bash
16
+ uv add validibot-shared
17
+ ```
18
+
19
+ ## Package Structure
20
+
21
+ ```
22
+ validibot_shared/
23
+ ├── validations/ # Base validation envelope schemas
24
+ │ └── envelopes.py # Input/output envelopes for all validators
25
+ ├── energyplus/ # EnergyPlus-specific models and envelopes
26
+ │ ├── models.py # Simulation output models
27
+ │ └── envelopes.py # Typed envelope subclasses
28
+ └── fmi/ # FMI/FMU-specific models
29
+ ├── models.py # Probe result models
30
+ └── envelopes.py # FMI envelope subclasses
31
+ ```
32
+
33
+ ## Core Concepts
34
+
35
+ ### Validation Envelopes
36
+
37
+ The library provides base envelope classes for validator communication:
38
+
39
+ - `ValidationInputEnvelope` - Standard input format for validation jobs
40
+ - `ValidationOutputEnvelope` - Standard output format with results
41
+ - `ValidationCallback` - Callback payload for async job completion
42
+
43
+ Supporting classes include `InputFileItem`, `ValidatorInfo`, `ExecutionContext`, `ValidationMessage`, `ValidationMetric`, and `ValidationArtifact`.
44
+
45
+ ### Typed Subclassing Pattern
46
+
47
+ Domain-specific validators extend the base envelopes with typed fields:
48
+
49
+ ```python
50
+ from validibot_shared.energyplus import EnergyPlusInputEnvelope, EnergyPlusInputs
51
+
52
+ # The envelope has typed inputs instead of dict[str, Any]
53
+ envelope = EnergyPlusInputEnvelope(
54
+ run_id="abc-123",
55
+ inputs=EnergyPlusInputs(timestep_per_hour=4),
56
+ # ... other fields
57
+ )
58
+
59
+ # IDE autocomplete and type checking work
60
+ timestep = envelope.inputs.timestep_per_hour
61
+ ```
62
+
63
+ This provides:
64
+
65
+ - **Type safety** - mypy/pyright catch errors at compile time
66
+ - **Runtime validation** - Pydantic validates all data
67
+ - **IDE support** - Full autocomplete for domain-specific fields
68
+
69
+ ## Usage Examples
70
+
71
+ ### Creating an Input Envelope
72
+
73
+ ```python
74
+ from validibot_shared.energyplus import EnergyPlusInputEnvelope, EnergyPlusInputs
75
+ from validibot_shared.validations.envelopes import (
76
+ InputFileItem,
77
+ ValidatorInfo,
78
+ ExecutionContext,
79
+ )
80
+
81
+ envelope = EnergyPlusInputEnvelope(
82
+ run_id="run-123",
83
+ validator=ValidatorInfo(id="v1", type="energyplus", version="24.2.0"),
84
+ input_files=[
85
+ InputFileItem(
86
+ name="model.idf",
87
+ mime_type="application/vnd.energyplus.idf",
88
+ role="primary-model",
89
+ uri="gs://bucket/model.idf",
90
+ ),
91
+ ],
92
+ inputs=EnergyPlusInputs(timestep_per_hour=4),
93
+ context=ExecutionContext(
94
+ callback_url="https://api.example.com/callback",
95
+ execution_bundle_uri="gs://bucket/run-123/",
96
+ ),
97
+ )
98
+ ```
99
+
100
+ ### Deserializing Results
101
+
102
+ ```python
103
+ from validibot_shared.energyplus import EnergyPlusOutputEnvelope
104
+
105
+ # Parse JSON response from validator
106
+ envelope = EnergyPlusOutputEnvelope.model_validate_json(response_json)
107
+
108
+ # Access typed outputs
109
+ if envelope.outputs:
110
+ print(f"EUI: {envelope.outputs.metrics.eui_kbtu_per_sqft}")
111
+ ```
112
+
113
+ ### FMI Probe Results
114
+
115
+ ```python
116
+ from validibot_shared.fmi.models import FMIProbeResult, FMIVariableMeta
117
+
118
+ # Create a successful probe result
119
+ result = FMIProbeResult.success(
120
+ variables=[
121
+ FMIVariableMeta(name="temperature", causality="output", value_type="Real"),
122
+ ],
123
+ execution_seconds=0.5,
124
+ )
125
+
126
+ # Create a failure result
127
+ result = FMIProbeResult.failure(errors=["Invalid FMU: missing modelDescription.xml"])
128
+ ```
129
+
130
+ ## Dependencies
131
+
132
+ - `pydantic>=2.8.0`
133
+
134
+ ## License
135
+
136
+ MIT License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "validibot-shared"
7
+ version = "0.1.0"
8
+ description = "Shared library for data interchange between Validibot and validator containers"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Daniel McQuillen", email = "daniel@validibot.com" }]
12
+ maintainers = [{ name = "Daniel McQuillen", email = "daniel@validibot.com" }]
13
+ requires-python = ">=3.10"
14
+ keywords = [
15
+ "validibot",
16
+ "validation",
17
+ "energyplus",
18
+ "fmu",
19
+ "simulation",
20
+ "data-interchange",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ "Topic :: Scientific/Engineering",
34
+ ]
35
+ dependencies = [
36
+ "pydantic>=2.8.0",
37
+ ]
38
+
39
+ [project.optional-dependencies]
40
+ dev = [
41
+ "pytest>=8.3.0",
42
+ "ruff>=0.8.0",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://validibot.com"
47
+ Documentation = "https://docs.validibot.com"
48
+ Repository = "https://github.com/danielmcquillen/validibot-shared"
49
+ Issues = "https://github.com/danielmcquillen/validibot-shared/issues"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["validibot_shared"]
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = [
56
+ "/validibot_shared",
57
+ "/tests",
58
+ "/README.md",
59
+ "/LICENSE",
60
+ ]
61
+
62
+ [tool.ruff]
63
+ line-length = 88
64
+ target-version = "py310"
65
+
66
+ [tool.ruff.lint]
67
+ select = [
68
+ "E", # pycodestyle errors
69
+ "W", # pycodestyle warnings
70
+ "F", # Pyflakes
71
+ "I", # isort
72
+ "B", # flake8-bugbear
73
+ "C4", # flake8-comprehensions
74
+ "UP", # pyupgrade
75
+ ]
76
+
77
+ [tool.ruff.lint.isort]
78
+ known-first-party = ["validibot_shared"]
79
+
80
+ [tool.pytest.ini_options]
81
+ addopts = "-ra -q"
82
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,146 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+ from pydantic import ValidationError
5
+
6
+ from validibot_shared.energyplus.envelopes import (
7
+ EnergyPlusInputEnvelope,
8
+ EnergyPlusInputs,
9
+ EnergyPlusOutputEnvelope,
10
+ EnergyPlusOutputs,
11
+ )
12
+ from validibot_shared.validations.envelopes import (
13
+ ExecutionContext,
14
+ InputFileItem,
15
+ SupportedMimeType,
16
+ ValidationStatus,
17
+ ValidatorType,
18
+ )
19
+
20
+ # Test constants to avoid magic values
21
+ DEFAULT_TIMESTEP_PER_HOUR = 4
22
+ TEST_TIMESTEP_PER_HOUR = 6
23
+ TEST_ELECTRICITY_KWH = 123.4
24
+ TEST_EXECUTION_SECONDS = 12.5
25
+ TEST_EUI_KWH_M2 = 10.5
26
+
27
+
28
+ def _base_input_envelope_kwargs():
29
+ return {
30
+ "run_id": "run-1",
31
+ "validator": {
32
+ "id": "val-1",
33
+ "type": ValidatorType.ENERGYPLUS,
34
+ "version": "1.0",
35
+ },
36
+ "org": {"id": "org-1", "name": "ValidiBot"},
37
+ "workflow": {"id": "wf-1", "step_id": "step-1", "step_name": "EnergyPlus"},
38
+ "input_files": [
39
+ InputFileItem(
40
+ name="model.idf",
41
+ mime_type=SupportedMimeType.ENERGYPLUS_IDF,
42
+ role="primary-model",
43
+ uri="gs://bucket/model.idf",
44
+ )
45
+ ],
46
+ "context": ExecutionContext(
47
+ callback_url="https://example.com/callback",
48
+ execution_bundle_uri="gs://bucket/run-1/",
49
+ timeout_seconds=600,
50
+ tags=["smoke"],
51
+ ),
52
+ }
53
+
54
+
55
+ def test_energyplus_inputs_defaults():
56
+ """EnergyPlusInputs should have sensible defaults."""
57
+ inputs = EnergyPlusInputs()
58
+
59
+ assert inputs.timestep_per_hour == DEFAULT_TIMESTEP_PER_HOUR
60
+ assert inputs.run_period_days is None
61
+ assert inputs.invocation_mode == "cli"
62
+
63
+
64
+ def test_energyplus_inputs_validation():
65
+ """EnergyPlusInputs should reject invalid values."""
66
+ with pytest.raises(ValidationError):
67
+ EnergyPlusInputs(timestep_per_hour=0)
68
+
69
+ with pytest.raises(ValidationError):
70
+ EnergyPlusInputs(invocation_mode="invalid-mode")
71
+
72
+
73
+ def test_energyplus_input_envelope_uses_typed_inputs():
74
+ """EnergyPlusInputEnvelope should parse inputs into EnergyPlusInputs."""
75
+ data = _base_input_envelope_kwargs()
76
+ envelope = EnergyPlusInputEnvelope(
77
+ **data,
78
+ inputs={"timestep_per_hour": TEST_TIMESTEP_PER_HOUR},
79
+ )
80
+
81
+ assert isinstance(envelope.inputs, EnergyPlusInputs)
82
+ assert envelope.inputs.timestep_per_hour == TEST_TIMESTEP_PER_HOUR
83
+
84
+
85
+ def test_energyplus_input_envelope_rejects_invalid_inputs():
86
+ """EnergyPlusInputEnvelope should reject invalid input configurations."""
87
+ data = _base_input_envelope_kwargs()
88
+
89
+ with pytest.raises(ValidationError):
90
+ EnergyPlusInputEnvelope(
91
+ **data,
92
+ inputs={"timestep_per_hour": 1, "invocation_mode": "bad"},
93
+ )
94
+
95
+
96
+ def test_energyplus_outputs_compose_nested_models():
97
+ """EnergyPlusOutputs should compose nested models correctly."""
98
+ outputs = EnergyPlusOutputs(
99
+ outputs={"eplusout_sql": "outputs/eplusout.sql", "eplusout_err": None},
100
+ metrics={"site_electricity_kwh": TEST_ELECTRICITY_KWH},
101
+ logs={"stdout_tail": "log tail"},
102
+ energyplus_returncode=0,
103
+ execution_seconds=TEST_EXECUTION_SECONDS,
104
+ invocation_mode="python_api",
105
+ )
106
+
107
+ assert isinstance(outputs.outputs.eplusout_sql, Path)
108
+ assert outputs.metrics.site_electricity_kwh == TEST_ELECTRICITY_KWH
109
+ assert outputs.logs.stdout_tail == "log tail"
110
+ assert outputs.execution_seconds == TEST_EXECUTION_SECONDS
111
+
112
+
113
+ def test_energyplus_outputs_forbid_extra_fields():
114
+ """EnergyPlusOutputs should forbid extra fields."""
115
+ with pytest.raises(ValidationError):
116
+ EnergyPlusOutputs(
117
+ energyplus_returncode=0,
118
+ execution_seconds=1.0,
119
+ invocation_mode="cli",
120
+ unknown="nope",
121
+ )
122
+
123
+
124
+ def test_energyplus_output_envelope_accepts_typed_outputs():
125
+ """EnergyPlusOutputEnvelope should accept typed outputs."""
126
+ envelope = EnergyPlusOutputEnvelope(
127
+ run_id="run-2",
128
+ validator={
129
+ "id": "val-1",
130
+ "type": ValidatorType.ENERGYPLUS,
131
+ "version": "1.0",
132
+ },
133
+ status=ValidationStatus.SUCCESS,
134
+ timing={"queued_at": None, "started_at": None, "finished_at": None},
135
+ outputs={
136
+ "outputs": {"eplusout_sql": "outputs/eplusout.sql"},
137
+ "metrics": {"site_eui_kwh_m2": TEST_EUI_KWH_M2},
138
+ "energyplus_returncode": 0,
139
+ "execution_seconds": 3.2,
140
+ "invocation_mode": "cli",
141
+ },
142
+ )
143
+
144
+ assert isinstance(envelope.outputs, EnergyPlusOutputs)
145
+ assert envelope.outputs.outputs.eplusout_sql.name == "eplusout.sql"
146
+ assert envelope.outputs.metrics.site_eui_kwh_m2 == TEST_EUI_KWH_M2