validibot-shared 0.1.0__py3-none-any.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.
@@ -0,0 +1,16 @@
1
+ """Shared domain models and utilities for Simple Validations."""
2
+
3
+ from validibot_shared.energyplus.models import (
4
+ EnergyPlusSimulationLogs,
5
+ EnergyPlusSimulationMetrics,
6
+ EnergyPlusSimulationOutputs,
7
+ )
8
+ from validibot_shared.fmi.models import FMIProbeResult, FMIVariableMeta
9
+
10
+ __all__ = [
11
+ "EnergyPlusSimulationLogs",
12
+ "EnergyPlusSimulationMetrics",
13
+ "EnergyPlusSimulationOutputs",
14
+ "FMIProbeResult",
15
+ "FMIVariableMeta",
16
+ ]
@@ -0,0 +1,25 @@
1
+ """EnergyPlus integration models and envelopes."""
2
+
3
+ # Reusable output models (components used within envelope classes)
4
+ # Typed envelope subclasses for validator containers
5
+ from .envelopes import (
6
+ EnergyPlusInputEnvelope,
7
+ EnergyPlusInputs,
8
+ EnergyPlusOutputEnvelope,
9
+ EnergyPlusOutputs,
10
+ )
11
+ from .models import (
12
+ EnergyPlusSimulationLogs,
13
+ EnergyPlusSimulationMetrics,
14
+ EnergyPlusSimulationOutputs,
15
+ )
16
+
17
+ __all__ = [
18
+ "EnergyPlusInputEnvelope",
19
+ "EnergyPlusInputs",
20
+ "EnergyPlusOutputEnvelope",
21
+ "EnergyPlusOutputs",
22
+ "EnergyPlusSimulationLogs",
23
+ "EnergyPlusSimulationMetrics",
24
+ "EnergyPlusSimulationOutputs",
25
+ ]
@@ -0,0 +1,300 @@
1
+ """
2
+ EnergyPlus-specific envelope schemas for Advanced validator communication.
3
+
4
+ EnergyPlus is an Advanced validator - it runs in a separate container/service rather
5
+ than within the Django app. These schemas extend the base validation envelopes with
6
+ EnergyPlus-specific typed inputs and outputs.
7
+
8
+ ## Architecture Pattern
9
+
10
+ This module demonstrates the envelope subclassing pattern:
11
+
12
+ 1. **Base envelopes** (in validibot_shared.validations.envelopes):
13
+ - ValidationInputEnvelope has inputs: dict[str, Any]
14
+ - ValidationOutputEnvelope has outputs: dict[str, Any] | None
15
+
16
+ 2. **Domain-specific envelopes** (this file):
17
+ - EnergyPlusInputEnvelope has inputs: EnergyPlusInputs (typed override!)
18
+ - EnergyPlusOutputEnvelope has outputs: EnergyPlusOutputs (typed override!)
19
+
20
+ This provides type safety while maintaining a consistent interface across
21
+ all Advanced validators.
22
+
23
+ ## Reusing Component Models
24
+
25
+ The EnergyPlusOutputs class composes existing models from models.py:
26
+ - EnergyPlusSimulationOutputs: File paths (SQL, CSV, ERR, ESO)
27
+ - EnergyPlusSimulationMetrics: Extracted metrics (electricity, gas, EUI)
28
+ - EnergyPlusSimulationLogs: Log tails (stdout, stderr, err file)
29
+
30
+ This follows DRY principles - we don't duplicate data structures. The models.py
31
+ file contains reusable components that represent EnergyPlus simulation outputs.
32
+ These are composed within the envelope classes to provide type-safe output packaging.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from pydantic import BaseModel, Field
38
+
39
+ from validibot_shared.energyplus.models import (
40
+ EnergyPlusSimulationLogs,
41
+ EnergyPlusSimulationMetrics,
42
+ EnergyPlusSimulationOutputs,
43
+ InvocationMode,
44
+ )
45
+ from validibot_shared.validations.envelopes import (
46
+ ValidationInputEnvelope,
47
+ ValidationOutputEnvelope,
48
+ )
49
+
50
+ # ==============================================================================
51
+ # EnergyPlus Input Configuration
52
+ # ==============================================================================
53
+
54
+
55
+ class EnergyPlusInputs(BaseModel):
56
+ """
57
+ EnergyPlus simulation configuration parameters.
58
+
59
+ These are the settings that control how the EnergyPlus simulation runs.
60
+ Input files (IDF model, EPW weather) are passed separately in the parent
61
+ envelope's input_files field with roles like 'primary-model' and 'weather'.
62
+
63
+ ## Why Separate From Input Files?
64
+
65
+ Configuration parameters (this class) are different from input files:
66
+ - Configuration: How to run the simulation (timesteps, invocation mode, etc.)
67
+ - Input files: What to run (IDF model, weather data)
68
+
69
+ This separation allows:
70
+ - Type-safe configuration validation
71
+ - Reusable models with different run settings
72
+ - Clear distinction between data files and execution parameters
73
+
74
+ ## Architecture Design
75
+
76
+ This configuration model is separate from input files to maintain clear
77
+ separation of concerns:
78
+ - Configuration (this class): How to run the simulation
79
+ - Input files (in parent envelope): What files to process
80
+
81
+ Using a typed Pydantic model provides compile-time type checking and
82
+ runtime validation of all configuration parameters.
83
+
84
+ Note: The validator always returns a fixed set of output signals defined
85
+ in its catalog. Users don't need to specify which outputs they want -
86
+ they get all defined signals and write assertions against the ones they
87
+ care about.
88
+ """
89
+
90
+ # Simulation timestep configuration
91
+ timestep_per_hour: int = Field(
92
+ default=4,
93
+ description="Number of timesteps per hour (e.g., 4 = 15-minute intervals)",
94
+ ge=1,
95
+ le=60,
96
+ )
97
+
98
+ # Optional run period override
99
+ run_period_days: int | None = Field(
100
+ default=None,
101
+ description="Optional override for run period length in days",
102
+ )
103
+
104
+ # Invocation method
105
+ invocation_mode: InvocationMode = Field(
106
+ default="cli",
107
+ description="How to invoke EnergyPlus: 'cli' or 'python_api'",
108
+ )
109
+
110
+ model_config = {"extra": "forbid"}
111
+
112
+
113
+ # ==============================================================================
114
+ # EnergyPlus Output Data
115
+ # ==============================================================================
116
+
117
+
118
+ class EnergyPlusOutputs(BaseModel):
119
+ """
120
+ EnergyPlus simulation outputs and execution information.
121
+
122
+ This contains detailed, EnergyPlus-specific results from the simulation.
123
+ It's used as the typed 'outputs' field in EnergyPlusOutputEnvelope.
124
+
125
+ ## Why This Class Exists
126
+
127
+ The parent envelope (ValidationOutputEnvelope) already has generic fields
128
+ like messages, metrics, and artifacts. This class adds EnergyPlus-specific
129
+ execution details that don't fit the generic schema:
130
+ - Return codes and execution timing
131
+ - Process logs (stdout, stderr, err file)
132
+ - Specific file paths (eplusout.sql, eplusout.err, etc.)
133
+
134
+ ## Composing Reusable Models (DRY Principle)
135
+
136
+ We reuse three models from models.py instead of duplicating their definitions:
137
+ - EnergyPlusSimulationOutputs: File paths (SQL, CSV, ERR, ESO)
138
+ - EnergyPlusSimulationMetrics: Extracted metrics (electricity, gas, EUI)
139
+ - EnergyPlusSimulationLogs: Log tails (stdout, stderr, err file)
140
+
141
+ This follows the composition pattern - small, focused models are composed
142
+ into larger structures.
143
+
144
+ ## Why Create This Class?
145
+
146
+ The parent envelope (ValidationOutputEnvelope) provides generic validation
147
+ outputs (messages, metrics, artifacts). This class adds EnergyPlus-specific
148
+ execution details that don't fit the generic schema:
149
+ - Process return codes and timing
150
+ - Detailed file paths and logs
151
+ - Invocation method tracking
152
+
153
+ This separation prevents redundancy:
154
+ - Generic validation data → parent envelope fields
155
+ - EnergyPlus execution data → this class
156
+ """
157
+
158
+ # Reuse existing output file tracking from models.py
159
+ outputs: EnergyPlusSimulationOutputs = Field(
160
+ default_factory=EnergyPlusSimulationOutputs,
161
+ description="Paths to EnergyPlus output files (SQL, CSV, ERR, ESO)",
162
+ )
163
+
164
+ # Reuse existing metrics tracking from models.py
165
+ metrics: EnergyPlusSimulationMetrics = Field(
166
+ default_factory=EnergyPlusSimulationMetrics,
167
+ description="Extracted simulation metrics (energy use, etc.)",
168
+ )
169
+
170
+ # Reuse existing log tracking from models.py
171
+ logs: EnergyPlusSimulationLogs | None = Field(
172
+ default=None, description="Simulation logs (stdout, stderr, err file tails)"
173
+ )
174
+
175
+ # Execution metadata
176
+ energyplus_returncode: int = Field(
177
+ description="EnergyPlus process return code (0 = success)"
178
+ )
179
+
180
+ execution_seconds: float = Field(
181
+ ge=0, description="Total simulation execution time in seconds"
182
+ )
183
+
184
+ invocation_mode: InvocationMode = Field(
185
+ description="How EnergyPlus was invoked ('cli' or 'python_api')"
186
+ )
187
+
188
+ model_config = {"extra": "forbid"}
189
+
190
+
191
+ # ==============================================================================
192
+ # EnergyPlus-Specific Envelopes
193
+ # ==============================================================================
194
+
195
+
196
+ class EnergyPlusInputEnvelope(ValidationInputEnvelope):
197
+ """
198
+ EnergyPlus-specific input envelope.
199
+
200
+ This is what Django serializes and writes to storage as input.json before
201
+ triggering an EnergyPlus validator container.
202
+
203
+ ## Type-Safe Field Override
204
+
205
+ The base ValidationInputEnvelope has:
206
+ ```python
207
+ inputs: dict[str, Any] = Field(default_factory=dict)
208
+ ```
209
+
210
+ We override it with a typed version:
211
+ ```python
212
+ inputs: EnergyPlusInputs
213
+ ```
214
+
215
+ This gives us:
216
+ - Compile-time type checking in Django (mypy catches config errors)
217
+ - Runtime validation (Pydantic validates timesteps, output variables, etc.)
218
+ - Auto-generated documentation (API docs from Pydantic schema)
219
+ - IDE autocomplete when building input envelopes
220
+
221
+ ## How Data Flows
222
+
223
+ 1. Django creates this envelope:
224
+ - input_files: [IDF with role='primary-model', EPW with role='weather']
225
+ - inputs: EnergyPlusInputs(timestep_per_hour=4)
226
+ - context: ExecutionContext(callback_url=..., execution_bundle_uri=...)
227
+
228
+ 2. Django serializes to JSON and uploads to storage as input.json
229
+
230
+ 3. Validator container downloads input.json and deserializes to this class
231
+
232
+ 4. Validator container extracts typed configuration from inputs field
233
+ """
234
+
235
+ # Override inputs field with typed EnergyPlus configuration
236
+ inputs: EnergyPlusInputs
237
+
238
+
239
+ class EnergyPlusOutputEnvelope(ValidationOutputEnvelope):
240
+ """
241
+ EnergyPlus-specific output envelope.
242
+
243
+ This is what the EnergyPlus validator container serializes and writes to
244
+ storage as output.json after simulation completes.
245
+
246
+ ## Type-Safe Field Override
247
+
248
+ The base ValidationOutputEnvelope has:
249
+ ```python
250
+ outputs: dict[str, Any] | None = Field(default=None)
251
+ ```
252
+
253
+ We override it with a typed version:
254
+ ```python
255
+ outputs: EnergyPlusOutputs
256
+ ```
257
+
258
+ This gives us the same benefits as the input envelope (type checking,
259
+ validation, docs, autocomplete).
260
+
261
+ ## Generic vs Domain-Specific Data
262
+
263
+ The envelope contains BOTH:
264
+
265
+ **Generic fields** (from parent ValidationOutputEnvelope):
266
+ - messages: ValidationMessage list (errors, warnings - consistent
267
+ across all validators)
268
+ - metrics: ValidationMetric list (high-level computed values for UI display)
269
+ - artifacts: ValidationArtifact list (GCS URIs to important output files)
270
+ - status: SUCCESS/FAILED_VALIDATION/FAILED_RUNTIME/CANCELLED
271
+
272
+ **Domain-specific field** (this class):
273
+ - outputs: EnergyPlusOutputs (returncode, logs, all file paths, execution time)
274
+
275
+ ## Why Both?
276
+
277
+ - Generic fields enable consistent UI across all validators
278
+ (same message format, same metric format)
279
+ - Domain-specific outputs preserve detailed execution info
280
+ for debugging and reproduction
281
+
282
+ ## How Data Flows
283
+
284
+ 1. Validator container runs EnergyPlus simulation
285
+
286
+ 2. Validator container creates this envelope:
287
+ - status: SUCCESS or FAILED_VALIDATION
288
+ - messages: [ValidationMessage(severity=ERROR, text="Missing required object")]
289
+ - metrics: [ValidationMetric(name="electricity_kwh", value=12345, unit="kWh")]
290
+ - outputs: EnergyPlusOutputs(energyplus_returncode=0, ...)
291
+
292
+ 3. Validator container serializes to JSON and uploads to storage as output.json
293
+
294
+ 4. Validator container POSTs minimal callback to Django (run_id, status, result_uri)
295
+
296
+ 5. Django downloads output.json and deserializes to this class
297
+ """
298
+
299
+ # Override outputs field with typed EnergyPlus results
300
+ outputs: EnergyPlusOutputs
@@ -0,0 +1,140 @@
1
+ """
2
+ Reusable EnergyPlus output models.
3
+
4
+ These models represent EnergyPlus simulation outputs and are used as components
5
+ within the typed envelope classes (see envelopes.py).
6
+
7
+ They are kept separate from envelopes to follow the single responsibility principle:
8
+ - These models: What data EnergyPlus produces (files, metrics, logs)
9
+ - Envelope classes: How that data is packaged for Django ↔ validator communication
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path # noqa: TC003
15
+ from typing import Annotated, Literal
16
+
17
+ from pydantic import BaseModel, ConfigDict, Field
18
+
19
+ # Useful constants for log tailing
20
+ STDOUT_TAIL_CHARS = 4000
21
+ LOG_TAIL_LINES = 200
22
+
23
+ # Type alias for invocation modes
24
+ InvocationMode = Literal["python_api", "cli"]
25
+
26
+ # Type aliases for non-negative numbers
27
+ NonNegFloat = Annotated[float, Field(ge=0)]
28
+ NonNegInt = Annotated[int, Field(ge=0)]
29
+
30
+
31
+ class EnergyPlusSimulationOutputs(BaseModel):
32
+ """
33
+ EnergyPlus output file paths.
34
+
35
+ Tracks the standard EnergyPlus output files produced by a simulation.
36
+ Used within EnergyPlusOutputs (in envelopes.py) to preserve file locations.
37
+ """
38
+
39
+ model_config = ConfigDict(extra="forbid")
40
+ eplusout_sql: Path | None = None
41
+ eplusout_err: Path | None = None
42
+ eplusout_csv: Path | None = None
43
+ eplusout_eso: Path | None = None
44
+
45
+
46
+ class EnergyPlusSimulationMetrics(BaseModel):
47
+ """
48
+ Extracted EnergyPlus simulation metrics.
49
+
50
+ These are the core output signals extracted from EnergyPlus simulation results.
51
+ Field names here must match the binding_config["key"] values in the EnergyPlus
52
+ provider catalog (see validibot/validations/providers/energyplus.py).
53
+
54
+ The validator extracts these values from the EnergyPlus SQL database
55
+ (eplusout.sql) and they become available as output signals for assertions.
56
+ """
57
+
58
+ model_config = ConfigDict(extra="forbid")
59
+
60
+ # ==========================================================================
61
+ # Energy Consumption (from EnergyPlus meters)
62
+ # ==========================================================================
63
+
64
+ # Total site electricity consumption
65
+ site_electricity_kwh: NonNegFloat | None = None
66
+
67
+ # Total natural gas consumption
68
+ site_natural_gas_kwh: NonNegFloat | None = None
69
+
70
+ # District cooling energy (if present in model)
71
+ site_district_cooling_kwh: NonNegFloat | None = None
72
+
73
+ # District heating energy (if present in model)
74
+ site_district_heating_kwh: NonNegFloat | None = None
75
+
76
+ # ==========================================================================
77
+ # Energy Use Intensity
78
+ # ==========================================================================
79
+
80
+ # Site EUI (total energy / floor area)
81
+ site_eui_kwh_m2: NonNegFloat | None = None
82
+
83
+ # ==========================================================================
84
+ # End-Use Breakdown (all fuels combined)
85
+ # ==========================================================================
86
+
87
+ # Space heating energy
88
+ heating_energy_kwh: NonNegFloat | None = None
89
+
90
+ # Space cooling energy
91
+ cooling_energy_kwh: NonNegFloat | None = None
92
+
93
+ # Interior lighting energy
94
+ interior_lighting_kwh: NonNegFloat | None = None
95
+
96
+ # Fan energy (supply, return, exhaust)
97
+ fans_energy_kwh: NonNegFloat | None = None
98
+
99
+ # Pump energy (chilled water, hot water, condenser)
100
+ pumps_energy_kwh: NonNegFloat | None = None
101
+
102
+ # Domestic hot water energy
103
+ water_systems_kwh: NonNegFloat | None = None
104
+
105
+ # ==========================================================================
106
+ # Comfort / Performance
107
+ # ==========================================================================
108
+
109
+ # Hours heating setpoint not met
110
+ unmet_heating_hours: NonNegFloat | None = None
111
+
112
+ # Hours cooling setpoint not met
113
+ unmet_cooling_hours: NonNegFloat | None = None
114
+
115
+ # Peak electric demand
116
+ peak_electric_demand_w: NonNegFloat | None = None
117
+
118
+ # ==========================================================================
119
+ # Building Characteristics (from IDF/SQL)
120
+ # ==========================================================================
121
+
122
+ # Total conditioned floor area
123
+ floor_area_m2: NonNegFloat | None = None
124
+
125
+ # Number of thermal zones
126
+ zone_count: NonNegInt | None = None
127
+
128
+
129
+ class EnergyPlusSimulationLogs(BaseModel):
130
+ """
131
+ EnergyPlus execution logs.
132
+
133
+ Tails of stdout, stderr, and the eplusout.err file for debugging failed simulations.
134
+ Tails are used instead of full logs to keep envelope sizes reasonable.
135
+ """
136
+
137
+ model_config = ConfigDict(extra="forbid")
138
+ stdout_tail: str | None = None
139
+ stderr_tail: str | None = None
140
+ err_tail: str | None = None
@@ -0,0 +1,6 @@
1
+ from validibot_shared.fmi.models import FMIProbeResult, FMIVariableMeta
2
+
3
+ __all__ = [
4
+ "FMIProbeResult",
5
+ "FMIVariableMeta",
6
+ ]
@@ -0,0 +1,190 @@
1
+ """
2
+ Pydantic envelopes for FMI Advanced validator jobs.
3
+
4
+ FMI is an Advanced validator - it runs in a separate container/service rather
5
+ than within the Django app. These schemas define the contract between Django
6
+ and the FMI validator container:
7
+ - Input envelope: FMU URI plus resolved input values and simulation config
8
+ - Output envelope: FMI outputs, metrics, messages, and artifacts
9
+
10
+ Inputs/outputs are keyed by validator catalog slugs. Workflow authors cannot
11
+ remap signals; bindings live on catalog entries (input_binding_path) and
12
+ default to slug-name lookups.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from validibot_shared.validations.envelopes import (
22
+ ExecutionContext,
23
+ InputFileItem,
24
+ SupportedMimeType,
25
+ ValidationInputEnvelope,
26
+ ValidationOutputEnvelope,
27
+ ValidatorType,
28
+ )
29
+
30
+
31
+ class FMISimulationConfig(BaseModel):
32
+ """Simulation configuration for FMI runs."""
33
+
34
+ start_time: float = Field(
35
+ default=0.0,
36
+ description="Simulation start time (seconds).",
37
+ ge=0,
38
+ )
39
+ stop_time: float = Field(
40
+ default=1.0,
41
+ description="Simulation stop time (seconds).",
42
+ gt=0,
43
+ )
44
+ step_size: float = Field(
45
+ default=0.01,
46
+ description="Communication step size (seconds).",
47
+ gt=0,
48
+ )
49
+ tolerance: float | None = Field(
50
+ default=None,
51
+ description="Solver tolerance, if supported by the FMU.",
52
+ gt=0,
53
+ )
54
+
55
+
56
+ class FMIInputs(BaseModel):
57
+ """Resolved inputs plus simulation config, keyed by catalog slugs."""
58
+
59
+ input_values: dict[str, Any] = Field(
60
+ default_factory=dict,
61
+ description="Input values keyed by catalog slugs.",
62
+ )
63
+ simulation: FMISimulationConfig = Field(
64
+ default_factory=FMISimulationConfig,
65
+ description="Simulation time/step configuration.",
66
+ )
67
+ output_variables: list[str] = Field(
68
+ default_factory=list,
69
+ description=(
70
+ "Catalog slugs to capture as outputs. Empty means all output slugs."
71
+ ),
72
+ )
73
+
74
+
75
+ class FMIOutputs(BaseModel):
76
+ """FMI execution results keyed by catalog slugs."""
77
+
78
+ output_values: dict[str, Any] = Field(
79
+ default_factory=dict,
80
+ description="Output values keyed by catalog slugs.",
81
+ )
82
+ fmu_guid: str | None = Field(default=None, description="FMU GUID, if reported.")
83
+ fmi_version: str | None = Field(default=None, description="FMI version.")
84
+ model_name: str | None = Field(default=None, description="FMU model name.")
85
+ execution_seconds: float = Field(
86
+ description="Wall-clock execution time (seconds).",
87
+ ge=0,
88
+ )
89
+ simulation_time_reached: float = Field(
90
+ description="Simulation time reached before completion/stop.",
91
+ ge=0,
92
+ )
93
+ fmu_log: str | None = Field(
94
+ default=None,
95
+ description="Optional FMU log output.",
96
+ )
97
+
98
+
99
+ class FMIInputEnvelope(ValidationInputEnvelope):
100
+ """Input envelope for FMI validator containers."""
101
+
102
+ inputs: FMIInputs
103
+
104
+
105
+ class FMIOutputEnvelope(ValidationOutputEnvelope):
106
+ """Output envelope from FMI validator containers.
107
+
108
+ Note: outputs can be None for failure cases where simulation didn't complete.
109
+ """
110
+
111
+ outputs: FMIOutputs | None = None
112
+
113
+
114
+ def build_fmi_input_envelope(
115
+ *,
116
+ run_id: str,
117
+ validator,
118
+ org_id: str,
119
+ org_name: str,
120
+ workflow_id: str,
121
+ step_id: str,
122
+ step_name: str | None,
123
+ fmu_uri: str,
124
+ input_values: dict[str, Any],
125
+ callback_url: str,
126
+ execution_bundle_uri: str,
127
+ simulation: FMISimulationConfig | None = None,
128
+ output_variables: list[str] | None = None,
129
+ ) -> FMIInputEnvelope:
130
+ """
131
+ Build an FMIInputEnvelope from Django validation data.
132
+
133
+ Args:
134
+ run_id: ValidationRun ID
135
+ validator: Validator-like object (id/type/version attrs)
136
+ org_id: Organization ID
137
+ org_name: Organization name
138
+ workflow_id: Workflow ID
139
+ step_id: Workflow step ID
140
+ step_name: Optional step name
141
+ fmu_uri: FMU storage URI (gs://... or local path in dev)
142
+ input_values: Resolved inputs keyed by catalog slug
143
+ callback_url: URL to POST callback
144
+ execution_bundle_uri: Base URI/path for this run's files
145
+ simulation: Optional FMISimulationConfig
146
+ output_variables: Optional list of catalog slugs to capture (empty=all outputs)
147
+ """
148
+
149
+ input_files = [
150
+ InputFileItem(
151
+ name="model.fmu",
152
+ mime_type=SupportedMimeType.FMU,
153
+ role="fmu",
154
+ uri=fmu_uri,
155
+ )
156
+ ]
157
+
158
+ envelope_inputs = FMIInputs(
159
+ input_values=input_values,
160
+ simulation=simulation or FMISimulationConfig(),
161
+ output_variables=output_variables or [],
162
+ )
163
+
164
+ context = ExecutionContext(
165
+ callback_url=callback_url,
166
+ execution_bundle_uri=execution_bundle_uri,
167
+ )
168
+
169
+ envelope = FMIInputEnvelope(
170
+ run_id=run_id,
171
+ validator={
172
+ "id": str(validator.id),
173
+ "type": ValidatorType(validator.validation_type),
174
+ "version": getattr(validator, "version", "1.0.0"),
175
+ },
176
+ org={
177
+ "id": org_id,
178
+ "name": org_name,
179
+ },
180
+ workflow={
181
+ "id": workflow_id,
182
+ "step_id": step_id,
183
+ "step_name": step_name,
184
+ },
185
+ input_files=input_files,
186
+ inputs=envelope_inputs,
187
+ context=context,
188
+ )
189
+
190
+ return envelope