@snaptrude/plugin-core 0.9.16 → 0.9.18-edgesetbacks-20261021.1

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 (221) hide show
  1. package/AGENTS.md +231 -231
  2. package/CHANGELOG.md +408 -402
  3. package/CLAUDE.md +11 -11
  4. package/api-manifest.json +1510 -701
  5. package/dist/api/analysis/daylight.d.ts +5 -5
  6. package/dist/api/analysis/solar.d.ts +2 -2
  7. package/dist/api/core/camera/index.d.ts +29 -0
  8. package/dist/api/core/camera/index.d.ts.map +1 -1
  9. package/dist/api/core/geom/create/index.d.ts +7 -1
  10. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  11. package/dist/api/core/geom/update/contour.d.ts +8 -2
  12. package/dist/api/core/geom/update/contour.d.ts.map +1 -1
  13. package/dist/api/core/storeys/index.d.ts +5 -2
  14. package/dist/api/core/storeys/index.d.ts.map +1 -1
  15. package/dist/api/core/zoom/index.d.ts +13 -0
  16. package/dist/api/core/zoom/index.d.ts.map +1 -1
  17. package/dist/api/design/bim.d.ts +299 -0
  18. package/dist/api/design/bim.d.ts.map +1 -0
  19. package/dist/api/design/boolean/index.d.ts +2 -0
  20. package/dist/api/design/boolean/index.d.ts.map +1 -1
  21. package/dist/api/design/catalog.d.ts +320 -0
  22. package/dist/api/design/catalog.d.ts.map +1 -0
  23. package/dist/api/design/create/bulk-items.d.ts +65 -25
  24. package/dist/api/design/create/bulk-items.d.ts.map +1 -1
  25. package/dist/api/design/create/index.d.ts +299 -100
  26. package/dist/api/design/create/index.d.ts.map +1 -1
  27. package/dist/api/design/create/opening-fields.d.ts +13 -12
  28. package/dist/api/design/create/opening-fields.d.ts.map +1 -1
  29. package/dist/api/design/delete/index.d.ts +1 -0
  30. package/dist/api/design/delete/index.d.ts.map +1 -1
  31. package/dist/api/design/doors/index.d.ts +18 -24
  32. package/dist/api/design/doors/index.d.ts.map +1 -1
  33. package/dist/api/design/family.d.ts +813 -35
  34. package/dist/api/design/family.d.ts.map +1 -1
  35. package/dist/api/design/index.d.ts +16 -0
  36. package/dist/api/design/index.d.ts.map +1 -1
  37. package/dist/api/design/networks.d.ts +1703 -0
  38. package/dist/api/design/networks.d.ts.map +1 -0
  39. package/dist/api/design/query/grids.d.ts +2 -2
  40. package/dist/api/design/query/index.d.ts +42 -32
  41. package/dist/api/design/query/index.d.ts.map +1 -1
  42. package/dist/api/design/selection/index.d.ts +10 -10
  43. package/dist/api/design/types/index.d.ts +40 -24
  44. package/dist/api/design/types/index.d.ts.map +1 -1
  45. package/dist/api/design/update/index.d.ts +111 -6
  46. package/dist/api/design/update/index.d.ts.map +1 -1
  47. package/dist/api/design/windows/index.d.ts +8 -8
  48. package/dist/api/design/windows/index.d.ts.map +1 -1
  49. package/dist/api/entity/buildableEnvelope.d.ts +84 -2
  50. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  51. package/dist/api/presentation/aiInspiration.d.ts +10 -10
  52. package/dist/api/presentation/annotate.d.ts +38 -22
  53. package/dist/api/presentation/annotate.d.ts.map +1 -1
  54. package/dist/api/presentation/drawingContract.d.ts +32 -0
  55. package/dist/api/presentation/drawingContract.d.ts.map +1 -0
  56. package/dist/api/presentation/drawings.d.ts +3523 -0
  57. package/dist/api/presentation/drawings.d.ts.map +1 -0
  58. package/dist/api/presentation/export.d.ts +23 -5
  59. package/dist/api/presentation/export.d.ts.map +1 -1
  60. package/dist/api/presentation/index.d.ts +41 -12
  61. package/dist/api/presentation/index.d.ts.map +1 -1
  62. package/dist/api/presentation/placedViews.d.ts +106 -6
  63. package/dist/api/presentation/placedViews.d.ts.map +1 -1
  64. package/dist/api/presentation/sections.d.ts +461 -0
  65. package/dist/api/presentation/sections.d.ts.map +1 -0
  66. package/dist/api/presentation/shapes.d.ts +2 -2
  67. package/dist/api/presentation/sheets.d.ts +175 -19
  68. package/dist/api/presentation/sheets.d.ts.map +1 -1
  69. package/dist/api/presentation/viewPresets.d.ts +222 -0
  70. package/dist/api/presentation/viewPresets.d.ts.map +1 -0
  71. package/dist/api/presentation/views.d.ts +141 -8
  72. package/dist/api/presentation/views.d.ts.map +1 -1
  73. package/dist/api/presentation/walkthroughs.d.ts +6 -6
  74. package/dist/errors/codes.d.ts +3 -1
  75. package/dist/errors/codes.d.ts.map +1 -1
  76. package/dist/index.cjs +3883 -2385
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.js +3744 -2385
  79. package/dist/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/scripts/check-dist-fresh.mjs +22 -22
  82. package/scripts/generate-manifest.mjs +243 -238
  83. package/scripts/generate-manifest.test.mjs +303 -303
  84. package/src/api/analysis/daylight.ts +470 -470
  85. package/src/api/analysis/heatmaps.ts +683 -683
  86. package/src/api/analysis/illuminance.ts +155 -155
  87. package/src/api/analysis/index.ts +61 -61
  88. package/src/api/analysis/shadows.ts +183 -183
  89. package/src/api/analysis/solar.ts +237 -237
  90. package/src/api/analysis/sunlightHours.ts +211 -211
  91. package/src/api/analysis/sunpath.ts +83 -83
  92. package/src/api/analysis/weather.ts +179 -179
  93. package/src/api/core/buildings.ts +485 -485
  94. package/src/api/core/camera/index.ts +696 -667
  95. package/src/api/core/comment/index.ts +376 -376
  96. package/src/api/core/geom/create/index.ts +2139 -2133
  97. package/src/api/core/geom/delete/index.ts +84 -84
  98. package/src/api/core/geom/index.ts +35 -35
  99. package/src/api/core/geom/query/arc.ts +169 -169
  100. package/src/api/core/geom/query/brep.ts +621 -621
  101. package/src/api/core/geom/query/circle.ts +404 -404
  102. package/src/api/core/geom/query/contour.ts +540 -540
  103. package/src/api/core/geom/query/curve.ts +1102 -1102
  104. package/src/api/core/geom/query/edge.ts +130 -130
  105. package/src/api/core/geom/query/face.ts +508 -508
  106. package/src/api/core/geom/query/halfedge.ts +210 -210
  107. package/src/api/core/geom/query/index.ts +63 -63
  108. package/src/api/core/geom/query/profile.ts +473 -473
  109. package/src/api/core/geom/query/vertex.ts +205 -205
  110. package/src/api/core/geom/update/contour.ts +323 -317
  111. package/src/api/core/geom/update/curve.ts +209 -209
  112. package/src/api/core/geom/update/index.ts +29 -29
  113. package/src/api/core/geom/update/profile.ts +379 -379
  114. package/src/api/core/groups.ts +521 -521
  115. package/src/api/core/handles/index.ts +234 -234
  116. package/src/api/core/history/index.ts +84 -84
  117. package/src/api/core/index.ts +99 -99
  118. package/src/api/core/io/export/index.ts +126 -126
  119. package/src/api/core/io/import/index.ts +504 -504
  120. package/src/api/core/io/index.ts +42 -42
  121. package/src/api/core/io/job/index.ts +140 -140
  122. package/src/api/core/io/query/index.ts +71 -71
  123. package/src/api/core/io/terrain/index.ts +360 -360
  124. package/src/api/core/io/underlay/index.ts +705 -705
  125. package/src/api/core/layers.ts +540 -540
  126. package/src/api/core/math/index.ts +20 -20
  127. package/src/api/core/math/quat.ts +172 -172
  128. package/src/api/core/math/vec3.ts +179 -179
  129. package/src/api/core/mode/index.ts +108 -108
  130. package/src/api/core/project/index.ts +479 -479
  131. package/src/api/core/proposals/index.ts +570 -570
  132. package/src/api/core/storeys/index.ts +311 -308
  133. package/src/api/core/storeys/layers.ts +339 -339
  134. package/src/api/core/tags.ts +911 -911
  135. package/src/api/core/units/index.ts +164 -164
  136. package/src/api/core/user.ts +46 -46
  137. package/src/api/core/zoom/index.ts +64 -51
  138. package/src/api/design/bim.ts +210 -0
  139. package/src/api/design/boolean/index.ts +181 -179
  140. package/src/api/design/catalog.ts +203 -0
  141. package/src/api/design/create/bulk-items.ts +294 -254
  142. package/src/api/design/create/grids.ts +137 -137
  143. package/src/api/design/create/index.ts +2178 -1980
  144. package/src/api/design/create/opening-fields.ts +40 -37
  145. package/src/api/design/delete/index.ts +75 -74
  146. package/src/api/design/dimensions.ts +453 -453
  147. package/src/api/design/doors/index.ts +516 -500
  148. package/src/api/design/edit/index.ts +54 -54
  149. package/src/api/design/erase/index.ts +151 -151
  150. package/src/api/design/family.ts +1342 -704
  151. package/src/api/design/furniture/index.ts +319 -319
  152. package/src/api/design/grids.ts +237 -237
  153. package/src/api/design/index.ts +304 -274
  154. package/src/api/design/lock.ts +69 -69
  155. package/src/api/design/materials/index.ts +608 -608
  156. package/src/api/design/networks.ts +607 -0
  157. package/src/api/design/query/geometry/index.ts +319 -319
  158. package/src/api/design/query/grids.ts +197 -197
  159. package/src/api/design/query/index.ts +1236 -1226
  160. package/src/api/design/query/referenceLines.ts +52 -52
  161. package/src/api/design/query/spaces.ts +275 -275
  162. package/src/api/design/selection/index.ts +273 -273
  163. package/src/api/design/shared.ts +40 -40
  164. package/src/api/design/transform/index.ts +254 -254
  165. package/src/api/design/types/index.ts +163 -156
  166. package/src/api/design/update/index.ts +1096 -983
  167. package/src/api/design/visibility.ts +143 -143
  168. package/src/api/design/windows/index.ts +351 -333
  169. package/src/api/entity/buildableEnvelope.ts +397 -322
  170. package/src/api/entity/index.ts +30 -30
  171. package/src/api/entity/referenceLine.ts +235 -235
  172. package/src/api/entity/space.ts +612 -612
  173. package/src/api/entity/story.ts +517 -517
  174. package/src/api/index.ts +47 -47
  175. package/src/api/presentation/aiInspiration.ts +1116 -1116
  176. package/src/api/presentation/annotate.ts +417 -385
  177. package/src/api/presentation/camera.ts +289 -289
  178. package/src/api/presentation/diagrams.ts +194 -194
  179. package/src/api/presentation/drawingContract.ts +70 -0
  180. package/src/api/presentation/drawings.ts +1179 -0
  181. package/src/api/presentation/export.ts +134 -108
  182. package/src/api/presentation/import.ts +159 -159
  183. package/src/api/presentation/index.ts +160 -126
  184. package/src/api/presentation/placedViews.ts +1211 -1120
  185. package/src/api/presentation/sections.ts +420 -0
  186. package/src/api/presentation/shapes.ts +274 -274
  187. package/src/api/presentation/sheets.ts +746 -585
  188. package/src/api/presentation/slideshow.ts +134 -134
  189. package/src/api/presentation/tables.ts +84 -84
  190. package/src/api/presentation/viewPresets.ts +241 -0
  191. package/src/api/presentation/views.ts +742 -673
  192. package/src/api/presentation/walkthroughs.ts +1201 -1201
  193. package/src/api/program/adjacency.ts +164 -164
  194. package/src/api/program/areas.ts +408 -408
  195. package/src/api/program/classification.ts +138 -138
  196. package/src/api/program/cores.ts +83 -83
  197. package/src/api/program/departments.ts +517 -517
  198. package/src/api/program/index.ts +73 -73
  199. package/src/api/program/labels.ts +332 -332
  200. package/src/api/program/layout.ts +422 -422
  201. package/src/api/program/metadata.ts +364 -364
  202. package/src/api/program/metrics.ts +120 -120
  203. package/src/api/program/site.ts +748 -748
  204. package/src/api/program/spreadsheet.ts +2698 -2698
  205. package/src/api/workspace/index.ts +612 -612
  206. package/src/errors/codes.ts +143 -136
  207. package/src/errors/envelope.ts +75 -75
  208. package/src/errors/index.ts +21 -21
  209. package/src/errors/plugin-error.ts +134 -134
  210. package/src/handles.ts +297 -297
  211. package/src/host-utils.ts +67 -67
  212. package/src/index.ts +7 -7
  213. package/src/massParameters.ops.ts +246 -246
  214. package/src/massParameters.ts +427 -427
  215. package/src/types.ts +16 -16
  216. package/test/drawingContract.test.mjs +288 -0
  217. package/test/errors.test.mjs +185 -184
  218. package/test/massParameters.test.mjs +952 -952
  219. package/test/presentationViewSettings.test.mjs +85 -0
  220. package/tsconfig.json +22 -22
  221. package/tsup.config.ts +12 -12
@@ -1,237 +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
- >
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
+ >