flowquery 1.0.14 → 1.0.16
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/.editorconfig +21 -0
- package/.husky/pre-commit +1 -0
- package/.prettierrc +22 -0
- package/dist/flowquery.min.js +1 -1
- package/dist/parsing/expressions/expression_map.d.ts +9 -0
- package/dist/parsing/expressions/expression_map.d.ts.map +1 -0
- package/dist/parsing/expressions/expression_map.js +24 -0
- package/dist/parsing/expressions/expression_map.js.map +1 -0
- package/dist/parsing/operations/call.d.ts +17 -0
- package/dist/parsing/operations/call.d.ts.map +1 -0
- package/dist/parsing/operations/call.js +105 -0
- package/dist/parsing/operations/call.js.map +1 -0
- package/dist/parsing/operations/load.d.ts +6 -6
- package/dist/parsing/operations/load.d.ts.map +1 -1
- package/dist/parsing/operations/load.js +8 -6
- package/dist/parsing/operations/load.js.map +1 -1
- package/dist/parsing/operations/operation.d.ts +1 -0
- package/dist/parsing/operations/operation.d.ts.map +1 -1
- package/dist/parsing/operations/operation.js +6 -5
- package/dist/parsing/operations/operation.js.map +1 -1
- package/dist/parsing/operations/projection.d.ts +1 -1
- package/dist/parsing/operations/projection.d.ts.map +1 -1
- package/dist/parsing/operations/projection.js.map +1 -1
- package/dist/parsing/parser.d.ts +1 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +148 -99
- package/dist/parsing/parser.js.map +1 -1
- package/dist/parsing/token_to_node.d.ts +2 -2
- package/dist/parsing/token_to_node.d.ts.map +1 -1
- package/dist/parsing/token_to_node.js +12 -12
- package/dist/parsing/token_to_node.js.map +1 -1
- package/dist/tokenization/token.d.ts +5 -1
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +17 -5
- package/dist/tokenization/token.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +1 -1
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +21 -26
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +65 -0
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +163 -147
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +106 -92
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +80 -58
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +106 -103
- package/misc/apps/RAG/src/plugins/loaders/Weather.ts +50 -38
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +77 -78
- package/package.json +12 -2
- package/src/parsing/expressions/expression_map.ts +22 -0
- package/src/parsing/operations/call.ts +69 -0
- package/src/parsing/operations/load.ts +123 -120
- package/src/parsing/operations/operation.ts +14 -13
- package/src/parsing/operations/projection.ts +3 -3
- package/src/parsing/parser.ts +303 -239
- package/src/parsing/token_to_node.ts +67 -50
- package/src/tokenization/token.ts +29 -14
- package/tests/compute/runner.test.ts +277 -165
- package/tests/parsing/parser.test.ts +352 -303
- package/tests/tokenization/tokenizer.test.ts +17 -17
- package/vscode-settings.json.recommended +16 -0
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Table plugin - transforms tabular data into Adaptive Card format.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Adaptive Cards are platform-agnostic UI snippets that can be rendered in
|
|
5
5
|
* Microsoft Teams, Outlook, Windows, and other applications.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Usage in FlowQuery:
|
|
8
8
|
* // First collect data from an async provider, then pass to table:
|
|
9
|
-
*
|
|
10
|
-
* WITH collect(
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* CALL mockUsers(5) YIELD id, name, email
|
|
10
|
+
* WITH collect({ id, name, email }) AS users
|
|
11
|
+
* CALL table(users, 'Users') YIELD type, body
|
|
12
|
+
*
|
|
14
13
|
* Note: Async providers cannot be nested as function arguments.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
import { FunctionDef } from 'flowquery/extensibility';
|
|
15
|
+
import { AsyncFunction, FunctionDef } from "flowquery/extensibility";
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Interface for Adaptive Card structure
|
|
21
19
|
*/
|
|
22
20
|
interface AdaptiveCard {
|
|
23
|
-
type:
|
|
21
|
+
type: "AdaptiveCard";
|
|
24
22
|
$schema: string;
|
|
25
23
|
version: string;
|
|
26
24
|
body: AdaptiveCardElement[];
|
|
@@ -32,12 +30,12 @@ interface AdaptiveCardElement {
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
interface TableCell {
|
|
35
|
-
type:
|
|
33
|
+
type: "TableCell";
|
|
36
34
|
items: AdaptiveCardElement[];
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
interface TableRow {
|
|
40
|
-
type:
|
|
38
|
+
type: "TableRow";
|
|
41
39
|
cells: TableCell[];
|
|
42
40
|
}
|
|
43
41
|
|
|
@@ -45,69 +43,70 @@ interface TableRow {
|
|
|
45
43
|
* Table class - transforms tabular data into an Adaptive Card table format.
|
|
46
44
|
*/
|
|
47
45
|
@FunctionDef({
|
|
48
|
-
description:
|
|
49
|
-
category:
|
|
46
|
+
description: "Transforms tabular data into an Adaptive Card JSON format with a table layout",
|
|
47
|
+
category: "async",
|
|
50
48
|
parameters: [
|
|
51
49
|
{
|
|
52
|
-
name:
|
|
53
|
-
description:
|
|
54
|
-
type:
|
|
55
|
-
required: true
|
|
50
|
+
name: "data",
|
|
51
|
+
description: "Array of objects or async generator to display as a table",
|
|
52
|
+
type: "array",
|
|
53
|
+
required: true,
|
|
56
54
|
},
|
|
57
55
|
{
|
|
58
|
-
name:
|
|
59
|
-
description:
|
|
60
|
-
type:
|
|
56
|
+
name: "title",
|
|
57
|
+
description: "Optional title for the card",
|
|
58
|
+
type: "string",
|
|
61
59
|
required: false,
|
|
62
|
-
default:
|
|
60
|
+
default: "Data Table",
|
|
63
61
|
},
|
|
64
62
|
{
|
|
65
|
-
name:
|
|
66
|
-
description:
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
name: "columns",
|
|
64
|
+
description:
|
|
65
|
+
"Optional array of column names to include (defaults to all columns from first row)",
|
|
66
|
+
type: "array",
|
|
67
|
+
required: false,
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
|
-
name:
|
|
72
|
-
description:
|
|
73
|
-
type:
|
|
70
|
+
name: "maxRows",
|
|
71
|
+
description: "Maximum number of rows to display",
|
|
72
|
+
type: "number",
|
|
74
73
|
required: false,
|
|
75
|
-
default: 100
|
|
76
|
-
}
|
|
74
|
+
default: 100,
|
|
75
|
+
},
|
|
77
76
|
],
|
|
78
77
|
output: {
|
|
79
|
-
description:
|
|
80
|
-
type:
|
|
78
|
+
description: "Adaptive Card JSON object",
|
|
79
|
+
type: "object",
|
|
81
80
|
properties: {
|
|
82
|
-
type: { description: 'Always "AdaptiveCard"', type:
|
|
83
|
-
$schema: { description:
|
|
84
|
-
version: { description:
|
|
85
|
-
body: { description:
|
|
86
|
-
}
|
|
81
|
+
type: { description: 'Always "AdaptiveCard"', type: "string" },
|
|
82
|
+
$schema: { description: "Adaptive Card schema URL", type: "string" },
|
|
83
|
+
version: { description: "Adaptive Card version", type: "string" },
|
|
84
|
+
body: { description: "Card body elements including table", type: "array" },
|
|
85
|
+
},
|
|
87
86
|
},
|
|
88
87
|
examples: [
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
]
|
|
88
|
+
"CALL mockUsers(5) YIELD id, name, email WITH collect({ id, name, email }) AS users CALL table(users, 'User List') YIELD type, body",
|
|
89
|
+
"CALL mockProducts(10) YIELD name, price, category WITH collect({ name, price, category }) AS products CALL table(products, 'Products', ['name', 'price', 'category']) YIELD type, body",
|
|
90
|
+
],
|
|
92
91
|
})
|
|
93
|
-
export class Table {
|
|
92
|
+
export class Table extends AsyncFunction {
|
|
94
93
|
/**
|
|
95
94
|
* Transforms data into an Adaptive Card with table layout.
|
|
96
|
-
*
|
|
95
|
+
*
|
|
97
96
|
* @param data - Array or async iterable of objects
|
|
98
97
|
* @param title - Card title
|
|
99
98
|
* @param columns - Optional column names to include
|
|
100
99
|
* @param maxRows - Maximum rows to include
|
|
101
100
|
*/
|
|
102
|
-
async *
|
|
101
|
+
async *generate(
|
|
103
102
|
data: any[] | AsyncIterable<any>,
|
|
104
|
-
title: string =
|
|
103
|
+
title: string = "Data Table",
|
|
105
104
|
columns?: string[],
|
|
106
105
|
maxRows: number = 100
|
|
107
106
|
): AsyncGenerator<AdaptiveCard, void, unknown> {
|
|
108
107
|
// Collect data from array or async iterable
|
|
109
108
|
const rows: any[] = [];
|
|
110
|
-
|
|
109
|
+
|
|
111
110
|
if (Symbol.asyncIterator in Object(data)) {
|
|
112
111
|
for await (const item of data as AsyncIterable<any>) {
|
|
113
112
|
rows.push(item);
|
|
@@ -137,71 +136,75 @@ export class Table {
|
|
|
137
136
|
*/
|
|
138
137
|
private createTableCard(title: string, columnNames: string[], rows: any[]): AdaptiveCard {
|
|
139
138
|
const card: AdaptiveCard = {
|
|
140
|
-
type:
|
|
141
|
-
$schema:
|
|
142
|
-
version:
|
|
143
|
-
body: []
|
|
139
|
+
type: "AdaptiveCard",
|
|
140
|
+
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
141
|
+
version: "1.3",
|
|
142
|
+
body: [],
|
|
144
143
|
};
|
|
145
144
|
|
|
146
145
|
// Add title
|
|
147
146
|
card.body.push({
|
|
148
|
-
type:
|
|
147
|
+
type: "TextBlock",
|
|
149
148
|
text: title,
|
|
150
|
-
weight:
|
|
151
|
-
size:
|
|
152
|
-
wrap: true
|
|
149
|
+
weight: "Bolder",
|
|
150
|
+
size: "Large",
|
|
151
|
+
wrap: true,
|
|
153
152
|
});
|
|
154
153
|
|
|
155
154
|
// Add separator
|
|
156
155
|
card.body.push({
|
|
157
|
-
type:
|
|
158
|
-
text:
|
|
159
|
-
separator: true
|
|
156
|
+
type: "TextBlock",
|
|
157
|
+
text: " ",
|
|
158
|
+
separator: true,
|
|
160
159
|
});
|
|
161
160
|
|
|
162
161
|
// Create header row using ColumnSet
|
|
163
162
|
const headerColumnSet: AdaptiveCardElement = {
|
|
164
|
-
type:
|
|
165
|
-
columns: columnNames.map(col => ({
|
|
166
|
-
type:
|
|
167
|
-
width:
|
|
168
|
-
items: [
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
type: "ColumnSet",
|
|
164
|
+
columns: columnNames.map((col) => ({
|
|
165
|
+
type: "Column",
|
|
166
|
+
width: "stretch",
|
|
167
|
+
items: [
|
|
168
|
+
{
|
|
169
|
+
type: "TextBlock",
|
|
170
|
+
text: this.formatColumnName(col),
|
|
171
|
+
weight: "Bolder",
|
|
172
|
+
wrap: true,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
174
175
|
})),
|
|
175
|
-
style:
|
|
176
|
+
style: "accent",
|
|
176
177
|
};
|
|
177
178
|
card.body.push(headerColumnSet);
|
|
178
179
|
|
|
179
180
|
// Add data rows using ColumnSets
|
|
180
181
|
for (const row of rows) {
|
|
181
182
|
const dataColumnSet: AdaptiveCardElement = {
|
|
182
|
-
type:
|
|
183
|
-
columns: columnNames.map(col => ({
|
|
184
|
-
type:
|
|
185
|
-
width:
|
|
186
|
-
items: [
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
type: "ColumnSet",
|
|
184
|
+
columns: columnNames.map((col) => ({
|
|
185
|
+
type: "Column",
|
|
186
|
+
width: "stretch",
|
|
187
|
+
items: [
|
|
188
|
+
{
|
|
189
|
+
type: "TextBlock",
|
|
190
|
+
text: this.formatCellValue(row[col]),
|
|
191
|
+
wrap: true,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
191
194
|
})),
|
|
192
|
-
separator: true
|
|
195
|
+
separator: true,
|
|
193
196
|
};
|
|
194
197
|
card.body.push(dataColumnSet);
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
// Add row count footer
|
|
198
201
|
card.body.push({
|
|
199
|
-
type:
|
|
200
|
-
text: `Showing ${rows.length} row${rows.length !== 1 ?
|
|
201
|
-
size:
|
|
202
|
+
type: "TextBlock",
|
|
203
|
+
text: `Showing ${rows.length} row${rows.length !== 1 ? "s" : ""}`,
|
|
204
|
+
size: "Small",
|
|
202
205
|
isSubtle: true,
|
|
203
|
-
horizontalAlignment:
|
|
204
|
-
separator: true
|
|
206
|
+
horizontalAlignment: "Right",
|
|
207
|
+
separator: true,
|
|
205
208
|
});
|
|
206
209
|
|
|
207
210
|
return card;
|
|
@@ -212,24 +215,24 @@ export class Table {
|
|
|
212
215
|
*/
|
|
213
216
|
private createEmptyCard(title: string): AdaptiveCard {
|
|
214
217
|
return {
|
|
215
|
-
type:
|
|
216
|
-
$schema:
|
|
217
|
-
version:
|
|
218
|
+
type: "AdaptiveCard",
|
|
219
|
+
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
220
|
+
version: "1.3",
|
|
218
221
|
body: [
|
|
219
222
|
{
|
|
220
|
-
type:
|
|
223
|
+
type: "TextBlock",
|
|
221
224
|
text: title,
|
|
222
|
-
weight:
|
|
223
|
-
size:
|
|
224
|
-
wrap: true
|
|
225
|
+
weight: "Bolder",
|
|
226
|
+
size: "Large",
|
|
227
|
+
wrap: true,
|
|
225
228
|
},
|
|
226
229
|
{
|
|
227
|
-
type:
|
|
228
|
-
text:
|
|
230
|
+
type: "TextBlock",
|
|
231
|
+
text: "No data available",
|
|
229
232
|
isSubtle: true,
|
|
230
|
-
wrap: true
|
|
231
|
-
}
|
|
232
|
-
]
|
|
233
|
+
wrap: true,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
233
236
|
};
|
|
234
237
|
}
|
|
235
238
|
|
|
@@ -238,9 +241,9 @@ export class Table {
|
|
|
238
241
|
*/
|
|
239
242
|
private formatColumnName(name: string): string {
|
|
240
243
|
return name
|
|
241
|
-
.replace(/([A-Z])/g,
|
|
242
|
-
.replace(/_/g,
|
|
243
|
-
.replace(/^\w/, c => c.toUpperCase())
|
|
244
|
+
.replace(/([A-Z])/g, " $1") // camelCase
|
|
245
|
+
.replace(/_/g, " ") // snake_case
|
|
246
|
+
.replace(/^\w/, (c) => c.toUpperCase())
|
|
244
247
|
.trim();
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -249,19 +252,19 @@ export class Table {
|
|
|
249
252
|
*/
|
|
250
253
|
private formatCellValue(value: any): string {
|
|
251
254
|
if (value === null || value === undefined) {
|
|
252
|
-
return
|
|
255
|
+
return "-";
|
|
253
256
|
}
|
|
254
|
-
if (typeof value ===
|
|
255
|
-
return value ?
|
|
257
|
+
if (typeof value === "boolean") {
|
|
258
|
+
return value ? "✓" : "✗";
|
|
256
259
|
}
|
|
257
|
-
if (typeof value ===
|
|
260
|
+
if (typeof value === "number") {
|
|
258
261
|
// Format numbers nicely
|
|
259
262
|
if (Number.isInteger(value)) {
|
|
260
263
|
return value.toString();
|
|
261
264
|
}
|
|
262
265
|
return value.toFixed(2);
|
|
263
266
|
}
|
|
264
|
-
if (typeof value ===
|
|
267
|
+
if (typeof value === "object") {
|
|
265
268
|
return JSON.stringify(value);
|
|
266
269
|
}
|
|
267
270
|
return String(value);
|
|
@@ -1,68 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Example plugin: Fetch weather forecast for a location.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Usage in FlowQuery:
|
|
5
|
-
*
|
|
6
|
-
* RETURN forecast
|
|
5
|
+
* CALL weather('Oslo') YIELD date, location, temperature, humidity
|
|
7
6
|
*/
|
|
7
|
+
import { AsyncFunction, FunctionDef } from "flowquery/extensibility";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const GEOCODING_API = 'https://geocoding-api.open-meteo.com/v1/search';
|
|
12
|
-
const WEATHER_API = 'https://api.met.no/weatherapi/locationforecast/2.0/compact';
|
|
9
|
+
const GEOCODING_API = "https://geocoding-api.open-meteo.com/v1/search";
|
|
10
|
+
const WEATHER_API = "https://api.met.no/weatherapi/locationforecast/2.0/compact";
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Weather class - fetches weather forecast for a given location.
|
|
16
14
|
*/
|
|
17
15
|
@FunctionDef({
|
|
18
|
-
description:
|
|
19
|
-
|
|
16
|
+
description:
|
|
17
|
+
"Fetches weather forecast for a location using Open-Meteo geocoding and MET Norway weather API",
|
|
18
|
+
category: "async",
|
|
20
19
|
parameters: [
|
|
21
20
|
{
|
|
22
|
-
name:
|
|
21
|
+
name: "location",
|
|
23
22
|
description: 'The name of the location to get weather for (e.g., "Oslo", "New York")',
|
|
24
|
-
type:
|
|
25
|
-
required: true
|
|
26
|
-
}
|
|
23
|
+
type: "string",
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
27
26
|
],
|
|
28
27
|
output: {
|
|
29
|
-
description:
|
|
30
|
-
type:
|
|
28
|
+
description: "Weather forecast data point with location and weather parameters",
|
|
29
|
+
type: "object",
|
|
31
30
|
properties: {
|
|
32
|
-
date: { description:
|
|
33
|
-
location: { description:
|
|
34
|
-
lat: { description:
|
|
35
|
-
lon: { description:
|
|
36
|
-
temperature: { description:
|
|
37
|
-
humidity: { description:
|
|
38
|
-
pressure: { description:
|
|
39
|
-
cloud_cover: { description:
|
|
40
|
-
wind_speed: { description:
|
|
41
|
-
wind_direction: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
date: { description: "ISO 8601 timestamp in UTC", type: "string" },
|
|
32
|
+
location: { description: "Name of the location", type: "string" },
|
|
33
|
+
lat: { description: "Latitude in decimal degrees", type: "number" },
|
|
34
|
+
lon: { description: "Longitude in decimal degrees", type: "number" },
|
|
35
|
+
temperature: { description: "Air temperature in celsius", type: "number" },
|
|
36
|
+
humidity: { description: "Relative humidity in %", type: "number" },
|
|
37
|
+
pressure: { description: "Air pressure at sea level in hPa", type: "number" },
|
|
38
|
+
cloud_cover: { description: "Total cloud cover in %", type: "number" },
|
|
39
|
+
wind_speed: { description: "Wind speed in m/s", type: "number" },
|
|
40
|
+
wind_direction: {
|
|
41
|
+
description: "Wind direction in degrees (0=N, 90=E, 180=S, 270=W)",
|
|
42
|
+
type: "number",
|
|
43
|
+
},
|
|
44
|
+
precipitation: {
|
|
45
|
+
description: "Expected precipitation in mm (next hour if available)",
|
|
46
|
+
type: "number",
|
|
47
|
+
},
|
|
48
|
+
symbol: {
|
|
49
|
+
description: 'Weather symbol code (e.g., "partlycloudy_day", "rain")',
|
|
50
|
+
type: "string",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
45
53
|
},
|
|
46
54
|
examples: [
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
]
|
|
55
|
+
"CALL weather('Oslo') YIELD date, location, temperature, humidity",
|
|
56
|
+
"CALL weather('London') YIELD date, location, temperature, wind_speed, precipitation",
|
|
57
|
+
],
|
|
50
58
|
})
|
|
51
|
-
export class Weather {
|
|
59
|
+
export class Weather extends AsyncFunction {
|
|
52
60
|
private readonly geocodingApiUrl: string;
|
|
53
61
|
private readonly weatherApiUrl: string;
|
|
54
62
|
|
|
55
63
|
constructor(geocodingApiUrl: string = GEOCODING_API, weatherApiUrl: string = WEATHER_API) {
|
|
64
|
+
super();
|
|
56
65
|
this.geocodingApiUrl = geocodingApiUrl;
|
|
57
66
|
this.weatherApiUrl = weatherApiUrl;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
70
|
* Fetches weather forecast for a given location.
|
|
62
|
-
*
|
|
71
|
+
*
|
|
63
72
|
* @param location - The name of the location to get weather for
|
|
64
73
|
*/
|
|
65
|
-
async *
|
|
74
|
+
async *generate(location: string): AsyncGenerator<any, void, unknown> {
|
|
66
75
|
// Step 1: Geocode the location name to get lat/lon
|
|
67
76
|
const geocodeUrl = `${this.geocodingApiUrl}?name=${encodeURIComponent(location)}`;
|
|
68
77
|
const geocodeResponse = await fetch(geocodeUrl);
|
|
@@ -86,8 +95,8 @@ export class Weather {
|
|
|
86
95
|
const weatherResponse = await fetch(weatherUrl, {
|
|
87
96
|
headers: {
|
|
88
97
|
// MET Norway API requires a User-Agent header
|
|
89
|
-
|
|
90
|
-
}
|
|
98
|
+
"User-Agent": "FlowQuery-RAG/1.0",
|
|
99
|
+
},
|
|
91
100
|
});
|
|
92
101
|
|
|
93
102
|
if (!weatherResponse.ok) {
|
|
@@ -116,8 +125,11 @@ export class Weather {
|
|
|
116
125
|
cloud_cover: instant.cloud_area_fraction,
|
|
117
126
|
wind_speed: instant.wind_speed,
|
|
118
127
|
wind_direction: instant.wind_from_direction,
|
|
119
|
-
precipitation:
|
|
120
|
-
|
|
128
|
+
precipitation:
|
|
129
|
+
next1h.details?.precipitation_amount ??
|
|
130
|
+
next6h.details?.precipitation_amount ??
|
|
131
|
+
null,
|
|
132
|
+
symbol: next1h.summary?.symbol_code ?? next6h.summary?.symbol_code ?? null,
|
|
121
133
|
};
|
|
122
134
|
}
|
|
123
135
|
}
|