@snaptrude/plugin-core 0.8.0 → 0.9.0
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.
- package/CHANGELOG.md +16 -1
- package/api-manifest.full.json +1350 -89
- package/api-manifest.json +826 -40
- package/dist/api/analysis/daylight.d.ts +603 -0
- package/dist/api/analysis/daylight.d.ts.map +1 -0
- package/dist/api/analysis/heatmaps.d.ts +438 -14
- package/dist/api/analysis/heatmaps.d.ts.map +1 -1
- package/dist/api/analysis/index.d.ts +15 -0
- package/dist/api/analysis/index.d.ts.map +1 -1
- package/dist/api/analysis/solar.d.ts +249 -0
- package/dist/api/analysis/solar.d.ts.map +1 -0
- package/dist/api/analysis/weather.d.ts +193 -0
- package/dist/api/analysis/weather.d.ts.map +1 -0
- package/dist/api/core/camera/index.d.ts +37 -0
- package/dist/api/core/camera/index.d.ts.map +1 -1
- package/dist/api/core/geom/create/index.d.ts +318 -1
- package/dist/api/core/geom/create/index.d.ts.map +1 -1
- package/dist/api/core/index.d.ts +9 -0
- package/dist/api/core/index.d.ts.map +1 -1
- package/dist/api/core/io/export/index.d.ts +3 -1
- package/dist/api/core/io/export/index.d.ts.map +1 -1
- package/dist/api/core/io/import/index.d.ts +61 -0
- package/dist/api/core/io/import/index.d.ts.map +1 -1
- package/dist/api/core/mode/index.d.ts +99 -0
- package/dist/api/core/mode/index.d.ts.map +1 -0
- package/dist/api/core/storeys/index.d.ts +251 -0
- package/dist/api/core/storeys/index.d.ts.map +1 -0
- package/dist/api/core/tags.d.ts +24 -0
- package/dist/api/core/tags.d.ts.map +1 -1
- package/dist/api/core/zoom/index.d.ts +4 -0
- package/dist/api/core/zoom/index.d.ts.map +1 -1
- package/dist/api/design/create/index.d.ts +115 -4
- package/dist/api/design/create/index.d.ts.map +1 -1
- package/dist/api/design/delete/index.d.ts +3 -0
- package/dist/api/design/delete/index.d.ts.map +1 -1
- package/dist/api/design/query/geometry/index.d.ts +112 -0
- package/dist/api/design/query/geometry/index.d.ts.map +1 -1
- package/dist/api/design/query/index.d.ts +4 -0
- package/dist/api/design/query/index.d.ts.map +1 -1
- package/dist/api/design/query/referenceLines.d.ts +45 -0
- package/dist/api/design/query/referenceLines.d.ts.map +1 -0
- package/dist/api/design/query/spaces.d.ts +173 -0
- package/dist/api/design/query/spaces.d.ts.map +1 -1
- package/dist/api/design/transform/index.d.ts +12 -0
- package/dist/api/design/transform/index.d.ts.map +1 -1
- package/dist/api/design/update/index.d.ts +51 -1
- package/dist/api/design/update/index.d.ts.map +1 -1
- package/dist/api/entity/buildableEnvelope.d.ts +4 -0
- package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
- package/dist/api/entity/referenceLine.d.ts +8 -0
- package/dist/api/entity/referenceLine.d.ts.map +1 -1
- package/dist/api/entity/story.d.ts +20 -0
- package/dist/api/entity/story.d.ts.map +1 -1
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/presentation/export.d.ts +4 -0
- package/dist/api/presentation/export.d.ts.map +1 -1
- package/dist/api/presentation/import.d.ts +6 -0
- package/dist/api/presentation/import.d.ts.map +1 -1
- package/dist/api/presentation/index.d.ts +9 -0
- package/dist/api/presentation/index.d.ts.map +1 -1
- package/dist/api/presentation/placedViews.d.ts +370 -0
- package/dist/api/presentation/placedViews.d.ts.map +1 -0
- package/dist/api/presentation/shapes.d.ts +481 -0
- package/dist/api/presentation/shapes.d.ts.map +1 -0
- package/dist/api/program/site.d.ts +94 -0
- package/dist/api/program/site.d.ts.map +1 -1
- package/dist/api/program/spreadsheet.d.ts +260 -28
- package/dist/api/program/spreadsheet.d.ts.map +1 -1
- package/dist/api/workspace/index.d.ts +460 -0
- package/dist/api/workspace/index.d.ts.map +1 -0
- package/dist/index.cjs +2625 -1758
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2515 -1758
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/scripts/generate-manifest.mjs +45 -0
- package/src/api/analysis/daylight.ts +470 -0
- package/src/api/analysis/heatmaps.ts +444 -17
- package/src/api/analysis/index.ts +15 -0
- package/src/api/analysis/solar.ts +237 -0
- package/src/api/analysis/weather.ts +179 -0
- package/src/api/core/camera/index.ts +39 -0
- package/src/api/core/geom/create/index.ts +344 -1
- package/src/api/core/index.ts +9 -0
- package/src/api/core/io/export/index.ts +3 -1
- package/src/api/core/io/import/index.ts +64 -0
- package/src/api/core/mode/index.ts +96 -0
- package/src/api/core/storeys/index.ts +279 -0
- package/src/api/core/tags.ts +27 -0
- package/src/api/core/zoom/index.ts +4 -0
- package/src/api/design/create/index.ts +138 -1
- package/src/api/design/delete/index.ts +3 -0
- package/src/api/design/query/geometry/index.ts +125 -3
- package/src/api/design/query/index.ts +4 -0
- package/src/api/design/query/referenceLines.ts +52 -0
- package/src/api/design/query/spaces.ts +143 -0
- package/src/api/design/transform/index.ts +12 -0
- package/src/api/design/update/index.ts +66 -1
- package/src/api/entity/buildableEnvelope.ts +4 -0
- package/src/api/entity/referenceLine.ts +8 -0
- package/src/api/entity/story.ts +20 -0
- package/src/api/index.ts +5 -0
- package/src/api/presentation/export.ts +4 -0
- package/src/api/presentation/import.ts +6 -0
- package/src/api/presentation/index.ts +9 -0
- package/src/api/presentation/placedViews.ts +363 -0
- package/src/api/presentation/shapes.ts +274 -0
- package/src/api/program/site.ts +95 -0
- package/src/api/program/spreadsheet.ts +251 -6
- package/src/api/workspace/index.ts +516 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as z from "zod"
|
|
2
|
+
import { PluginApiReturn } from "../../types"
|
|
3
|
+
import { PluginWeatherSourceMeta } from "./weather"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Analysis solar — sample solar exposure at caller-chosen points.
|
|
7
|
+
*
|
|
8
|
+
* Unlike the heatmap studies (which raster the model's own surfaces), this
|
|
9
|
+
* samples solar exposure at **points you supply** — an instant (sun visible? /
|
|
10
|
+
* irradiance now) or aggregated over a date range (shaded fraction / cumulative
|
|
11
|
+
* irradiance). Positions are **world plan-space** coordinates, the same units
|
|
12
|
+
* and frame as plugin geometry reads; the backend echoes them verbatim and
|
|
13
|
+
* never re-projects.
|
|
14
|
+
*
|
|
15
|
+
* Raw numbers only: this renders no heatmap, mutates no geometry, and writes no
|
|
16
|
+
* save commands — visualization is the plugin's job. Large grids run as a job
|
|
17
|
+
* (`status: "running"` until complete); every result carries `modelRevision` +
|
|
18
|
+
* `inputHash` so the caller can mark it stale after a geometry change.
|
|
19
|
+
*
|
|
20
|
+
* Accessed via `snaptrude.analysis.solar`.
|
|
21
|
+
*/
|
|
22
|
+
export abstract class PluginAnalysisSolarApi {
|
|
23
|
+
constructor() {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sample solar exposure at a set of points.
|
|
27
|
+
*
|
|
28
|
+
* Provide **exactly one** timing mode: `dateTime` (a single instant →
|
|
29
|
+
* `mode: "instant"`, results in `W/m²`), or `startDate` + `endDate` (a range
|
|
30
|
+
* → `mode: "range"`, results aggregated in `kWh/m²`). `results` is paged —
|
|
31
|
+
* follow `nextCursor` until it is `null`. Each point's optional `normal`
|
|
32
|
+
* gives plane-of-array irradiance; omit it for a horizontal sample.
|
|
33
|
+
*
|
|
34
|
+
* @param args - {@linkcode PluginSolarSampleGridArgs}: the `points`, exactly
|
|
35
|
+
* one timing mode (`dateTime` XOR `startDate`+`endDate`), and an optional
|
|
36
|
+
* `includeHourlyArtifact` to attach a downloadable per-hour series.
|
|
37
|
+
* @returns A {@linkcode PluginSolarSampleGridResult} — the run `status`,
|
|
38
|
+
* `mode`/`units`, provenance (`weather`, `modelRevision`, `inputHash`),
|
|
39
|
+
* this page of `results`, an optional `hourlyArtifact`, and `nextCursor`.
|
|
40
|
+
* @throws When the project has no geo-located site (no location to sample
|
|
41
|
+
* solar exposure for).
|
|
42
|
+
*
|
|
43
|
+
* @examplePrompt What's the irradiance at these facade points at noon on June 21?
|
|
44
|
+
* @examplePrompt How shaded are these balcony points across the summer?
|
|
45
|
+
* @examplePrompt Sample direct sun visibility at these roof points right now
|
|
46
|
+
*
|
|
47
|
+
* # Example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const grid = await snaptrude.analysis.solar.sampleGrid({
|
|
50
|
+
* points: [
|
|
51
|
+
* { id: "a", position: [0, 3, 0] },
|
|
52
|
+
* { id: "b", position: [5, 3, 0], normal: [0, 0, 1] },
|
|
53
|
+
* ],
|
|
54
|
+
* dateTime: "2026-06-21T12:00:00+05:30",
|
|
55
|
+
* })
|
|
56
|
+
* for (const r of grid.results) {
|
|
57
|
+
* console.log(r.id, r.directSunVisible, r.totalIrradiance, grid.units)
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
public abstract sampleGrid(
|
|
62
|
+
args: PluginSolarSampleGridArgs,
|
|
63
|
+
): PluginApiReturn<PluginSolarSampleGridResult>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Status of a {@linkcode PluginAnalysisSolarApi.sampleGrid} run.
|
|
68
|
+
*
|
|
69
|
+
* | Value | Meaning |
|
|
70
|
+
* |---|---|
|
|
71
|
+
* | `"running"` | The job is in flight — poll again |
|
|
72
|
+
* | `"complete"` | Results are final |
|
|
73
|
+
* | `"cancelled"` | The run was cancelled |
|
|
74
|
+
* | `"failed"` | The run failed |
|
|
75
|
+
*/
|
|
76
|
+
export const PluginSolarRunStatus = z.enum([
|
|
77
|
+
"running",
|
|
78
|
+
"complete",
|
|
79
|
+
"cancelled",
|
|
80
|
+
"failed",
|
|
81
|
+
])
|
|
82
|
+
export type PluginSolarRunStatus = z.infer<typeof PluginSolarRunStatus>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Timing mode of the run — chosen by which timing args were supplied.
|
|
86
|
+
*
|
|
87
|
+
* | Value | Meaning |
|
|
88
|
+
* |---|---|
|
|
89
|
+
* | `"instant"` | Single `dateTime` — irradiance in `W/m²` |
|
|
90
|
+
* | `"range"` | `startDate`+`endDate` — irradiance aggregated in `kWh/m²` |
|
|
91
|
+
*/
|
|
92
|
+
export const PluginSolarSampleMode = z.enum(["instant", "range"])
|
|
93
|
+
export type PluginSolarSampleMode = z.infer<typeof PluginSolarSampleMode>
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Irradiance units of the results — `W/m²` (instant) or `kWh/m²` (range).
|
|
97
|
+
*/
|
|
98
|
+
export const PluginSolarUnits = z.enum(["W/m2", "kWh/m2"])
|
|
99
|
+
export type PluginSolarUnits = z.infer<typeof PluginSolarUnits>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Downloadable per-hour artifact (present only when `includeHourlyArtifact`).
|
|
103
|
+
*
|
|
104
|
+
* | Property | Type | Description |
|
|
105
|
+
* |---|---|---|
|
|
106
|
+
* | `url` | `string` | Signed download URL |
|
|
107
|
+
* | `format` | `"parquet" \| "csv-gz"` | File format |
|
|
108
|
+
* | `expiresAt` | `string` | ISO 8601 expiry of the signed URL |
|
|
109
|
+
*/
|
|
110
|
+
export const PluginSolarHourlyArtifact = z.object({
|
|
111
|
+
url: z.string(),
|
|
112
|
+
format: z.enum(["parquet", "csv-gz"]),
|
|
113
|
+
expiresAt: z.string(),
|
|
114
|
+
})
|
|
115
|
+
export type PluginSolarHourlyArtifact = z.infer<
|
|
116
|
+
typeof PluginSolarHourlyArtifact
|
|
117
|
+
>
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A caller-supplied point to sample.
|
|
121
|
+
*
|
|
122
|
+
* | Property | Type | Description |
|
|
123
|
+
* |---|---|---|
|
|
124
|
+
* | `id` | `string` | Caller-chosen id, echoed back verbatim on the result |
|
|
125
|
+
* | `position` | `[number, number, number]` | World plan-space `[x, y, z]` |
|
|
126
|
+
* | `normal` | `[number, number, number]`? | Surface normal for plane-of-array; omit = horizontal |
|
|
127
|
+
*/
|
|
128
|
+
export const PluginSolarSamplePoint = z.object({
|
|
129
|
+
id: z.string(),
|
|
130
|
+
position: z.tuple([z.number(), z.number(), z.number()]),
|
|
131
|
+
normal: z.tuple([z.number(), z.number(), z.number()]).optional(),
|
|
132
|
+
})
|
|
133
|
+
export type PluginSolarSamplePoint = z.infer<typeof PluginSolarSamplePoint>
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Arguments for {@linkcode PluginAnalysisSolarApi.sampleGrid}.
|
|
137
|
+
*
|
|
138
|
+
* Provide **exactly one** timing mode: `dateTime` (instant) XOR
|
|
139
|
+
* `startDate`+`endDate` (range).
|
|
140
|
+
*
|
|
141
|
+
* | Property | Type | Description |
|
|
142
|
+
* |---|---|---|
|
|
143
|
+
* | `points` | {@linkcode PluginSolarSamplePoint}`[]` | Points to sample |
|
|
144
|
+
* | `dateTime` | `string`? | Instant mode: local ISO 8601 with offset |
|
|
145
|
+
* | `startDate` | `string`? | Range mode: inclusive start `"YYYY-MM-DD"` |
|
|
146
|
+
* | `endDate` | `string`? | Range mode: inclusive end `"YYYY-MM-DD"` |
|
|
147
|
+
* | `includeHourlyArtifact` | `boolean`? | Attach a downloadable per-hour series |
|
|
148
|
+
*/
|
|
149
|
+
export const PluginSolarSampleGridArgs = z
|
|
150
|
+
.object({
|
|
151
|
+
points: z.array(PluginSolarSamplePoint),
|
|
152
|
+
dateTime: z.string().optional(),
|
|
153
|
+
startDate: z.string().optional(),
|
|
154
|
+
endDate: z.string().optional(),
|
|
155
|
+
includeHourlyArtifact: z.boolean().optional(),
|
|
156
|
+
})
|
|
157
|
+
.superRefine((a, ctx) => {
|
|
158
|
+
const hasInstant = a.dateTime !== undefined
|
|
159
|
+
const hasStart = a.startDate !== undefined
|
|
160
|
+
const hasEnd = a.endDate !== undefined
|
|
161
|
+
const hasRange = hasStart || hasEnd
|
|
162
|
+
if (hasInstant === hasRange) {
|
|
163
|
+
ctx.addIssue({
|
|
164
|
+
code: z.ZodIssueCode.custom,
|
|
165
|
+
message:
|
|
166
|
+
"provide exactly one timing mode: dateTime (instant), or startDate+endDate (range)",
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
if (hasRange && !(hasStart && hasEnd)) {
|
|
170
|
+
ctx.addIssue({
|
|
171
|
+
code: z.ZodIssueCode.custom,
|
|
172
|
+
message: "range mode requires both startDate and endDate",
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
export type PluginSolarSampleGridArgs = z.infer<
|
|
177
|
+
typeof PluginSolarSampleGridArgs
|
|
178
|
+
>
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* One sampled point's result. Irradiance fields are in the enclosing result's
|
|
182
|
+
* `units` and are omitted when the backend did not compute them.
|
|
183
|
+
*
|
|
184
|
+
* | Property | Type | Description |
|
|
185
|
+
* |---|---|---|
|
|
186
|
+
* | `id` | `string` | The point's caller-chosen id |
|
|
187
|
+
* | `directSunVisible` | `boolean` | Instant mode: sun disc visible from the point |
|
|
188
|
+
* | `shadeFraction` | `number` | `0`..`1`; range mode: fraction of sun-up hours shaded |
|
|
189
|
+
* | `directIrradiance` | `number`? | Direct component, in the result's `units` |
|
|
190
|
+
* | `diffuseIrradiance` | `number`? | Diffuse component, in the result's `units` |
|
|
191
|
+
* | `totalIrradiance` | `number`? | Total (direct + diffuse), in the result's `units` |
|
|
192
|
+
*/
|
|
193
|
+
export const PluginSolarSampleResult = z.object({
|
|
194
|
+
id: z.string(),
|
|
195
|
+
directSunVisible: z.boolean(),
|
|
196
|
+
shadeFraction: z.number(),
|
|
197
|
+
directIrradiance: z.number().optional(),
|
|
198
|
+
diffuseIrradiance: z.number().optional(),
|
|
199
|
+
totalIrradiance: z.number().optional(),
|
|
200
|
+
})
|
|
201
|
+
export type PluginSolarSampleResult = z.infer<typeof PluginSolarSampleResult>
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Result of {@linkcode PluginAnalysisSolarApi.sampleGrid} — one page.
|
|
205
|
+
*
|
|
206
|
+
* | Property | Type | Description |
|
|
207
|
+
* |---|---|---|
|
|
208
|
+
* | `status` | {@linkcode PluginSolarRunStatus} | Run status |
|
|
209
|
+
* | `mode` | {@linkcode PluginSolarSampleMode} | `"instant"` or `"range"` |
|
|
210
|
+
* | `coordinateSystem` | `"world-plan-space"` | Echo of the input frame |
|
|
211
|
+
* | `modelRevision` | `string` | Geometry revision sampled against (staleness key) |
|
|
212
|
+
* | `inputHash` | `string` | Hash of points+dates+geometry (staleness key) |
|
|
213
|
+
* | `units` | {@linkcode PluginSolarUnits} | `"W/m2"` (instant) or `"kWh/m2"` (range) |
|
|
214
|
+
* | `sunUpHours` | `number` | Sun-up hours over the sampled window |
|
|
215
|
+
* | `weather` | {@linkcode PluginWeatherSourceMeta} \| `null` | Resolved weather file, or `null` |
|
|
216
|
+
* | `weatherQuality` | `string[]` | Deduped quality flags present in the consumed rows |
|
|
217
|
+
* | `results` | {@linkcode PluginSolarSampleResult}`[]` | This page of per-point results |
|
|
218
|
+
* | `hourlyArtifact` | {@linkcode PluginSolarHourlyArtifact} \| `null` | Per-hour download, or `null` |
|
|
219
|
+
* | `nextCursor` | `string \| null` | Next page cursor; `null` = last page |
|
|
220
|
+
*/
|
|
221
|
+
export const PluginSolarSampleGridResult = z.object({
|
|
222
|
+
status: PluginSolarRunStatus,
|
|
223
|
+
mode: PluginSolarSampleMode,
|
|
224
|
+
coordinateSystem: z.literal("world-plan-space"),
|
|
225
|
+
modelRevision: z.string(),
|
|
226
|
+
inputHash: z.string(),
|
|
227
|
+
units: PluginSolarUnits,
|
|
228
|
+
sunUpHours: z.number(),
|
|
229
|
+
weather: PluginWeatherSourceMeta.nullable(),
|
|
230
|
+
weatherQuality: z.array(z.string()),
|
|
231
|
+
results: z.array(PluginSolarSampleResult),
|
|
232
|
+
hourlyArtifact: PluginSolarHourlyArtifact.nullable(),
|
|
233
|
+
nextCursor: z.string().nullable(),
|
|
234
|
+
})
|
|
235
|
+
export type PluginSolarSampleGridResult = z.infer<
|
|
236
|
+
typeof PluginSolarSampleGridResult
|
|
237
|
+
>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as z from "zod"
|
|
2
|
+
import { PluginApiReturn } from "../../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Analysis weather — read the project site's typical-year weather series.
|
|
6
|
+
*
|
|
7
|
+
* Returns the hourly weather rows (temperature, humidity, wind, solar
|
|
8
|
+
* irradiance) of the EPW file the sustainability analyses compute against,
|
|
9
|
+
* resolved from the project's geo-located site. This is a **synchronous,
|
|
10
|
+
* paged read** of an already-resolved weather file — not a job: call
|
|
11
|
+
* {@linkcode PluginAnalysisWeatherApi.getSeries} for a date range, then follow
|
|
12
|
+
* `nextCursor` until it is `null`.
|
|
13
|
+
*
|
|
14
|
+
* The weather file is resolved by the **same** code path the daylight /
|
|
15
|
+
* sunlight-hours analyses use, so the `source.weatherFileId` here matches the
|
|
16
|
+
* provenance a {@linkcode PluginAnalysisDaylightApi} run echoes. Dates cross as
|
|
17
|
+
* ISO 8601 date strings (`"YYYY-MM-DD"`); each row's `timestamp` is a local
|
|
18
|
+
* ISO 8601 string **with** its UTC offset, and `utcOffsetMinutes` restates the
|
|
19
|
+
* offset so consumers never re-derive it.
|
|
20
|
+
*
|
|
21
|
+
* Reads never throw for "no data": when the project has no geo-located site
|
|
22
|
+
* (no weather file to resolve) the host surfaces that per its read contract.
|
|
23
|
+
*
|
|
24
|
+
* Accessed via `snaptrude.analysis.weather`.
|
|
25
|
+
*/
|
|
26
|
+
export abstract class PluginAnalysisWeatherApi {
|
|
27
|
+
constructor() {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get the hourly weather series for a date range, paged.
|
|
31
|
+
*
|
|
32
|
+
* Returns the resolved weather file's provenance (`source`) plus the weather
|
|
33
|
+
* `rows` for the requested range. The page size is backend-chosen; when
|
|
34
|
+
* `nextCursor` is a string, pass it back as `args.cursor` to fetch the next
|
|
35
|
+
* page, and stop when it is `null`.
|
|
36
|
+
*
|
|
37
|
+
* @param args - {@linkcode PluginWeatherGetSeriesArgs}: the inclusive
|
|
38
|
+
* `startDate`/`endDate` ISO dates, an optional `interval` (`"hourly"` is
|
|
39
|
+
* the only v1 value), and an optional `cursor` for paging.
|
|
40
|
+
* @returns A {@linkcode PluginWeatherSeriesResult} — the resolved weather
|
|
41
|
+
* `source`, the `rows` for this page, and `nextCursor` (`null` at the end).
|
|
42
|
+
*
|
|
43
|
+
* @examplePrompt Get the weather series for June at my site
|
|
44
|
+
* @examplePrompt Pull the hourly temperature and irradiance for this location
|
|
45
|
+
* @examplePrompt What EPW weather file is my project using?
|
|
46
|
+
*
|
|
47
|
+
* # Example
|
|
48
|
+
* ```ts
|
|
49
|
+
* let cursor: string | undefined = undefined
|
|
50
|
+
* const rows = []
|
|
51
|
+
* do {
|
|
52
|
+
* const page = await snaptrude.analysis.weather.getSeries({
|
|
53
|
+
* startDate: "2026-06-01",
|
|
54
|
+
* endDate: "2026-06-30",
|
|
55
|
+
* cursor,
|
|
56
|
+
* })
|
|
57
|
+
* rows.push(...page.rows)
|
|
58
|
+
* cursor = page.nextCursor ?? undefined
|
|
59
|
+
* } while (cursor)
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
public abstract getSeries(
|
|
63
|
+
args: PluginWeatherGetSeriesArgs,
|
|
64
|
+
): PluginApiReturn<PluginWeatherSeriesResult>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* How the weather file was matched to the project site.
|
|
69
|
+
*
|
|
70
|
+
* | Value | Meaning |
|
|
71
|
+
* |---|---|
|
|
72
|
+
* | `"nearest-station"` | Auto-resolved to the closest weather station |
|
|
73
|
+
* | `"user-upload"` | A weather file the user uploaded (`core.io.import.epw`) |
|
|
74
|
+
* | `"project-override"` | A station pinned for the project regardless of distance |
|
|
75
|
+
*/
|
|
76
|
+
export const PluginWeatherSelectionMethod = z.enum([
|
|
77
|
+
"nearest-station",
|
|
78
|
+
"user-upload",
|
|
79
|
+
"project-override",
|
|
80
|
+
])
|
|
81
|
+
export type PluginWeatherSelectionMethod = z.infer<
|
|
82
|
+
typeof PluginWeatherSelectionMethod
|
|
83
|
+
>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Provenance of the resolved weather file — where the numbers came from.
|
|
87
|
+
*
|
|
88
|
+
* | Property | Type | Description |
|
|
89
|
+
* |---|---|---|
|
|
90
|
+
* | `weatherFileId` | `string` | Catalog id (same key as `core.io.import.epw`'s `weatherFileId`) |
|
|
91
|
+
* | `station` | `string` | Station/file name, e.g. `"USA_IL_Chicago-OHare.Intl.AP.725300_TMY3"` |
|
|
92
|
+
* | `sourceId` | `string` | WMO/station identifier from the EPW header |
|
|
93
|
+
* | `distanceKm` | `number \| null` | Station → project site distance, km; `null` for pinned project-override files |
|
|
94
|
+
* | `selectionMethod` | {@linkcode PluginWeatherSelectionMethod} | How the file was matched |
|
|
95
|
+
* | `checksum` | `string` | sha256 of the EPW file — the provenance key |
|
|
96
|
+
* | `windReferenceHeightM` | `number` | Anemometer height the wind rows are valid at |
|
|
97
|
+
*/
|
|
98
|
+
export const PluginWeatherSourceMeta = z.object({
|
|
99
|
+
weatherFileId: z.string(),
|
|
100
|
+
station: z.string(),
|
|
101
|
+
sourceId: z.string(),
|
|
102
|
+
distanceKm: z.number().nullable(),
|
|
103
|
+
selectionMethod: PluginWeatherSelectionMethod,
|
|
104
|
+
checksum: z.string(),
|
|
105
|
+
windReferenceHeightM: z.number(),
|
|
106
|
+
})
|
|
107
|
+
export type PluginWeatherSourceMeta = z.infer<typeof PluginWeatherSourceMeta>
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One hourly weather reading.
|
|
111
|
+
*
|
|
112
|
+
* `flags` is empty for a clean measured/typical value; known flag strings are
|
|
113
|
+
* `"missing"`, `"substituted"`, and `"interpolated"` (kept as `string[]` so the
|
|
114
|
+
* backend can add flag kinds without a contract break).
|
|
115
|
+
*
|
|
116
|
+
* | Property | Type | Description |
|
|
117
|
+
* |---|---|---|
|
|
118
|
+
* | `timestamp` | `string` | Local ISO 8601 with offset, e.g. `"2026-06-21T14:00:00+05:30"` |
|
|
119
|
+
* | `utcOffsetMinutes` | `number` | UTC offset in minutes (restates the timestamp's offset) |
|
|
120
|
+
* | `dryBulbC` | `number` | Dry-bulb temperature, °C |
|
|
121
|
+
* | `relativeHumidityPercent` | `number` | Relative humidity, % |
|
|
122
|
+
* | `windSpeedMps` | `number` | Wind speed, m/s (at `source.windReferenceHeightM`) |
|
|
123
|
+
* | `ghiWm2` | `number` | Global horizontal irradiance, W/m² |
|
|
124
|
+
* | `dniWm2` | `number` | Direct normal irradiance, W/m² |
|
|
125
|
+
* | `dhiWm2` | `number` | Diffuse horizontal irradiance, W/m² |
|
|
126
|
+
* | `flags` | `string[]` | Quality flags; `[]` = clean value |
|
|
127
|
+
*/
|
|
128
|
+
export const PluginWeatherRow = z.object({
|
|
129
|
+
timestamp: z.string(),
|
|
130
|
+
utcOffsetMinutes: z.number(),
|
|
131
|
+
dryBulbC: z.number(),
|
|
132
|
+
relativeHumidityPercent: z.number(),
|
|
133
|
+
windSpeedMps: z.number(),
|
|
134
|
+
ghiWm2: z.number(),
|
|
135
|
+
dniWm2: z.number(),
|
|
136
|
+
dhiWm2: z.number(),
|
|
137
|
+
flags: z.array(z.string()),
|
|
138
|
+
})
|
|
139
|
+
export type PluginWeatherRow = z.infer<typeof PluginWeatherRow>
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Arguments for {@linkcode PluginAnalysisWeatherApi.getSeries}.
|
|
143
|
+
*
|
|
144
|
+
* | Property | Type | Description |
|
|
145
|
+
* |---|---|---|
|
|
146
|
+
* | `startDate` | `string` | Inclusive range start, ISO `"YYYY-MM-DD"` |
|
|
147
|
+
* | `endDate` | `string` | Inclusive range end, ISO `"YYYY-MM-DD"` |
|
|
148
|
+
* | `interval` | `"hourly"`? | Sampling interval; `"hourly"` is the only v1 value |
|
|
149
|
+
* | `cursor` | `string`? | Paging cursor from a prior page's `nextCursor` |
|
|
150
|
+
*/
|
|
151
|
+
export const PluginWeatherGetSeriesArgs = z.object({
|
|
152
|
+
startDate: z.string(),
|
|
153
|
+
endDate: z.string(),
|
|
154
|
+
interval: z.enum(["hourly"]).optional(),
|
|
155
|
+
cursor: z.string().optional(),
|
|
156
|
+
})
|
|
157
|
+
export type PluginWeatherGetSeriesArgs = z.infer<
|
|
158
|
+
typeof PluginWeatherGetSeriesArgs
|
|
159
|
+
>
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Result of {@linkcode PluginAnalysisWeatherApi.getSeries} — one page.
|
|
163
|
+
*
|
|
164
|
+
* | Property | Type | Description |
|
|
165
|
+
* |---|---|---|
|
|
166
|
+
* | `source` | {@linkcode PluginWeatherSourceMeta} | The resolved weather file's provenance |
|
|
167
|
+
* | `rows` | {@linkcode PluginWeatherRow}`[]` | Weather rows for this page |
|
|
168
|
+
* | `nextCursor` | `string \| null` | Pass back as `args.cursor`; `null` = last page |
|
|
169
|
+
* | `warnings` | `string[]`? | Degradation notices (e.g. `"override lookup failed; served nearest-station"`); omitted when the page is clean |
|
|
170
|
+
*/
|
|
171
|
+
export const PluginWeatherSeriesResult = z.object({
|
|
172
|
+
source: PluginWeatherSourceMeta,
|
|
173
|
+
rows: z.array(PluginWeatherRow),
|
|
174
|
+
nextCursor: z.string().nullable(),
|
|
175
|
+
warnings: z.array(z.string()).optional(),
|
|
176
|
+
})
|
|
177
|
+
export type PluginWeatherSeriesResult = z.infer<
|
|
178
|
+
typeof PluginWeatherSeriesResult
|
|
179
|
+
>
|
|
@@ -128,6 +128,45 @@ export abstract class PluginCameraApi {
|
|
|
128
128
|
public abstract setProjection(
|
|
129
129
|
projection: PluginCameraProjection,
|
|
130
130
|
): PluginApiReturn<boolean>
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Zoom (fit) the camera to all geometry in the scene — the "zoom extents"
|
|
134
|
+
* action. View-state only — not undoable. Canonical home of the zoom-extents
|
|
135
|
+
* read (the deprecated `core.zoom.extents`).
|
|
136
|
+
*
|
|
137
|
+
* @returns `true` once the camera has been framed to the scene.
|
|
138
|
+
*
|
|
139
|
+
* @examplePrompt Zoom out so I can see the whole model
|
|
140
|
+
* @examplePrompt Fit everything in the view
|
|
141
|
+
* @examplePrompt Frame the entire building in the viewport
|
|
142
|
+
* @examplePrompt Do a zoom extents on the canvas
|
|
143
|
+
*
|
|
144
|
+
* # Example
|
|
145
|
+
* ```ts
|
|
146
|
+
* await snaptrude.core.camera.zoomExtents()
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
public abstract zoomExtents(): PluginApiReturn<boolean>
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Zoom (fit) the camera to the current selection. View-state only — not
|
|
153
|
+
* undoable. Canonical home of the zoom-to-selection read (the deprecated
|
|
154
|
+
* `core.zoom.selection`).
|
|
155
|
+
*
|
|
156
|
+
* @returns `true` if there was a selection to frame; `false` if nothing is
|
|
157
|
+
* selected (no-op).
|
|
158
|
+
*
|
|
159
|
+
* @examplePrompt Zoom in on what I have selected
|
|
160
|
+
* @examplePrompt Frame the selected walls in the view
|
|
161
|
+
* @examplePrompt Focus the camera on my current selection
|
|
162
|
+
* @examplePrompt Fit the view to the selected room
|
|
163
|
+
*
|
|
164
|
+
* # Example
|
|
165
|
+
* ```ts
|
|
166
|
+
* await snaptrude.core.camera.zoomSelection()
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
public abstract zoomSelection(): PluginApiReturn<boolean>
|
|
131
170
|
}
|
|
132
171
|
|
|
133
172
|
/**
|