@trilogy-data/trilogy-studio-components 0.1.25 → 0.1.26
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/dist/AssetAutoImporter-CBzvBaOt.js +405 -0
- package/dist/ChatCreatorModal-CL-jBnc3.js +218 -0
- package/dist/ChatModelSelect-Do-VRUFc.js +40 -0
- package/dist/{CommunityModels-D7zWnHgG.js → CommunityModels-Cmro0hV3.js} +101 -70
- package/dist/{Dashboard-D1ZCTw5r.js → Dashboard-BH9ZwMVJ.js} +8 -8
- package/dist/{DashboardCreatorInline-LWvf--2x.js → DashboardCreatorInline-CDU7kLy3.js} +3 -3
- package/dist/{DashboardImportSelector-CNxibXlS.js → DashboardImportSelector-DQSvu9T-.js} +1 -1
- package/dist/{DashboardMobile-D_m0xv17.js → DashboardMobile-BUPtdN2V.js} +4 -4
- package/dist/{Editor-DhsP18KE.js → Editor-DQsYpkwl.js} +6 -6
- package/dist/{GlobalChatPanel-DFx9A4yP.js → GlobalChatPanel-DhnOaige.js} +155 -119
- package/dist/{IDE-BRMLxTfP.js → IDE-HLa8vybk.js} +9 -9
- package/dist/{JobsView-C9PYS6pZ.js → JobsView-B97HBoIE.js} +8 -8
- package/dist/{LLMChat-Cqg6pTgC.js → LLMChat-CxJcPLEG.js} +1 -1
- package/dist/{LLMView-VkJpP-49.js → LLMView-C1MUf0c2.js} +21 -21
- package/dist/{MobileIDE-CHGJoh05.js → MobileIDE-BpqXoaMR.js} +258 -253
- package/dist/{ModelCard-D8tKkK9-.js → ModelCard-BhN25jaV.js} +10 -10
- package/dist/{ModelCreator-DrxLxeyH.js → ModelCreator-CCdC7EXt.js} +1 -1
- package/dist/{ModelView-rAurf9n8.js → ModelView-DUDA_PtZ.js} +3 -3
- package/dist/{PageLoading-I6mr-7NB.js → PageLoading-BzniJM0C.js} +622 -619
- package/dist/{ResultComponent-_-1qZCfv.js → ResultComponent-CAn_r3BL.js} +3 -3
- package/dist/{Results-CQGpFmPQ.js → Results-BJtlwebY.js} +3 -3
- package/dist/{SymbolsPane-BpmL___H.js → SymbolsPane-KYSwN9Sv.js} +1 -1
- package/dist/{TutorialPage-CZGBRJdq.js → TutorialPage-NRcee6Dx.js} +8 -8
- package/dist/{VegaLiteChart-CmkOw6yD.js → VegaLiteChart-D1slfOUY.js} +11227 -11195
- package/dist/{WelcomePage-CLKvAvk-.js → WelcomePage-D5mU2x1L.js} +1 -1
- package/dist/components/AssetAutoImporter.vue.d.ts.map +1 -1
- package/dist/components/TipsCTA.vue.d.ts +1 -1
- package/dist/components/VegaLiteChart.vue.d.ts.map +1 -1
- package/dist/components/chartHelpers.d.ts +9 -1
- package/dist/components/chartHelpers.d.ts.map +1 -1
- package/dist/components/chartRenderManager.d.ts +4 -1
- package/dist/components/chartRenderManager.d.ts.map +1 -1
- package/dist/components/community/AddStoreModal.vue.d.ts +1 -1
- package/dist/components/community/AddStoreModal.vue.d.ts.map +1 -1
- package/dist/components/community/CommunityModelCard.vue.d.ts.map +1 -1
- package/dist/components/editor/Editor.vue.d.ts.map +1 -1
- package/dist/components/jobs/JobsAddStoreModal.vue.d.ts.map +1 -1
- package/dist/components/llm/ChatCreatorModal.vue.d.ts +17 -1
- package/dist/components/llm/ChatCreatorModal.vue.d.ts.map +1 -1
- package/dist/components/llm/ChatModelSelect.vue.d.ts +13 -0
- package/dist/components/llm/ChatModelSelect.vue.d.ts.map +1 -0
- package/dist/components/llm/GlobalChatPanel.vue.d.ts.map +1 -1
- package/dist/components/sidebar/CommunityModelList.vue.d.ts +33 -50
- package/dist/components/sidebar/CommunityModelList.vue.d.ts.map +1 -1
- package/dist/components/sidebar/CommunityModelListItem.vue.d.ts.map +1 -1
- package/dist/components/sidebar/EditorList.vue.d.ts +32 -49
- package/dist/components/sidebar/EditorList.vue.d.ts.map +1 -1
- package/dist/components/sidebar/Sidebar.vue.d.ts +65 -99
- package/dist/components/sidebar/Sidebar.vue.d.ts.map +1 -1
- package/dist/dashboard.js +52 -52
- package/dist/{dashboardStore-CNkWQ4r7.js → dashboardStore-ppM6nGks.js} +33 -33
- package/dist/dashboards/layerSpec.d.ts +38 -4
- package/dist/dashboards/layerSpec.d.ts.map +1 -1
- package/dist/dashboards/lineAreaSpec.d.ts +8 -80
- package/dist/dashboards/lineAreaSpec.d.ts.map +1 -1
- package/dist/dashboards/mapSpec.d.ts +1 -0
- package/dist/dashboards/mapSpec.d.ts.map +1 -1
- package/dist/dashboards/spec.d.ts.map +1 -1
- package/dist/data/tutorial/documentation.d.ts.map +1 -1
- package/dist/data/tutorial/startupScripts.d.ts +3 -0
- package/dist/data/tutorial/startupScripts.d.ts.map +1 -0
- package/dist/{demoSetup-DiW3ROFQ.js → demoSetup-mwa-RDC6.js} +19 -18
- package/dist/{editorRefinementRuntime-C_YmXRy4.js → editorRefinementRuntime-DoP9P5J2.js} +892 -871
- package/dist/{editorRefinementToolExecutor-mX8ClQ63.js → editorRefinementToolExecutor-CVY3FA7k.js} +389 -263
- package/dist/{editorStore-Ch7k-gse.js → editorStore-BhhmXbi-.js} +230 -183
- package/dist/entry.dashboard.d.ts +1 -0
- package/dist/entry.dashboard.d.ts.map +1 -1
- package/dist/{globalChatRuntime-oMLiPLNC.js → globalChatRuntime-DWZxeS57.js} +13 -13
- package/dist/{helpers-DcWCa6bX.js → helpers-BLQAgX3C.js} +17 -16
- package/dist/llm/data/constants.d.ts +1 -1
- package/dist/llm/data/constants.d.ts.map +1 -1
- package/dist/llm/data/trilogySyntax.generated.d.ts +7 -1
- package/dist/llm/data/trilogySyntax.generated.d.ts.map +1 -1
- package/dist/llm/docsIndex.d.ts.map +1 -1
- package/dist/llm/openai.d.ts +2 -1
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/{llm-_807I79k.js → llm-BBOVWb-F.js} +7 -5
- package/dist/llm.js +3 -3
- package/dist/{llmStore-CSzM1IZd.js → llmStore-DJM2JhzY.js} +2 -2
- package/dist/{localStorage-CSuYS7QE.js → localStorage-D9uyOFEO.js} +14 -14
- package/dist/models/helpers.d.ts.map +1 -1
- package/dist/monaco.js +1 -1
- package/dist/{reconcile-Bk7M797_.js → reconcile-Dyf4UZmj.js} +1 -1
- package/dist/{remoteStoreSync-CLvZJrTK.js → remoteStoreSync-Z-yjlYtI.js} +1 -1
- package/dist/remotes/displayHelpers.d.ts.map +1 -1
- package/dist/remotes/genericStoreMetadata.d.ts +1 -0
- package/dist/remotes/genericStoreMetadata.d.ts.map +1 -1
- package/dist/remotes/models.d.ts +37 -5
- package/dist/remotes/models.d.ts.map +1 -1
- package/dist/remotes/staticStorePresets.d.ts +22 -0
- package/dist/remotes/staticStorePresets.d.ts.map +1 -0
- package/dist/remotes/storeService.d.ts +25 -4
- package/dist/remotes/storeService.d.ts.map +1 -1
- package/dist/stores/communityApiStore.d.ts +32 -60
- package/dist/stores/communityApiStore.d.ts.map +1 -1
- package/dist/stores/connectionStore.d.ts.map +1 -1
- package/dist/stores/resolver.d.ts +5 -0
- package/dist/stores/resolver.d.ts.map +1 -1
- package/dist/stores/urlStore.d.ts +1 -0
- package/dist/stores/urlStore.d.ts.map +1 -1
- package/dist/{stores-C1wUWiFm.js → stores-BT9hTLny.js} +47 -47
- package/dist/stores.js +7 -7
- package/dist/trilogy-studio-components.css +1 -1
- package/dist/{urlStore-Dgmwx1YI.js → urlStore-52HG6lsX.js} +1 -0
- package/dist/{useChatWithTools-GV1ufcqw.js → useChatWithTools-B0HZ_DVh.js} +1 -1
- package/dist/{useDashboard-OoBKkkuQ.js → useDashboard-CdSnPe_6.js} +186 -186
- package/dist/{useGlobalChatPanel-CIeUyZba.js → useGlobalChatPanel-D_0luajQ.js} +2 -2
- package/dist/views/MobileIDE.vue.d.ts.map +1 -1
- package/dist/views/TutorialPage.vue.d.ts +32 -49
- package/dist/views/TutorialPage.vue.d.ts.map +1 -1
- package/dist/views.js +16 -16
- package/package.json +1 -1
- package/dist/AssetAutoImporter-DfMd_DSk.js +0 -403
- package/dist/ChatCreatorModal-BTqY06B6.js +0 -177
package/dist/{editorRefinementToolExecutor-mX8ClQ63.js → editorRefinementToolExecutor-CVY3FA7k.js}
RENAMED
|
@@ -299,7 +299,7 @@ var c = class e {
|
|
|
299
299
|
size() {
|
|
300
300
|
return this.cache.size;
|
|
301
301
|
}
|
|
302
|
-
}, y = class {
|
|
302
|
+
}, y = class e {
|
|
303
303
|
settingStore;
|
|
304
304
|
validateCache;
|
|
305
305
|
formatCache;
|
|
@@ -338,17 +338,28 @@ var c = class e {
|
|
|
338
338
|
buildResolverUrl(e) {
|
|
339
339
|
return `${this.settingStore.settings.trilogyResolver.replace(/\/+$/, "")}/${e}`;
|
|
340
340
|
}
|
|
341
|
-
|
|
341
|
+
static RETRY_STATUSES = /* @__PURE__ */ new Set([503]);
|
|
342
|
+
static MAX_RETRIES = 3;
|
|
343
|
+
static MAX_RETRY_DELAY_MS = 5e3;
|
|
344
|
+
delay(e) {
|
|
345
|
+
return new Promise((t) => setTimeout(t, e));
|
|
346
|
+
}
|
|
347
|
+
retryDelayMs(t, n) {
|
|
348
|
+
let r = Number(t.headers?.get?.("retry-after")), i = Number.isFinite(r) && r > 0 ? r * 1e3 : 500 * 2 ** n;
|
|
349
|
+
return Math.min(i, e.MAX_RETRY_DELAY_MS) * (.75 + Math.random() * .5);
|
|
350
|
+
}
|
|
351
|
+
async fetchWithErrorHandling(t, n) {
|
|
342
352
|
try {
|
|
343
|
-
let
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
let r = await fetch(t, n);
|
|
354
|
+
for (let i = 0; !r.ok && e.RETRY_STATUSES.has(r.status) && i < e.MAX_RETRIES; i++) await this.delay(this.retryDelayMs(r, i)), r = await fetch(t, n);
|
|
355
|
+
if (!r.ok) {
|
|
356
|
+
let e = await r.json().catch(() => ({}));
|
|
346
357
|
throw {
|
|
347
|
-
message: `HTTP error ${
|
|
358
|
+
message: `HTTP error ${r.status}`,
|
|
348
359
|
response: { data: e }
|
|
349
360
|
};
|
|
350
361
|
}
|
|
351
|
-
return { data: await
|
|
362
|
+
return { data: await r.json() };
|
|
352
363
|
} catch (e) {
|
|
353
364
|
throw console.log(e), Error(this.getErrorMessage(e));
|
|
354
365
|
}
|
|
@@ -926,9 +937,9 @@ function De(e) {
|
|
|
926
937
|
Object.entries(e).forEach(([e, n]) => {
|
|
927
938
|
t[e] || (t[e] = []), t[e].push(n);
|
|
928
939
|
});
|
|
929
|
-
}), Object.entries(t).map(([e, t]) => t.length > 1 ? `(${t.map((t) =>
|
|
940
|
+
}), Object.entries(t).map(([e, t]) => t.length > 1 ? `(${t.map((t) => C(e, t)).join(" OR ")})` : C(e, t[0])).join(" AND ");
|
|
930
941
|
}
|
|
931
|
-
return Object.keys(e).length === 0 ? "" : Object.entries(e).map(([e, t]) =>
|
|
942
|
+
return Object.keys(e).length === 0 ? "" : Object.entries(e).map(([e, t]) => C(e, t)).join(" AND ");
|
|
932
943
|
}
|
|
933
944
|
function Oe(e) {
|
|
934
945
|
if (e instanceof Date) return `'${e.getFullYear()}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}'::date`;
|
|
@@ -937,23 +948,23 @@ function Oe(e) {
|
|
|
937
948
|
if (typeof e == "boolean") return e ? "TRUE" : "FALSE";
|
|
938
949
|
if (typeof e == "number") return e.toString();
|
|
939
950
|
}
|
|
940
|
-
function
|
|
951
|
+
function C(e, t) {
|
|
941
952
|
return t === null ? `${e} IS NULL` : typeof t == "string" ? `${e}='''${t.replace(/'/g, "!`")}'''` : Array.isArray(t) ? `${e} between ${Oe(t[0])} and ${Oe(t[t.length - 1])}` : typeof t == "number" || typeof t == "boolean" ? `${e}=${t}` : t === void 0 ? `${e} IS NULL` : `${e}='${JSON.stringify(t).replace(/'/g, "!`")}'`;
|
|
942
953
|
}
|
|
943
954
|
//#endregion
|
|
944
955
|
//#region dashboards/crossFilters.ts
|
|
945
|
-
function
|
|
956
|
+
function ke(e = []) {
|
|
946
957
|
let t = /* @__PURE__ */ new Set();
|
|
947
958
|
for (let n of e) n?.label && t.add(n.label);
|
|
948
959
|
return Array.from(t);
|
|
949
960
|
}
|
|
950
|
-
function
|
|
961
|
+
function w(e) {
|
|
951
962
|
return { ...e };
|
|
952
963
|
}
|
|
953
|
-
function
|
|
964
|
+
function T(e) {
|
|
954
965
|
return { ...e };
|
|
955
966
|
}
|
|
956
|
-
function
|
|
967
|
+
function Ae(e, t) {
|
|
957
968
|
if (e.op !== t.op) return !1;
|
|
958
969
|
switch (e.op) {
|
|
959
970
|
case "is_null": return !0;
|
|
@@ -968,31 +979,31 @@ function je(e, t) {
|
|
|
968
979
|
}
|
|
969
980
|
}
|
|
970
981
|
}
|
|
971
|
-
function
|
|
982
|
+
function E(e, t) {
|
|
972
983
|
let n = Object.keys(e).sort(), r = Object.keys(t).sort();
|
|
973
|
-
return n.length === r.length && n.every((n, i) => n === r[i] &&
|
|
984
|
+
return n.length === r.length && n.every((n, i) => n === r[i] && Ae(e[n], t[n]));
|
|
974
985
|
}
|
|
975
|
-
function
|
|
986
|
+
function je(e, t) {
|
|
976
987
|
return Array.isArray(e) && Array.isArray(t) ? e.length === t.length && e.every((e, n) => e === t[n]) : e === t;
|
|
977
988
|
}
|
|
978
|
-
function
|
|
989
|
+
function Me(e, t) {
|
|
979
990
|
let n = Object.keys(e).sort(), r = Object.keys(t).sort();
|
|
980
|
-
return n.length === r.length && n.every((n, i) => n === r[i] &&
|
|
991
|
+
return n.length === r.length && n.every((n, i) => n === r[i] && je(e[n], t[n]));
|
|
981
992
|
}
|
|
982
|
-
function
|
|
993
|
+
function Ne(e, t, n, r) {
|
|
983
994
|
let i = e.filter((e) => e.source !== t), a = e.filter((e) => e.source === t);
|
|
984
995
|
if (r === "remove") return i;
|
|
985
996
|
if (r === "add") return [...i, {
|
|
986
997
|
source: t,
|
|
987
|
-
value:
|
|
998
|
+
value: T(n)
|
|
988
999
|
}];
|
|
989
|
-
let o = a.some((e) =>
|
|
1000
|
+
let o = a.some((e) => Me(e.value, n)) ? a.filter((e) => !Me(e.value, n)) : [...a, {
|
|
990
1001
|
source: t,
|
|
991
|
-
value:
|
|
1002
|
+
value: T(n)
|
|
992
1003
|
}];
|
|
993
1004
|
return [...i, ...o];
|
|
994
1005
|
}
|
|
995
|
-
function
|
|
1006
|
+
function Pe(e, t, n) {
|
|
996
1007
|
if (t.size === 0) return n && e.startsWith("local.") ? e.replace(/^local\./, "") : e;
|
|
997
1008
|
if (t.has(e)) return e;
|
|
998
1009
|
if (e.startsWith("local.")) {
|
|
@@ -1001,7 +1012,7 @@ function Fe(e, t, n) {
|
|
|
1001
1012
|
}
|
|
1002
1013
|
return null;
|
|
1003
1014
|
}
|
|
1004
|
-
function
|
|
1015
|
+
function Fe(e, t = [], n = {}) {
|
|
1005
1016
|
let r = new Set(t), i = n.normalizeLocalFields ?? !1;
|
|
1006
1017
|
return Object.entries(e).reduce((e, [t, n]) => {
|
|
1007
1018
|
if (typeof n != "object" || !n || ![
|
|
@@ -1010,11 +1021,11 @@ function Ie(e, t = [], n = {}) {
|
|
|
1010
1021
|
"in",
|
|
1011
1022
|
"is_null"
|
|
1012
1023
|
].includes(n.op)) return e;
|
|
1013
|
-
let a =
|
|
1024
|
+
let a = Pe(t, r, i);
|
|
1014
1025
|
return a && (e[a] = n), e;
|
|
1015
1026
|
}, {});
|
|
1016
1027
|
}
|
|
1017
|
-
function
|
|
1028
|
+
function D(e) {
|
|
1018
1029
|
if (!e.length) return {
|
|
1019
1030
|
filterStrings: [],
|
|
1020
1031
|
parameters: {}
|
|
@@ -1044,8 +1055,8 @@ function E(e) {
|
|
|
1044
1055
|
parameters: r
|
|
1045
1056
|
};
|
|
1046
1057
|
}
|
|
1047
|
-
function
|
|
1048
|
-
let t = e.conceptFilters || [], n = (e.filters || []).filter((e) => e.source !== "cross"), { filterStrings: r, parameters: i } =
|
|
1058
|
+
function O(e) {
|
|
1059
|
+
let t = e.conceptFilters || [], n = (e.filters || []).filter((e) => e.source !== "cross"), { filterStrings: r, parameters: i } = D(t), a = r.join(" AND ");
|
|
1049
1060
|
a && n.push({
|
|
1050
1061
|
source: "cross",
|
|
1051
1062
|
value: a,
|
|
@@ -1054,43 +1065,43 @@ function D(e) {
|
|
|
1054
1065
|
let o = JSON.stringify(n) !== JSON.stringify(e.filters || []);
|
|
1055
1066
|
return e.filters = n, o;
|
|
1056
1067
|
}
|
|
1057
|
-
function
|
|
1068
|
+
function Ie(e, t, n, r) {
|
|
1058
1069
|
let i = e.filter((e) => e.source !== t), a = e.filter((e) => e.source === t);
|
|
1059
1070
|
if (r === "remove") return i;
|
|
1060
1071
|
if (r === "add") return [...i, {
|
|
1061
1072
|
source: t,
|
|
1062
|
-
value:
|
|
1073
|
+
value: w(n)
|
|
1063
1074
|
}];
|
|
1064
|
-
let o = a.some((e) =>
|
|
1075
|
+
let o = a.some((e) => E(e.value, n)) ? a.filter((e) => !E(e.value, n)) : [...a, {
|
|
1065
1076
|
source: t,
|
|
1066
|
-
value:
|
|
1077
|
+
value: w(n)
|
|
1067
1078
|
}];
|
|
1068
1079
|
return [...i, ...o];
|
|
1069
1080
|
}
|
|
1070
|
-
function
|
|
1081
|
+
function Le(e, t, n, r, i) {
|
|
1071
1082
|
let a = [];
|
|
1072
1083
|
return Object.entries(e).forEach(([e, o]) => {
|
|
1073
1084
|
if (e === t) {
|
|
1074
|
-
let e =
|
|
1085
|
+
let e = Ne(o.chartFilters || [], t, r, i);
|
|
1075
1086
|
JSON.stringify(e) !== JSON.stringify(o.chartFilters || []) && (o.chartFilters = e);
|
|
1076
1087
|
return;
|
|
1077
1088
|
}
|
|
1078
1089
|
if (!o.allowCrossFilter) return;
|
|
1079
|
-
let s =
|
|
1090
|
+
let s = Ie(o.conceptFilters || [], t, n, i), c = JSON.stringify(s) !== JSON.stringify(o.conceptFilters || []);
|
|
1080
1091
|
c && (o.conceptFilters = s);
|
|
1081
|
-
let l =
|
|
1092
|
+
let l = O(o);
|
|
1082
1093
|
(c || l) && a.push(e);
|
|
1083
1094
|
}), a;
|
|
1084
1095
|
}
|
|
1085
|
-
function
|
|
1096
|
+
function Re(e, t, n) {
|
|
1086
1097
|
let r = e[t];
|
|
1087
1098
|
if (!r) return !1;
|
|
1088
1099
|
let i = (r.conceptFilters || []).filter((e) => e.source !== n), a = JSON.stringify(i) !== JSON.stringify(r.conceptFilters || []);
|
|
1089
1100
|
a && (r.conceptFilters = i);
|
|
1090
|
-
let o =
|
|
1101
|
+
let o = O(r);
|
|
1091
1102
|
return a || o;
|
|
1092
1103
|
}
|
|
1093
|
-
function
|
|
1104
|
+
function ze(e, t) {
|
|
1094
1105
|
let n = [];
|
|
1095
1106
|
return Object.entries(e).forEach(([e, r]) => {
|
|
1096
1107
|
if (e === t) {
|
|
@@ -1099,27 +1110,27 @@ function Be(e, t) {
|
|
|
1099
1110
|
}
|
|
1100
1111
|
let i = (r.conceptFilters || []).filter((e) => e.source !== t), a = JSON.stringify(i) !== JSON.stringify(r.conceptFilters || []);
|
|
1101
1112
|
a && (r.conceptFilters = i);
|
|
1102
|
-
let o =
|
|
1113
|
+
let o = O(r);
|
|
1103
1114
|
(a || o) && n.push(e);
|
|
1104
1115
|
}), n;
|
|
1105
1116
|
}
|
|
1106
|
-
function
|
|
1117
|
+
function Be(e) {
|
|
1107
1118
|
let t = [];
|
|
1108
1119
|
return Object.entries(e).forEach(([e, n]) => {
|
|
1109
1120
|
let r = !!n.conceptFilters?.length || !!n.chartFilters?.length || !!n.filters?.length;
|
|
1110
1121
|
n.conceptFilters = [], n.chartFilters = [], n.filters = [], r && t.push(e);
|
|
1111
1122
|
}), t;
|
|
1112
1123
|
}
|
|
1113
|
-
function
|
|
1124
|
+
function Ve(e = {}) {
|
|
1114
1125
|
let t = /* @__PURE__ */ new Map(), n = () => {
|
|
1115
1126
|
let t = e.validFields ? o(e.validFields) : [];
|
|
1116
1127
|
return new Set(t);
|
|
1117
1128
|
}, r = (t) => {
|
|
1118
|
-
let r =
|
|
1129
|
+
let r = Fe(t.filters, n(), { normalizeLocalFields: e.normalizeLocalFields ?? !0 });
|
|
1119
1130
|
return {
|
|
1120
1131
|
source: t.source,
|
|
1121
1132
|
filters: r,
|
|
1122
|
-
chart:
|
|
1133
|
+
chart: T(t.chart || {}),
|
|
1123
1134
|
append: t.append
|
|
1124
1135
|
};
|
|
1125
1136
|
};
|
|
@@ -1127,8 +1138,8 @@ function He(e = {}) {
|
|
|
1127
1138
|
getSelections() {
|
|
1128
1139
|
return Array.from(t.values()).flat().map((e) => ({
|
|
1129
1140
|
source: e.source,
|
|
1130
|
-
filters:
|
|
1131
|
-
chart:
|
|
1141
|
+
filters: w(e.filters),
|
|
1142
|
+
chart: T(e.chart || {}),
|
|
1132
1143
|
append: e.append
|
|
1133
1144
|
}));
|
|
1134
1145
|
},
|
|
@@ -1141,7 +1152,7 @@ function He(e = {}) {
|
|
|
1141
1152
|
let o = t.get(e.source) || [];
|
|
1142
1153
|
if (a === "remove") return t.delete(e.source), i.filters;
|
|
1143
1154
|
if (a === "add") return t.set(e.source, [i]), i.filters;
|
|
1144
|
-
let s = o.some((e) =>
|
|
1155
|
+
let s = o.some((e) => E(e.filters, i.filters)) ? o.filter((e) => !E(e.filters, i.filters)) : [...o, i];
|
|
1145
1156
|
return s.length === 0 ? t.delete(e.source) : t.set(e.source, s), i.filters;
|
|
1146
1157
|
},
|
|
1147
1158
|
clearSource(e) {
|
|
@@ -1156,14 +1167,14 @@ function He(e = {}) {
|
|
|
1156
1167
|
getSqlFilterInputsFor(e) {
|
|
1157
1168
|
return Array.from(t.entries()).filter(([t]) => t !== e).flatMap(([e, t]) => t.map((t) => ({
|
|
1158
1169
|
source: e,
|
|
1159
|
-
value:
|
|
1170
|
+
value: w(t.filters)
|
|
1160
1171
|
})));
|
|
1161
1172
|
},
|
|
1162
1173
|
getChartSelectionsFor(e) {
|
|
1163
|
-
return (t.get(e) || []).map((e) =>
|
|
1174
|
+
return (t.get(e) || []).map((e) => T(e.chart || {}));
|
|
1164
1175
|
},
|
|
1165
1176
|
getFilterExpressionFor(e) {
|
|
1166
|
-
let { filterStrings: t } =
|
|
1177
|
+
let { filterStrings: t } = D(this.getSqlFilterInputsFor(e));
|
|
1167
1178
|
return t.join(" AND ");
|
|
1168
1179
|
},
|
|
1169
1180
|
getSqlFiltersFor(e, t = []) {
|
|
@@ -1174,7 +1185,7 @@ function He(e = {}) {
|
|
|
1174
1185
|
let n = t.map((e) => ({
|
|
1175
1186
|
source: "base",
|
|
1176
1187
|
value: e
|
|
1177
|
-
})), { filterStrings: r, parameters: i } =
|
|
1188
|
+
})), { filterStrings: r, parameters: i } = D(this.getSqlFilterInputsFor(e)), a = r.join(" AND ");
|
|
1178
1189
|
return a && n.push({
|
|
1179
1190
|
source: "cross",
|
|
1180
1191
|
value: a,
|
|
@@ -1182,13 +1193,13 @@ function He(e = {}) {
|
|
|
1182
1193
|
}), n;
|
|
1183
1194
|
},
|
|
1184
1195
|
getSqlParametersFor(e) {
|
|
1185
|
-
let { parameters: t } =
|
|
1196
|
+
let { parameters: t } = D(this.getSqlFilterInputsFor(e));
|
|
1186
1197
|
return t;
|
|
1187
1198
|
}
|
|
1188
1199
|
};
|
|
1189
1200
|
}
|
|
1190
|
-
function
|
|
1191
|
-
let t = a(0), n =
|
|
1201
|
+
function He(e = {}) {
|
|
1202
|
+
let t = a(0), n = Ve(o(e)), r = () => {
|
|
1192
1203
|
t.value += 1;
|
|
1193
1204
|
};
|
|
1194
1205
|
return {
|
|
@@ -1234,7 +1245,7 @@ function Ue(e = {}) {
|
|
|
1234
1245
|
}
|
|
1235
1246
|
//#endregion
|
|
1236
1247
|
//#region dashboards/base.ts
|
|
1237
|
-
var
|
|
1248
|
+
var k = {
|
|
1238
1249
|
CHART: "chart",
|
|
1239
1250
|
MARKDOWN: "markdown",
|
|
1240
1251
|
TABLE: "table",
|
|
@@ -1245,24 +1256,24 @@ var O = {
|
|
|
1245
1256
|
APPENDIX_HEADER: "appendix-header",
|
|
1246
1257
|
FREEFORM: "freeform"
|
|
1247
1258
|
};
|
|
1248
|
-
function
|
|
1259
|
+
function Ue(e) {
|
|
1249
1260
|
return !!e && typeof e == "object" && "markdown" in e;
|
|
1250
1261
|
}
|
|
1251
|
-
function
|
|
1262
|
+
function We(e) {
|
|
1252
1263
|
return !!e && typeof e == "object" && "headline" in e && "verdict" in e && "confidence" in e;
|
|
1253
1264
|
}
|
|
1254
|
-
function
|
|
1265
|
+
function Ge(e) {
|
|
1255
1266
|
return !!e && typeof e == "object" && "claim" in e && !("headline" in e);
|
|
1256
1267
|
}
|
|
1257
|
-
function
|
|
1268
|
+
function Ke(e) {
|
|
1258
1269
|
return !!e && typeof e == "object" && typeof e.html == "string";
|
|
1259
1270
|
}
|
|
1260
|
-
function
|
|
1271
|
+
function qe(e) {
|
|
1261
1272
|
let t = e.content;
|
|
1262
|
-
if (typeof t == "string") return e.type ===
|
|
1263
|
-
if (
|
|
1273
|
+
if (typeof t == "string") return e.type === k.CHART || e.type === k.TABLE ? t : void 0;
|
|
1274
|
+
if (Ke(t) || We(t) || Ge(t) || Ue(t)) return t.query || void 0;
|
|
1264
1275
|
}
|
|
1265
|
-
var
|
|
1276
|
+
var Je = class t {
|
|
1266
1277
|
id;
|
|
1267
1278
|
name;
|
|
1268
1279
|
storage;
|
|
@@ -1305,7 +1316,7 @@ var Ye = class t {
|
|
|
1305
1316
|
}
|
|
1306
1317
|
addItem(e, t = 0, n = 0, r = 4, i = null, a = null, o) {
|
|
1307
1318
|
let s = this.nextId.toString(), c = 10, l = r;
|
|
1308
|
-
e ===
|
|
1319
|
+
e === k.MARKDOWN ? c = 3 : e === k.SECTION_HEADER || e === k.APPENDIX_HEADER ? c = 1 : e === k.MEMO ? (c = 8, l = 20) : e === k.CLAIM ? (c = 4, l = 20) : e === k.FREEFORM && (c = 10);
|
|
1309
1320
|
let u = n;
|
|
1310
1321
|
u = this.layout.reduce((e, t) => Math.max(e, t.y + t.h), 0), this.layout.push({
|
|
1311
1322
|
x: t,
|
|
@@ -1316,16 +1327,16 @@ var Ye = class t {
|
|
|
1316
1327
|
static: !1
|
|
1317
1328
|
});
|
|
1318
1329
|
let d = `Note ${s}`;
|
|
1319
|
-
e ===
|
|
1330
|
+
e === k.CHART ? d = `Chart ${s}` : e === k.TABLE ? d = `Table ${s}` : e === k.SECTION_HEADER ? d = "### Section Header" : e === k.MEMO ? d = "Executive Memo" : e === k.CLAIM ? d = `Claim ${s}` : e === k.APPENDIX_HEADER ? d = "### Appendix" : e === k.FREEFORM && (d = `Widget ${s}`);
|
|
1320
1331
|
let f = o || d, p = "# Markdown Cell\nEnter your markdown content here.";
|
|
1321
|
-
e ===
|
|
1332
|
+
e === k.CHART ? p = "SELECT unnest([1,2,3,4]) as value, 'example' as dim" : e === k.TABLE ? p = "SELECT [1,2,3,4] as value, 'example' as dim" : e === k.SECTION_HEADER || e === k.APPENDIX_HEADER ? p = "" : e === k.MEMO ? p = {
|
|
1322
1333
|
headline: "",
|
|
1323
1334
|
verdict: "inconclusive",
|
|
1324
1335
|
magnitude: "",
|
|
1325
1336
|
cause: "",
|
|
1326
1337
|
action: "",
|
|
1327
1338
|
confidence: "medium"
|
|
1328
|
-
} : e ===
|
|
1339
|
+
} : e === k.CLAIM ? p = { claim: "" } : e === k.FREEFORM && (p = {
|
|
1329
1340
|
html: ee,
|
|
1330
1341
|
query: "SELECT unnest(['alpha','beta','gamma']) as dim, random() * 100 as value"
|
|
1331
1342
|
});
|
|
@@ -1336,7 +1347,7 @@ var Ye = class t {
|
|
|
1336
1347
|
name: f,
|
|
1337
1348
|
width: 0,
|
|
1338
1349
|
height: 0,
|
|
1339
|
-
allowCrossFilter: !(e ===
|
|
1350
|
+
allowCrossFilter: !(e === k.SECTION_HEADER || e === k.APPENDIX_HEADER || e === k.MEMO)
|
|
1340
1351
|
}, this.nextId++, this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0, s;
|
|
1341
1352
|
}
|
|
1342
1353
|
setLayoutType(e) {
|
|
@@ -1427,22 +1438,22 @@ var Ye = class t {
|
|
|
1427
1438
|
}
|
|
1428
1439
|
updateItemFilters(e) {
|
|
1429
1440
|
let t = this.gridItems[e];
|
|
1430
|
-
t &&
|
|
1441
|
+
t && O(t) && (this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0);
|
|
1431
1442
|
}
|
|
1432
1443
|
removeItemCrossFilter(e, t) {
|
|
1433
|
-
|
|
1444
|
+
Re(this.gridItems, e, t) && (this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0);
|
|
1434
1445
|
}
|
|
1435
1446
|
removeItemCrossFilterSource(e) {
|
|
1436
|
-
let t = !!this.gridItems[e]?.chartFilters?.length, n =
|
|
1447
|
+
let t = !!this.gridItems[e]?.chartFilters?.length, n = ze(this.gridItems, e);
|
|
1437
1448
|
return (n.length > 0 || t) && (this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0), n;
|
|
1438
1449
|
}
|
|
1439
1450
|
removeAllFilters() {
|
|
1440
|
-
let e =
|
|
1451
|
+
let e = Be(this.gridItems);
|
|
1441
1452
|
for (let e in this.gridItems) this.gridItems[e].parameters = {};
|
|
1442
1453
|
return e.length > 0 && (this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0), e;
|
|
1443
1454
|
}
|
|
1444
1455
|
updateItemCrossFilters(e, t, n, r) {
|
|
1445
|
-
let i =
|
|
1456
|
+
let i = Le(this.gridItems, e, t, n, r);
|
|
1446
1457
|
return this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0, i;
|
|
1447
1458
|
}
|
|
1448
1459
|
updateItemName(e, t) {
|
|
@@ -1547,7 +1558,7 @@ var Ye = class t {
|
|
|
1547
1558
|
children: n.children || []
|
|
1548
1559
|
});
|
|
1549
1560
|
}
|
|
1550
|
-
},
|
|
1561
|
+
}, Ye = .5, Xe = 1e4, Ze = 2e5, Qe = [
|
|
1551
1562
|
{
|
|
1552
1563
|
value: "demo",
|
|
1553
1564
|
label: "Demo (limited messages)"
|
|
@@ -1572,13 +1583,13 @@ var Ye = class t {
|
|
|
1572
1583
|
value: "deepseek",
|
|
1573
1584
|
label: "DeepSeek"
|
|
1574
1585
|
}
|
|
1575
|
-
],
|
|
1586
|
+
], $e = Object.fromEntries(Qe.map((e) => [e.value, e.label])), et = {
|
|
1576
1587
|
anthropic: "sk-ant-...",
|
|
1577
1588
|
openai: "sk-...",
|
|
1578
1589
|
google: "AIza...",
|
|
1579
1590
|
openrouter: "sk-or-...",
|
|
1580
1591
|
deepseek: "sk-..."
|
|
1581
|
-
}, k = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` — replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; — a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a ⊆ b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order — all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection — grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation — it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL — what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT — never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select — hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain — a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** — a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) — never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` ≡ `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages — give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship — the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology — use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE — its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` → leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows — use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain — on a select item (`~customer.id`) or in `merge a into ~b` (a ⊆ b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary — wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end − start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.\n\n\nFunctions (the `aggregate` family doubles as the Aggregate Functions list):\naggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)\nstring: concat|concat_strict|concat_ws(<arg1>, <arg2>, ..., <argN>) ; split|strpos|contains|trim|regexp_contains|format_time|parse_time(<arg1>, <arg2>) ; len|lower|upper|ltrim|rtrim|hex(<arg1>) ; substring|replace|regexp_extract|regexp_replace(<arg1>, <arg2>, <arg3>)\ndate/time: date|datetime|timestamp|second|minute|hour|day|day_of_week|week|month|quarter|year|month_name|day_name|unix_to_timestamp(<arg1>) ; date_spine(<arg1>, <arg2>) ; current_date|current_datetime()\n date_part(<arg1>, <arg2>) e.g. date_part('2020-01-01'::date, year)\n date_truncate(<arg1>, <arg2>) e.g. date_trunc('2020-01-01'::date, month)\n date_add(<arg1>, <arg2>, <arg3>) e.g. date_add('2020-01-01'::date, month, 1)\n date_sub(<arg1>, <arg2>, <arg3>) e.g. date_sub('2020-01-01'::date, day, 1)\n date_diff(<arg1>, <arg2>, <arg3>) e.g. date_diff('2020-01-01'::date, '2020-01-02'::date, day)\n current_timestamp() e.g. now()\narray/map/struct: unnest|array_distinct|array_sum|map_keys|map_values(<arg1>) ; struct|array(<arg1>, <arg2>, ..., <argN>) ; array_sort|array_to_string(<arg1>, <arg2>) ; array_transform|array_filter|generate_array(<arg1>, <arg2>, <arg3>)\nmath: mod|round|log|power|hash(<arg1>, <arg2>) ; abs|sqrt|random|floor|ceil(<arg1>)\nconditional/cast: case|simple_case|coalesce|greatest|least(<arg1>, <arg2>, ..., <argN>) ; cast|nullif(<arg1>, <arg2>) ; isnull(<arg1>)\ngeo: geo_from_text|geo_x|geo_y|geo_centroid(<arg1>) ; geo_point|geo_distance(<arg1>, <arg2>) ; geo_transform(<arg1>, <arg2>, <arg3>)\nother: is_not_distinct(<arg1>, <arg2>)\n\nSome types carry trait metadata that adds meaning: 'latitude', 'longitude' and 'currency' are all 'float' with extra semantics.\n\nAdditional syntax examples:\nThese less-common patterns have complete, copy-pasteable examples. Do NOT guess\nthe syntax — print the full example on demand with\n`trilogy agent-info syntax example <name>`:\n- `python-datasource` - run a local Python script as a datasource: wrap a function in `trilogy.io.run`, which writes the Arrow IPC stream to stdout for you from a table, dataframe, or list of dicts; declare concepts, map script columns in `datasource (...)`, use `grain (...) file `path.py`;`, then reference locally declared concepts WITHOUT the datasource name as a prefix\n- `query-structure` - the exact clause order of a query, and how to define a rowset\n- `chart` - drawing a result rather than tabulating it: `chart layer <type> ( <role> <- <expr> as <name>, ... )` is a STATEMENT that wraps its own query - the bindings ARE the select; layers, roles, scales, reference lines and `from <select>`\n- `filtered-aggregate` - aggregating only the rows matching a condition, and counting rows correctly\n- `row-level-where-vs-having` - choosing between WHERE and HAVING for a threshold condition\n- `nested-aggregate-group-average` - the \"above 1.2x the group norm\" ask: inner `sum(x) by entity, group`, outer `avg(inner) by group`; use WHERE for the comparison when a separate output-only filter must not narrow the benchmark\n- `pivot-columns` - turn distinct values of a dimension into columns with a reusable `def` macro + filtered aggregate\n- `case-when` - `case when <cond> then <val> ... else <val> end` to relabel codes or bucket a measure into a derived value, then group by it\n- `window-period-over-period` - `lead`/`lag` over (order by ...) to compare a row to an offset period; build a scoped set with `<col> ? <cond>` and filter with `in`\n- `union-stack-channels` - stacking several sources/channels together as rows\n- `except-intersect-setops` - set difference or co-occurrence across sources\n- `scoped-join` - blending a second fact model or rowset into one query\n- `existence-anti-join` - keeping or excluding rows by whether a key matches another model\n- `set-intersect-difference` - count tuples present in / absent from other groups via per-group presence flags - the multi-column INTERSECT/EXCEPT idiom\n- `dedup-before-aggregate` - a `rowset <- select <cols>` collapses identical tuples to one row (SELECT-grain group by) before a downstream sum - the `distinct`/UNION substitute\n- `rollup` - subtotals and a grand total in one pass, and labeling the levels\n- `rank-over-rollup` - rank rollup subtotals/leaves with a SINGLE `rank(a,b) over (partition by level, parent ...)` - not separate ranks per level\n- `staged-membership` - filtering a fine-grain query by a set computed at a coarser grain\n- `correlated-exists-via-grouped-counts` - translate `EXISTS other` / `NOT EXISTS other matching` over the same model into two `count(...) by <grain>` compared in `where` (`> 1` = another exists, `= 1` = no other matches) - pin the correlation grain with `by`", nt = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` — replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; — a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a ⊆ b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order — all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection — grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation — it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL — what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT — never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select — hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain — a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** — a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) — never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` ≡ `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages — give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship — the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology — use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE — its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` → leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows — use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain — on a select item (`~customer.id`) or in `merge a into ~b` (a ⊆ b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary — wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end − start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.", rt = ["aggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)"], it = [
|
|
1592
|
+
}, A = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` — replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; — a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a ⊆ b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order — all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection — grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation — it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL — what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT — never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select — hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain — a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** — a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) — never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` ≡ `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages — give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship — the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology — use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE — its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` → leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows — use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain — on a select item (`~customer.id`) or in `merge a into ~b` (a ⊆ b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary — wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end − start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.\n\n\nFunctions (the `aggregate` family doubles as the Aggregate Functions list):\naggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)\nstring: concat|concat_strict|concat_ws(<arg1>, <arg2>, ..., <argN>) ; split|strpos|contains|trim|regexp_contains|format_time|parse_time(<arg1>, <arg2>) ; len|lower|upper|ltrim|rtrim|hex(<arg1>) ; substring|replace|regexp_extract|regexp_replace(<arg1>, <arg2>, <arg3>)\ndate/time: date|datetime|timestamp|second|minute|hour|day|day_of_week|week|month|quarter|year|month_name|day_name|unix_to_timestamp(<arg1>) ; date_spine(<arg1>, <arg2>) ; current_date|current_datetime()\n date_part(<arg1>, <arg2>) e.g. date_part('2020-01-01'::date, year)\n date_truncate(<arg1>, <arg2>) e.g. date_trunc('2020-01-01'::date, month)\n date_add(<arg1>, <arg2>, <arg3>) e.g. date_add('2020-01-01'::date, month, 1)\n date_sub(<arg1>, <arg2>, <arg3>) e.g. date_sub('2020-01-01'::date, day, 1)\n date_diff(<arg1>, <arg2>, <arg3>) e.g. date_diff('2020-01-01'::date, '2020-01-02'::date, day)\n current_timestamp() e.g. now()\narray/map/struct: unnest|array_distinct|array_sum|map_keys|map_values(<arg1>) ; struct|array(<arg1>, <arg2>, ..., <argN>) ; array_sort|array_to_string(<arg1>, <arg2>) ; array_transform|array_filter|generate_array(<arg1>, <arg2>, <arg3>)\nmath: mod|round|log|power|hash(<arg1>, <arg2>) ; abs|sqrt|random|floor|ceil(<arg1>)\nconditional/cast: case|simple_case|coalesce|greatest|least(<arg1>, <arg2>, ..., <argN>) ; cast|nullif(<arg1>, <arg2>) ; isnull(<arg1>)\ngeo: geo_from_text|geo_x|geo_y|geo_centroid(<arg1>) ; geo_point|geo_distance(<arg1>, <arg2>) ; geo_transform(<arg1>, <arg2>, <arg3>)\nother: is_not_distinct(<arg1>, <arg2>)\n\nSome types carry trait metadata that adds meaning: 'latitude', 'longitude' and 'currency' are all 'float' with extra semantics.\n\nAdditional syntax examples:\nThese less-common patterns have complete, copy-pasteable examples. Do NOT guess\nthe syntax — print the full example on demand with\n`trilogy agent-info syntax example <name>`:\n- `python-datasource` - run a local Python script as a datasource: wrap a function in `trilogy.io.run`, which writes the Arrow IPC stream to stdout for you from a table, dataframe, or list of dicts; declare concepts, map script columns in `datasource (...)`, use `grain (...) file `path.py`;`, then reference locally declared concepts WITHOUT the datasource name as a prefix\n- `query-structure` - the exact clause order of a query, and how to define a rowset\n- `chart` - drawing a result rather than tabulating it: `chart layer <type> ( <role> <- <expr> as <name>, ... )` is a STATEMENT that wraps its own query - the bindings ARE the select; layers, roles, scales, reference lines and `from <select>`\n- `filtered-aggregate` - aggregating only the rows matching a condition, and counting rows correctly\n- `row-level-where-vs-having` - choosing between WHERE and HAVING for a threshold condition\n- `nested-aggregate-group-average` - the \"above 1.2x the group norm\" ask: inner `sum(x) by entity, group`, outer `avg(inner) by group`; use WHERE for the comparison when a separate output-only filter must not narrow the benchmark\n- `pivot-columns` - turn distinct values of a dimension into columns with a reusable `def` macro + filtered aggregate\n- `case-when` - `case when <cond> then <val> ... else <val> end` to relabel codes or bucket a measure into a derived value, then group by it\n- `window-period-over-period` - `lead`/`lag` over (order by ...) to compare a row to an offset period; build a scoped set with `<col> ? <cond>` and filter with `in`\n- `union-stack-channels` - stacking several sources/channels together as rows\n- `except-intersect-setops` - set difference or co-occurrence across sources\n- `scoped-join` - blending a second fact model or rowset into one query\n- `existence-anti-join` - keeping or excluding rows by whether a key matches another model\n- `set-intersect-difference` - count tuples present in / absent from other groups via per-group presence flags - the multi-column INTERSECT/EXCEPT idiom\n- `dedup-before-aggregate` - a `rowset <- select <cols>` collapses identical tuples to one row (SELECT-grain group by) before a downstream sum - the `distinct`/UNION substitute\n- `rollup` - subtotals and a grand total in one pass, and labeling the levels\n- `rank-over-rollup` - rank rollup subtotals/leaves with a SINGLE `rank(a,b) over (partition by level, parent ...)` - not separate ranks per level\n- `staged-membership` - filtering a fine-grain query by a set computed at a coarser grain\n- `correlated-exists-via-grouped-counts` - translate `EXISTS other` / `NOT EXISTS other matching` over the same model into two `count(...) by <grain>` compared in `where` (`> 1` = another exists, `= 1` = no other matches) - pin the correlation grain with `by`", tt = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` — replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; — a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a ⊆ b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order — all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection — grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation — it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL — what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT — never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select — hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain — a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** — a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) — never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` ≡ `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages — give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship — the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology — use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE — its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` → leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows — use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain — on a select item (`~customer.id`) or in `merge a into ~b` (a ⊆ b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary — wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end − start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.", nt = ["aggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)"], rt = [
|
|
1582
1593
|
"aggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)",
|
|
1583
1594
|
"string: concat|concat_strict|concat_ws(<arg1>, <arg2>, ..., <argN>) ; split|strpos|contains|trim|regexp_contains|format_time|parse_time(<arg1>, <arg2>) ; len|lower|upper|ltrim|rtrim|hex(<arg1>) ; substring|replace|regexp_extract|regexp_replace(<arg1>, <arg2>, <arg3>)",
|
|
1584
1595
|
"date/time: date|datetime|timestamp|second|minute|hour|day|day_of_week|week|month|quarter|year|month_name|day_name|unix_to_timestamp(<arg1>) ; date_spine(<arg1>, <arg2>) ; current_date|current_datetime()",
|
|
@@ -1593,7 +1604,7 @@ var Ye = class t {
|
|
|
1593
1604
|
"conditional/cast: case|simple_case|coalesce|greatest|least(<arg1>, <arg2>, ..., <argN>) ; cast|nullif(<arg1>, <arg2>) ; isnull(<arg1>)",
|
|
1594
1605
|
"geo: geo_from_text|geo_x|geo_y|geo_centroid(<arg1>) ; geo_point|geo_distance(<arg1>, <arg2>) ; geo_transform(<arg1>, <arg2>, <arg3>)",
|
|
1595
1606
|
"other: is_not_distinct(<arg1>, <arg2>)"
|
|
1596
|
-
],
|
|
1607
|
+
], it = [
|
|
1597
1608
|
"string",
|
|
1598
1609
|
"bytes",
|
|
1599
1610
|
"bool",
|
|
@@ -1615,6 +1626,121 @@ var Ye = class t {
|
|
|
1615
1626
|
"unix_seconds",
|
|
1616
1627
|
"unknown",
|
|
1617
1628
|
"any"
|
|
1629
|
+
], at = [
|
|
1630
|
+
{
|
|
1631
|
+
name: "case-when",
|
|
1632
|
+
title: "Conditional values with case (relabel codes / bucket a measure)",
|
|
1633
|
+
summary: "`case when <cond> then <val> ... else <val> end` to relabel codes or bucket a measure into a derived value, then group by it",
|
|
1634
|
+
body: "# `case when <cond> then <val> ... else <val> end` builds a derived value.\n# Assign it to an `auto` concept, then group/filter by it like any other.\n# Use `else null` when unmatched rows should drop out of downstream grouping.\nimport iris as iris;\n\nauto petal_size <- case\n when iris.petal_length < 2.0 then 'small'\n when iris.petal_length < 5.0 then 'medium'\n else 'large'\nend;\n\nselect\n petal_size,\n count(iris.petal_length) as flower_count,\n avg(iris.petal_width) as avg_petal_width,\norder by flower_count desc;\n"
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
name: "chart",
|
|
1638
|
+
title: "Chart statement - layers, roles, settings and reference lines",
|
|
1639
|
+
summary: "drawing a result rather than tabulating it: `chart layer <type> ( <role> <- <expr> as <name>, ... )` is a STATEMENT that wraps its own query - the bindings ARE the select; layers, roles, scales, reference lines and `from <select>`",
|
|
1640
|
+
body: "# `chart` is a top-level statement, like `select` - it wraps a query AND says\n# how to draw it. It renders wherever results render: a trilogy code block in a\n# `trilogy render` report, a studio cell, terminal output, and\n# `copy into png|svg|html|pdf '<path>' from chart ...` for a standalone image.\nimport enrollments as enroll;\n\n# --- 1. Minimal form --------------------------------------------------------\n# `layer <type> ( <role> <- <expr> [as <name>], ... )`. The bindings ARE the\n# select: grouping is automatic by the non-aggregated ones, exactly as in a\n# query. NEVER write a separate `select` for a chart.\nchart layer bar ( x_axis <- enroll.department, y_axis <- sum(enroll.credits) as credits );\n\n# A COMPUTED binding must be aliased: `y_axis <- sum(enroll.credits) as credits`.\n# A bare concept reference needs no alias (`x_axis <- enroll.department`) but may\n# take one as a display label. `y_axis <- sum(...)` with no `as` is a parse error.\n#\n# GRAIN: every binding is a select column, so binding a FINER concept to any\n# role (color, group, annotation, ...) refines the grain and splits the marks -\n# exactly as adding that column to a select would. Aggregate annotations at the\n# mark's own grain (`count(enroll.id) as ...`) instead of binding a detail field.\n\n# --- 2. Every part of the statement -----------------------------------------\n# Components may appear in any order after `chart`; at least one `layer` is\n# required, and multiple `layer`s compose into one overlaid chart.\nchart\n set show_title # title from the value-axis label\n set hide_legend\n set scale_y: log # scale_x | scale_y : linear | log | sqrt\n layer bar (\n x_axis <- enroll.department,\n y_axis <- sum(enroll.credits) as credits,\n color <- enroll.completed, # one legend series per value\n annotation <- count(enroll.id) as enrollments # text label on each mark\n )\n order by credits desc # category order follows ORDER BY\n limit 10 # (ascending when there is no ORDER BY)\n place hline at 5 as target; # reference rule; `place vline at ...`\n # too. `at` takes a literal; `as <label>`\n # is optional\n\n# --- 3. `from <select>` - when the bindings cannot express the query ---------\n# Bindings alone have no `where` / `having` / hidden columns. Add `from` plus a\n# COMPLETE select, which carries its own clauses in the usual order:\nchart layer line (\n x_axis <- enroll.year,\n y_axis <- completions,\n color <- enroll.department\n )\n from\n where enroll.year >= 2016\n select\n enroll.year,\n enroll.department,\n count(enroll.id ? enroll.completed = true) as completions\n order by enroll.year asc;\n# With `from select`, EVERY binding must be a bare reference to one of the\n# select's outputs - put transformations inside the select - and layer-level\n# `order by` / `limit` are rejected: they belong to the select.\n\n# --- 4. Types ---------------------------------------------------------------\n# bar vertical bars: x_axis = category, y_axis = measure\n# barh horizontal bars: x_axis = MEASURE, y_axis = category (roles swap)\n# line x_axis = the ordered axis (date/year), y_axis = measure\n# point scatter; `size <- <measure>` scales the marks\n# area line, filled\n# headline big KPI number - needs only `x_axis <- <measure> as <name>`\n# donut the numeric binding sizes the slices; the other positional one -\n# or `color` - names them. No annotations.\n# heatmap x_axis and y_axis are BOTH categories, `color` is the measure\n# boxplot x_axis = category, y_axis = the distribution being summarized\n# `treemap` parses but no renderer implements it; the error names what does.\nchart layer barh ( x_axis <- sum(enroll.credits) as credits, y_axis <- enroll.department );\nchart layer headline ( x_axis <- count(enroll.student_id) as students );\nchart layer donut ( x_axis <- enroll.department, y_axis <- sum(enroll.credits) as credits );\nchart layer heatmap (\n x_axis <- enroll.year,\n y_axis <- enroll.department,\n color <- sum(enroll.credits) as credits\n);\n# A boxplot summarizes RAW rows, so its select has to carry the fact's row grain\n# - hidden with `--` - or output dedup collapses the distribution to the set of\n# distinct values.\nchart layer boxplot ( x_axis <- enroll.department, y_axis <- enroll.credits )\n from select --enroll.id, enroll.department, enroll.credits;\n\n# --- 5. Roles ---------------------------------------------------------------\n# x_axis, y_axis position\n# color one series per value, WITH a legend\n# size mark size (point)\n# group side-by-side bars, or a per-series split on\n# line/point/area - no legend\n# x_trellis / y_trellis small multiples (columns / rows)\n# annotation per-mark text label\n# geo reserved - parses, then raises\n# Each role may be bound at most once per layer; an unknown role name is a parse\n# error. Trellis roles cannot combine with multiple layers, placements, or\n# annotations (Vega-Lite forbids facets inside layered charts).\nchart layer bar (\n x_axis <- enroll.year,\n y_axis <- sum(enroll.credits) as credits,\n group <- enroll.department # side-by-side bars, no legend\n);\n\n# EXPLICIT SERIES COLORS: select a `string::hex` column (the trait comes from\n# `import std.color;`) alongside a `color` binding and each color value takes the\n# hex code found on its rows (missing -> gray); bind the hex column itself to\n# `color` to use the codes directly.\n"
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
name: "correlated-exists-via-grouped-counts",
|
|
1644
|
+
title: "Correlated EXISTS / NOT-EXISTS over rows of the SAME model",
|
|
1645
|
+
summary: "translate `EXISTS other` / `NOT EXISTS other matching` over the same model into two `count(...) by <grain>` compared in `where` (`> 1` = another exists, `= 1` = no other matches) - pin the correlation grain with `by`",
|
|
1646
|
+
body: "# A self-referential EXISTS / NOT-EXISTS (e.g. \"the ONLY enrollee who completed\n# a course that had more than one enrollee\") becomes TWO filtered counts pinned\n# to the correlation grain, compared in `where`:\n# count(k) by <grain> > 1 # EXISTS another row in the group\n# count(k ? cond) by <grain> = 1 # NOT EXISTS another row matching cond\n# (exactly one matches: the row itself)\n# An aggregate condition in `where` groups by the SELECT grain unless pinned.\n# When the correlation grain DIFFERS from the select grain - as here\n# (correlate by course, select per student) - you MUST pin it with `by <grain>`;\n# a bare `count(...) > 0` would group by the select grain and give wrong rows.\n# SCOPE: the pin fixes only the GROUPS. A `where` aggregate always computes\n# over ALL rows of the model; the other `where` predicates never narrow its\n# input rows - exactly right for whole-table EXISTS checks like this one. If\n# the aggregate should see only already-filtered rows, use `having` or stage\n# it with `then where` (a stage's aggregates see only rows passing earlier\n# stages).\nimport enrollments as enroll;\n\nauto enrollees_per_course <- count(enroll.student_id) by enroll.course;\nauto completers_per_course <- count(enroll.student_id ? enroll.completed = true) by enroll.course;\n\nwhere\n enroll.completed = true\n and enrollees_per_course > 1 # the course had at least one OTHER enrollee\n and completers_per_course = 1 # and no OTHER enrollee completed it\nselect\n enroll.student_id,\n count(enroll.course) as sole_completions\norder by sole_completions desc\nlimit 100;\n"
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
name: "dedup-before-aggregate",
|
|
1650
|
+
title: "Deduplicate identical rows before aggregating (UNION DISTINCT)",
|
|
1651
|
+
summary: "a `rowset <- select <cols>` collapses identical tuples to one row (SELECT-grain group by) before a downstream sum - the `distinct`/UNION substitute",
|
|
1652
|
+
body: "# `distinct` and UNION are not available. To collapse identical tuples to one\n# row BEFORE aggregating (so duplicates don't double-count), materialize the\n# tuple in a `rowset <- select <cols>`: a rowset select groups at its own grain,\n# emitting one row per distinct tuple. Then aggregate over the rowset's columns.\n# - ALIAS every rowset column you reference downstream with `as`. Without `as`,\n# the column keeps its FULL source path under the rowset name - `select\n# sales.item.brand_id` is reachable as `rs.sales.item.brand_id`, NOT\n# `rs.brand_id`. `as brand_id` makes it `rs.brand_id`.\nimport enrollments as enroll;\n\n# Collapse repeat (student, course) enrollments to one row each (a student who\n# took a course in multiple years counts once).\nrowset unique_sc <- select\n enroll.student_id as sid,\n enroll.course as course,\n;\n\n# Count over the deduplicated rows - identical (student, course) pairs counted once.\nselect\n unique_sc.course,\n count(unique_sc.sid) as distinct_students,\norder by unique_sc.course asc;\n"
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
name: "except-intersect-setops",
|
|
1656
|
+
title: "Set difference / co-occurrence across sources with except(...) / intersect(...)",
|
|
1657
|
+
summary: "set difference or co-occurrence across sources",
|
|
1658
|
+
body: "# `except(...)` and `intersect(...)` are relational table-valued functions with the\n# SAME shape as `union(...)`: TWO OR MORE inline self-contained select arms, matched\n# by COLUMN POSITION to the trailing `-> (...)` output list. They are SQL SET\n# operators, which differ from the union stack in three ways:\n# - the output is DEDUPLICATED (distinct rows);\n# - whole rows are compared with NULL-SAFE equality (NULL matches NULL - the\n# same identity semantics as `in` / `not in` membership);\n# - for `except(...)` ARM ORDER MATTERS: the FIRST arm is the base population and\n# every later arm is subtracted from it, left to right.\n# Use `except` for \"combinations in A that never appear in B (or C)\" and\n# `intersect` for \"combinations present in every source\".\nimport enrollments as enroll;\n\n# Distinct (course, year) combinations Biology offered that Chemistry did not.\nwith bio_only as except(\n (where enroll.department = 'Biology'\n select enroll.course as course, enroll.year as year),\n (where enroll.department = 'Chemistry'\n select enroll.course as course, enroll.year as year)\n) -> (course, year);\n\nselect\n bio_only.course,\n bio_only.year,\norder by bio_only.course asc nulls first, bio_only.year asc nulls first\nlimit 100;\n\n# Counting the surviving combinations is the idiomatic anti-membership count\n# (e.g. \"customers who bought in store but never online\"):\n# from except((...store combos...), (...web combos...)) -> (last, first, day)\n# select count(day) as combos;\n# ---------------------------------------------------------------------------\n# NOTES:\n# - Same arm rules as `union(...)`: inline arms only, positional binding, equal\n# column count/order/types per arm; reference outputs as `<rowset>.<output>`.\n# - `except((a), (b), (c))` means (a EXCEPT b) EXCEPT c - subtract left to right.\n# `intersect(...)` keeps rows present in EVERY arm (order does not matter).\n# - Outputs are DISTINCT rows: aggregate them directly (`count(x)`) for \"how many\n# unique combinations\" - no extra dedup step needed.\n"
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
name: "existence-anti-join",
|
|
1662
|
+
title: "Existence / anti-join across models",
|
|
1663
|
+
summary: "keeping or excluding rows by whether a key matches another model",
|
|
1664
|
+
body: "# `in` / `not in` against another model's key is the semi-join / anti-join\n# idiom - no manual JOIN. Use it instead of `count(...) is null` hacks.\n# Membership is IDENTITY matching and total: NULL matches NULL, the result is\n# never NULL, and `not in` is the EXACT complement of `in` - a NULL in the set\n# does not empty an anti-join (no SQL NOT-IN footgun), and NULL-keyed rows land\n# in exactly one of the two sides. For strict SQL parity, filter NULLs\n# explicitly (`where key is not null and key in other.key`).\nimport students as students;\nimport enrollments as enroll;\n\n# semi-join: majors with at least one student enrolled in 'Biology 101'\nauto bio_students <- enroll.student_id ? enroll.course = 'Biology 101';\nwhere students.id in bio_students\nselect students.major, count(students.id) as enrolled_students;\n\n# anti-join: students who never enrolled in ANY course\nwhere students.id not in enroll.student_id\nselect students.major, count(students.id) as never_enrolled;\n\n# NULLABLE FLAG: a flag that is true only when a match exists and NULL otherwise\n# (e.g. `enroll.completed` set only for graded rows) needs `is null` for the\n# \"never matched\" case - `not flag` evaluates NULL→dropped, losing those rows.\nwhere enroll.completed is null\nselect enroll.student_id;\n\n# COMPOSITE (multi-column) membership: to test a MULTI-PART key against a set,\n# wrap both sides in matching tuples - `(a, b) in (set.a, set.b)`. This matches\n# ROW-WISE (the whole tuple must appear as one row of the set), so it is NOT the\n# same as `a in set.a and b in set.b`, which matches the parts independently and\n# lets unrelated cross-pairs through. Build the set as a `rowset` selecting the\n# key columns, then test the tuple with `in` / `not in`. Prefer this over\n# concatenating the parts into a single string key.\nrowset multi_course_pairs <- select\n enroll.department as dept,\n enroll.course as course,\nhaving count_distinct(enroll.year) > 1; # (dept, course) offered in >1 year\n\nwhere (enroll.department, enroll.course)\n in (multi_course_pairs.dept, multi_course_pairs.course)\nselect enroll.student_id, count(enroll.id) as enrollments;\n"
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
name: "filtered-aggregate",
|
|
1668
|
+
title: "Filtered aggregate - aggregate only the rows matching a condition",
|
|
1669
|
+
summary: "aggregating only the rows matching a condition, and counting rows correctly",
|
|
1670
|
+
body: "# `?` is a local filter - a filter that applies only to the prior expression-\n# several differently-filtered measures can share one select. Add `by <grain>`\n# to pin an aggregate to a grain other than the select's.\nimport iris as iris;\n\nselect\n iris.species,\n avg(iris.petal_length) as avg_petal,\n avg(iris.petal_length ? iris.petal_width > 1.5) as avg_petal_wide_only,\n count(iris.petal_length ? iris.sepal_length > 6.0) as long_sepal_count,\norder by iris.species asc;\n\n# COUNTING ROWS: `count(k ? cond)` counts DISTINCT values of k - `count` is\n# distinct-by-argument. To count matching ROWS, count the model's unique grain /\n# row key (often named `row_key`), NOT a non-unique sub-key. e.g. counting line\n# items by `count(line_number ? cond)` collapses to the number of distinct line\n# numbers (1..7) and undercounts - use `count(row_key ? cond)`.\n\n# COMPOSITE GRAIN: when SEVERAL keys together identify the thing you are counting,\n# name them with `grain(...)` instead of picking one:\n# count(grain(sales.order_id, sales.item_id)) # order+item lines\n# count_distinct(grain(cust.first_name, cust.last_name, sales.date))\n# `grain(...)` is NEVER NULL, so a combination with a missing member (a customer\n# with no last name) still counts - whereas `count(last_name)` silently drops it.\n# It composes with `?` like any expression: count(grain(a, b) ? cond).\n"
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
name: "nested-aggregate-group-average",
|
|
1674
|
+
title: "Compare a per-entity total to the group average of those totals",
|
|
1675
|
+
summary: "the \"above 1.2x the group norm\" ask: inner `sum(x) by entity, group`, outer `avg(inner) by group`; use WHERE for the comparison when a separate output-only filter must not narrow the benchmark",
|
|
1676
|
+
body: "# Compare each entity's total to the GROUP AVERAGE of those per-entity totals\n# (the \"above 1.2x the group norm\" pattern). Give EACH grain its own `by`: the\n# inner total is per (entity, group); the outer average re-aggregates those\n# totals up to the group. \n# since the aggregates are in the having, both are aggregates *after* the id filter is applied\nimport enrollments as enroll;\n\nauto student_dept_credits <- sum(enroll.credits) by enroll.student_id, enroll.department;\nauto dept_avg_credits <- avg(student_dept_credits) by enroll.department;\n\nwhere enroll.student_id > 103\nselect\n enroll.student_id,\nhaving student_dept_credits > 1.2 * dept_avg_credits;\n\n# BENCHMARK OVER FULL POPULATION + FILTERED OUTPUT: if the department average must span\n# EVERY student but the output should contain only target students (id > 103), put the aggregate\n# comparison in WHERE alongside the output-only filter. Aggregate conditions in WHERE do\n# not filter one another's inputs, so `student_id > 103` does not narrow the department\n# benchmark. Do NOT put the comparison in HAVING after the output filter; that computes a\n# target-students-only department average.\nwhere\n student_dept_credits > 1.2 * dept_avg_credits\n and enroll.student_id > 103\nselect\n enroll.student_id,\n enroll.department,\n student_dept_credits,\n;\n\n# SCOPED INPUT POPULATION: when the totals and their group average must use only\n# rows from a specific period, bind that condition inside the inner aggregate.\n# A separate output-only filter still belongs in the final WHERE and does not\n# narrow the rows feeding either aggregate.\nauto student_dept_credits_2024 <- sum(enroll.credits ? enroll.year = 2024)\n by enroll.student_id, enroll.department;\nauto dept_avg_credits_2024 <- avg(student_dept_credits_2024) by enroll.department;\n\nwhere\n student_dept_credits_2024 > 1.2 * dept_avg_credits_2024\n and enroll.student_id > 103\nselect\n enroll.student_id,\n enroll.department,\n student_dept_credits_2024,\n;\n\n# HIDDEN GROUP KEY: if the requested output is one row per (student, department)\n# but displays only student_id, keep department in the select with the `--`\n# PREFIX. Otherwise identical displayed student rows are deduplicated and the\n# required per-department row grain is lost.\nwhere student_dept_credits_2024 > 1.2 * dept_avg_credits_2024\nselect\n enroll.student_id,\n --enroll.department,\n;\n\n# For more complex boundaries, compute the full-population benchmark in a separate rowset\n# with no output filter, then apply the subset filter only in the final selection.\n"
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
name: "pivot-columns",
|
|
1680
|
+
title: "Pivot dimension values into columns",
|
|
1681
|
+
summary: "turn distinct values of a dimension into columns with a reusable `def` macro + filtered aggregate",
|
|
1682
|
+
body: "# A `def` macro expands inline at each `@call`. Pair it with a filtered\n# aggregate to project one column per category value (a pivot). With no row\n# dimension in the select this yields a single summary row.\nimport iris as iris;\n\ndef species_avg(s) -> avg(iris.petal_length ? iris.species = s);\n\nselect\n @species_avg('setosa') as setosa_avg_petal,\n @species_avg('versicolor') as versicolor_avg_petal,\n @species_avg('virginica') as virginica_avg_petal,\n;\n"
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "python-datasource",
|
|
1686
|
+
title: "Python script datasource - Arrow IPC producer plus Trilogy model",
|
|
1687
|
+
summary: "run a local Python script as a datasource: wrap a function in `trilogy.io.run`, which writes the Arrow IPC stream to stdout for you from a table, dataframe, or list of dicts; declare concepts, map script columns in `datasource (...)`, use `grain (...) file `path.py`;`, then reference locally declared concepts WITHOUT the datasource name as a prefix",
|
|
1688
|
+
body: "# Python datasources require `enable_python_datasources = true` under\n# `[engine.config]` in trilogy.toml. Create these TWO files side by side.\n#\n# File 1: fibonacci.py\n# ---------------------------------------------------------------------------\n# #!/usr/bin/env -S uv run\n# # /// script\n# # requires-python = \">=3.11\"\n# # dependencies = [\"pyarrow>=16\", \"pytrilogy\"]\n# # ///\n#\n# from trilogy.io import run\n#\n#\n# def fibonacci():\n# values = [0, 1]\n# while len(values) < 20:\n# values.append(values[-1] + values[-2])\n# return [{\"index\": i, \"value\": v} for i, v in enumerate(values)]\n#\n#\n# if __name__ == \"__main__\":\n# raise SystemExit(run(fibonacci))\n#\n# `run` converts whatever you return -- a pyarrow table, a pandas/polars frame,\n# a list of dicts, or an iterator of those -- and gives the script a CLI\n# (`--limit`, `--filter`, `--columns`, `--describe`). Run\n# `trilogy source describe ./fibonacci.py` to print the datasource block below\n# instead of writing it against a guessed schema.\n#\n# IMPORTANT: stdout must contain ONLY the Arrow IPC stream. Send diagnostics\n# to stderr. A PEP 723 block lets `uv run` install script dependencies.\n#\n# File 2: query.preql\n# ---------------------------------------------------------------------------\nkey index int;\nproperty index.value int;\n\n# The left side is the physical Arrow column; the right side is the Trilogy\n# concept declared above. The file suffix identifies this as a Python source.\ndatasource fibonacci_numbers (\n index: index,\n value: value,\n)\ngrain (index)\nfile `./fibonacci.py`;\n\n# A datasource declaration does NOT create a namespace. Use local concepts as\n# `index` and `value`, NOT `fibonacci_numbers.index` / `.value`.\nselect\n index,\n value,\norder by index;\n"
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
name: "query-structure",
|
|
1692
|
+
title: "Full query structure - every clause and where it goes (+ rowsets)",
|
|
1693
|
+
summary: "the exact clause order of a query, and how to define a rowset",
|
|
1694
|
+
body: "# Every Trilogy query has the SAME fixed skeleton. Top-level statements come\n# first (each ends with `;`), then ONE query whose clauses appear in this exact\n# order - each clause is OPTIONAL except `select`:\n#\n# <top-level statements>; # import / key / property / auto / metric /\n# # rowset / merge / def / datasource / parameter\n# where <row condition> # 1. filter INPUT rows (BEFORE aggregation)\n# select <col>, <agg> as name, # 2. the projection - grouping is AUTOMATIC by the\n# # non-aggregated columns (never write GROUP BY)\n# <subset|union join a = b (=c)?>* # 3. blend models (zero or more joins; one per concept RIGHT AFTER the\n# # select list - the SQL-like spot)\n# having <result condition> # 4. filter on an AGGREGATED / joined RESULT\n# order by <col> asc|desc # 5. sort\n# limit <n> # 6. cap rows\nimport enrollments as enroll;\nimport students as students;\n\n# --- TOP-LEVEL DEFINITIONS (reusable; all live ABOVE the query) -------------\n# An `auto`/`metric`/`property`/`key` defines a field. It expands inline wherever\n# referenced (a macro), re-evaluated in the referencing query's scope.\nauto completed_credits <- sum(enroll.credits ? enroll.completed = true);\n\n# A ROWSET is a NAMED select: `rowset <name> <- <a full select>;`. It runs as its\n# own scoped query (its own where/select/having) and exposes its outputs as\n# `<name>.<col>`. ALIAS every column you reuse downstream with `as`.\nrowset dept_totals <- select\n enroll.department as department,\n sum(enroll.credits) as total_credits,\n;\n\n# --- THE QUERY - clauses in the fixed order above ---------------------------\nwhere enroll.year >= 2015 # 1. WHERE: per-row filter, before aggregation\nselect # 2. SELECT: grouped automatically by students.major\n students.major,\n count(enroll.id) as enrollments,\n --completed_credits, # a leading `--` HIDES a column from the output\nsubset join enroll.student_id = students.id # 3. JOIN: blend students onto enrollments (after the select list)\nhaving completed_credits > 0 # 4. HAVING: condition on an aggregated RESULT\norder by enrollments desc nulls first # 5. ORDER BY\nlimit 100; # 6. LIMIT\n\n# Reference a rowset's outputs as `<rowset>.<output>` in a later query.\nselect\n dept_totals.department,\n dept_totals.total_credits,\norder by dept_totals.total_credits desc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# NOTES:\n# - Joins sit RIGHT AFTER the select list (the SQL-like, preferred spot). They\n# may also appear before `select` - both parse - but keep them after the\n# select list for consistency.\n# - `where` filters INPUT rows (before aggregation). To filter on something only\n# known AFTER the join / aggregation (e.g. comparing two joined rowsets'\n# counts), select that value (hide it with `--`) and test it in `having` -\n# `having` is the post-aggregation/output filter. There is only ONE `where`.\n# - Output rows dedupe to the select grain. To keep legitimate duplicates (one\n# row per matching fact), select the fact's grain keys - hide with `--` if\n# they shouldn't appear in the output.\n# - No FROM, GROUP BY, DISTINCT, SELECT *, subqueries, or SQL-style set/JOIN\n# operators. Grouping is automatic; blend with the scoped `join` above.\n"
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
name: "rank-over-rollup",
|
|
1698
|
+
title: "Rank within each rollup level (one window over the rollup)",
|
|
1699
|
+
summary: "rank rollup subtotals/leaves with a SINGLE `rank(a,b) over (partition by level, parent ...)` - not separate ranks per level",
|
|
1700
|
+
body: "# When ranking the rows of a ROLLUP within each hierarchy level, use ONE\n# multi-key window over the rollup output. `grouping()` gives the level; the\n# window partitions by (level, parent) so leaves rank within their parent and\n# subtotals rank among themselves - all in one pass. Do NOT write separate rank\n# concepts per level (they mis-rank across the wrong row set). The aggregates and\n# grouping() level concepts are plain - the select's `by rollup (…)` clause stamps\n# the grouping onto all of them.\nimport enrollments as enroll;\n\nauto total <- count(enroll.id);\nauto g_course <- grouping(enroll.course);\nauto g_year <- grouping(enroll.year);\nauto level <- g_course + g_year; # 0 = leaf, 1 = course subtotal, 2 = grand total\nauto parent <- case when g_year = 0 then enroll.course else null end;\nauto rnk <- rank(enroll.course, enroll.year)\n over (partition by level, parent order by total desc);\n\nselect\n enroll.course,\n enroll.year,\n total,\n level,\n --parent,\n rnk,\nby rollup (enroll.course, enroll.year)\norder by level desc nulls first, parent asc nulls first, rnk asc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# NOTE - a composite aggregate over a rollup (a difference/ratio of sums) needs\n# NO special handling: `sum(profit) - sum(loss) as net` rolls up both operands\n# to the same levels, because `by rollup (…)` is a property of the whole select,\n# not of one aggregate.\n"
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
name: "rollup",
|
|
1704
|
+
title: "Subtotals + grand total in one pass with `by rollup (…)`",
|
|
1705
|
+
summary: "subtotals and a grand total in one pass, and labeling the levels",
|
|
1706
|
+
body: "# A `by rollup (d1, d2)` clause AFTER the select list (before having/order by)\n# computes the whole select at MULTIPLE grain levels in one pass - the grouping\n# sets (d1, d2), (d1), () - i.e. per-(d1,d2) leaf rows, a subtotal per d1, and a\n# grand total. (`by cube (d1, d2)` = every combination; `by rollup ()` over the\n# select's own grain.) It applies to EVERY aggregate in the select, so all\n# measures share one consistent grouping.\nimport enrollments as enroll;\n\n# `grouping(d)` = 1 when d is ROLLED UP on a row (a subtotal/grand-total row), 0 on\n# a leaf - use it to LABEL those rows: the rolled-up dimension is otherwise NULL\n# and would collide with any genuine NULL in the data.\nselect\n case when grouping(enroll.department) = 1 then 'ALL DEPARTMENTS'\n else enroll.department end as department,\n case when grouping(enroll.course) = 1 then 'all courses'\n else enroll.course end as course,\n sum(enroll.credits) as total_credits\nby rollup (enroll.department, enroll.course)\nhaving total_credits > 0\norder by department asc nulls first, course asc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# NOTES:\n# - CLAUSE ORDER matches SQL: the `by rollup (…)` grouping clause comes BEFORE\n# `having` (select list -> by rollup -> having -> order by -> limit). The\n# HAVING filters EVERY grouping level - subtotal and grand-total rows too.\n# - SORT: use EXACTLY the sort the question asks for. Rollup rows are ordinary rows\n# and sort with everything else (their rolled-up dimensions are NULL, hence\n# `nulls first`/`nulls last` decides where they land). Sorting by LEVEL is OPTIONAL\n# and only when you actually want leaves/subtotals/totals grouped together: ORDER BY\n# cannot compute a fresh aggregate, so PROJECT the level (`grouping(a) + grouping(b)`)\n# as a HIDDEN (`--`) column and sort by its alias:\n# --grouping(enroll.department) + grouping(enroll.course) as _level\n# order by _level asc, department asc nulls first\n# Do NOT add `_level` to a sort the question already specified - a leading sort key\n# changes WHICH rows survive a `limit`, so it can silently drop the totals.\n# - COMPOSITE measures just work: `sum(a) - sum(b) as net` rolls up BOTH operands\n# to the same levels because the `by rollup (…)` clause covers the whole select.\n# To zero-fill a measure, wrap it: `coalesce(sum(a), 0) as a`.\n# - FILTER-BEFORE-ROLLUP vs `having`: `by rollup (…) having x > t` filters EVERY level AFTER\n# the rollup, so the subtotal/total rows still AGGREGATE the below-threshold leaves (they\n# just aren't displayed). To keep only leaf groups meeting a threshold and roll up JUST\n# those survivors, filter the leaves in a rowset FIRST (`rowset kept <- select … having x >\n# t`), then `by rollup ()` over `kept`. `having` on the rollup itself is the wrong tool when\n# subtotals must exclude the filtered-out leaves.\n"
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
name: "row-level-where-vs-having",
|
|
1710
|
+
title: "Row-level filter (WHERE) vs aggregate filter (HAVING) - don't wrap a per-row threshold in max()/min()",
|
|
1711
|
+
summary: "choosing between WHERE and HAVING for a threshold condition",
|
|
1712
|
+
body: "# A non-aggregate, per-ROW condition belongs in WHERE - it filters the rows that\n# feed every aggregate, BEFORE grouping. HAVING is ONLY for filtering on an\n# aggregate RESULT. These are different operations; do not substitute one for the\n# other.\nimport iris as iris;\n\n# RIGHT - keep only large-sepal FLOWERS, then average their petals.\n# `sepal_length > 6.0` is a per-row test on a column -> WHERE.\nwhere iris.sepal_length > 6.0\nselect\n iris.species,\n avg(iris.petal_length) as avg_petal,\norder by iris.species asc;\n\n# WRONG - `having max(iris.sepal_length) by iris.species > 6.0` does NOT keep\n# large-sepal rows. It keeps whole SPECIES whose single largest flower exceeds\n# 6.0, then averages ALL of that species' flowers (small ones included). Wrapping\n# a per-row threshold in max()/min() in HAVING silently changes the meaning, and\n# AND-ing it with another filter (e.g. a rank cutoff) often yields 0 rows because\n# the two now select disjoint populations.\n#\n# Pick by what the condition is ABOUT:\n# per-row value of a column -> WHERE `where x.amount > 10000`\n# an aggregate's result -> HAVING `having total_amount > 10000`\n# (select it, hide with `--`)\n# aggregate just SOME rows -> inline `?` `sum(x.amount ? x.amount > 10000)`\n#\n# An aggregate condition CAN go in WHERE as a pre-filter (`where count(x) > 0`);\n# it groups by the SELECT grain unless pinned with `by <grain>` (`by *` = a\n# whole-table total). SCOPE: a WHERE aggregate computes over ALL rows of the\n# model - the other WHERE predicates never narrow its input (HAVING, selected,\n# and inline-`?` aggregates DO see the filtered rows). `by` pins the groups,\n# never the input rows; for an aggregate over the filtered rows use `having`\n# or a `then where` stage.\n"
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
name: "scoped-join",
|
|
1716
|
+
title: "Blend two models in one query with a scoped subset/union join",
|
|
1717
|
+
summary: "blending a second fact model or rowset into one query",
|
|
1718
|
+
body: "# A query-scoped `join` blends a second model into ONE select without editing the\n# model files (the query-local equivalent of `merge`). A join DECLARES how the\n# two keys' value domains relate - it never drops rows (rendering is always\n# row-preserving; restrict rows with explicit `is not null` filters):\n# subset join a = b -> declares a's values are contained in b's (a is the\n# narrow side, b superset, all values in a in b)\n# union join a = b -> neither domain contains the other; the key coalesces\n# and unmatched rows from EITHER side are kept\n# Place the clause(s) RIGHT AFTER the `select` list (the SQL-like spot).\nimport enrollments as enroll;\nimport students as students;\n\n# (1) SINGLE-KEY blend: bring students onto enrollments by the shared student\n# key. Enrollment student ids are a SUBSET of all student ids, so declare that;\n# majors with no enrollments are preserved with a NULL count.\nselect\n students.major,\n count(enroll.id) as enrollments,\nsubset join enroll.student_id = students.id\norder by enrollments desc nulls first\nlimit 100;\n\n# The select plus its join clause(s) is ONE statement, so it must end with `;`\n# even when no `order by`/`limit` follows the join - a quick probe that stops\n# right after the join clause still needs the terminator:\nselect\n students.major,\n count(enroll.id) as enrollments,\nsubset join enroll.student_id = students.id;\n\n# ---------------------------------------------------------------------------\n# (2) MULTI-KEY blend - JOIN ON THE FULL GRAIN. When facts share a COMPOSITE\n# grain, write ONE join clause per key. `trilogy explore` shows each fact's grain\n# as `@<k1, k2>` (e.g. a sales/returns fact keyed `@<department, course>`);\n# matching only ONE key fans out and DOUBLE-COUNTS. Two rowsets, joined on both\n# of their shared keys:\nrowset completed_by <- where enroll.completed = true\n select enroll.department as dept, enroll.course as course, count(enroll.id) as completed_cnt;\nrowset incomplete_by <- where enroll.completed = false\n select enroll.department as dept, enroll.course as course, count(enroll.id) as incomplete_cnt;\nrowset null_completed <- where enroll.completed is null\n select enroll.department as dept, enroll.course as course, count(enroll.id) as null_completed_cnt;\n\nselect\n completed_by.dept,\n completed_by.course,\n completed_by.completed_cnt,\n incomplete_by.incomplete_cnt,\n null_completed.null_completed_cnt,\nunion join completed_by.dept = incomplete_by.dept = null_completed.dept\nunion join completed_by.course = incomplete_by.course = null_completed.course\norder by completed_by.dept asc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# (3) SELF-PAIR across two periods/sets (period-over-period) - a useful shape\n# for \"this year vs last year\": build one rowset per period (each aggregated to\n# the SAME keys), filter to restrict to output to one side or the other\n# in having clause.\n\nrowset y2020 <- where enroll.year = 2020 select enroll.department as dept, count(enroll.id) as cnt;\nrowset y2021 <- where enroll.year = 2021 select enroll.department as dept, count(enroll.id) as cnt;\n\nselect\n y2020.dept,\n y2020.cnt as cnt_2020,\n y2021.cnt as cnt_2021,\n y2021.cnt - y2020.cnt as yoy_diff,\nunion join y2020.dept = y2021.dept\nhaving y2021.cnt is not null\norder by yoy_diff desc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# (4) EXPRESSION join keys - a key may be ANY expression (arithmetic, an\n# aggregate, a window), not just a bare field. Here each year is matched to the\n# PRIOR year by joining on an OFFSET key (`prev_year.yr + 1`), so the prior\n# year's count lands on the current row without a self-merge. Only `=` equality\n# is supported (a key below comparison level - wrap a comparison in parens).\nrowset cur_year <- select enroll.year as yr, count(enroll.id) as cnt;\nrowset prev_year <- select enroll.year as yr, count(enroll.id) as cnt;\n\nselect\n cur_year.yr,\n cur_year.cnt as this_year,\n prev_year.cnt as prior_year,\nunion join cur_year.yr = prev_year.yr + 1\norder by cur_year.yr asc\nlimit 100;\n\n# NOTES:\n# - JOIN ON THE FULL GRAIN: one `join` clause per key in the two facts' shared\n# `@<...>` grain. Under-joining (one key of a multi-key grain) can cause\n# duplication. Chain `= c` to also pull a base key into the join\n# group so its properties stay reachable (`union join a.k = b.k = base.k`).\n# - Stacked clauses of the SAME type can share one prefix via `and`:\n# `union join a.k1 = b.k1 and a.k2 = b.k2` == two stacked `union join` clauses.\n# `and` joins distinct KEY-EQUALITY groups (not filters); `= c` chains keys into\n# ONE group - both compose: `union join a.k = b.k = base.k and a.k2 = b.k2`.\n# A COMMA never separates join groups (it is a select-list separator only):\n# `union join a.k1 = b.k1, a.k2 = b.k2` is an error - use `and`.\n# - Terminate the statement with `;` after the LAST clause, whatever that clause\n# is (join, `having`, `order by`, `limit`).\n# - A join key may be ANY expression (a computed/offset key, an aggregate, a\n# window), not only a bare field - see (4). Only `=` equality is supported.\n# - Joins NEVER drop rows: for an intersection (rows present in BOTH sides),\n# add explicit conditions - `where <side_a attr> is not null and <side_b\n# attr> is not null`. A one-sided `is not null` keeps the other side's\n# exclusive rows. Either spelling reads as \"did this side match?\": the\n# side's own JOIN KEY (`where a.k is not null`, tested per side even though\n# the projected key is the coalesced group axis) or any of its measures /\n# attributes (`where a.cnt is not null`). Same in `having`.\n# - `left`/`full`/`inner`/`right`/`cross join` are NOT valid query-scoped joins\n# (they error) - only `subset`/`union`. Map an old `left join a = b` to\n# `subset join b = a` (swap operands) and `full join a = b` to\n# `union join a = b`. A union key-group must be ALL union (can't mix with\n# subset on the SAME key; `union join a = b = c` chains one union group);\n"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
name: "set-intersect-difference",
|
|
1722
|
+
title: "Set intersection / difference on a multi-column key (presence per group)",
|
|
1723
|
+
summary: "count tuples present in / absent from other groups via per-group presence flags - the multi-column INTERSECT/EXCEPT idiom",
|
|
1724
|
+
body: "# To intersect or difference rows on a MULTI-COLUMN key (the SQL INTERSECT /\n# EXCEPT of several columns), group by the full key tuple and build a presence\n# flag per set with `sum(case when <set> then 1 else 0 end)`. Grouping keeps\n# NULL key parts as their own group (correct INTERSECT/EXCEPT semantics).\n# (Avoid concatenating nullable fields into a single key: `||` over a NULL\n# yields NULL, and `concat()`/`concat_ws()` skip NULLs, which conflates\n# distinct tuples - tuple grouping handles NULL parts correctly.)\nimport enrollments as enroll;\n\n# Presence of each (student_id, course) tuple among completed vs open enrollments.\nauto in_completed <- sum(case when enroll.completed = true then 1 else 0 end)\n by enroll.student_id, enroll.course;\nauto in_open <- sum(case when enroll.completed = false then 1 else 0 end)\n by enroll.student_id, enroll.course;\n\nselect\n # intersection: tuples appearing in BOTH sets\n sum(case when in_completed > 0 and in_open > 0 then 1 else 0 end) as in_both,\n # difference: tuples completed but never open (completed EXCEPT open)\n sum(case when in_completed > 0 and in_open = 0 then 1 else 0 end) as completed_only,\n;\n"
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
name: "staged-membership",
|
|
1728
|
+
title: "Stage a coarse-grain membership set, then filter a fine-grain query",
|
|
1729
|
+
summary: "filtering a fine-grain query by a set computed at a coarser grain",
|
|
1730
|
+
body: "# When a query filters on a condition computed at a COARSER grain (e.g. \"items\n# sold in all 3 channels\", \"courses offered in more than one year\"), compute that\n# membership set in its own `rowset` and filter the fine-grain query with `in`.\n# Staging keeps the coarse aggregate from entangling with the fine-grain\n# aggregates (which otherwise re-aggregate / over-count).\nimport enrollments as enroll;\n\n# Coarse stage: courses offered in more than one year.\nrowset multi_year <- select\n enroll.course as course,\n --count_distinct(enroll.year) as year_count,\nhaving year_count > 1\n;\n\n# Fine stage: enrollments per course, only for those courses.\nwhere enroll.course in multi_year.course\nselect\n enroll.course,\n count(enroll.id) as enrollments,\norder by enrollments desc\nlimit 100;\n"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
name: "union-stack-channels",
|
|
1734
|
+
title: "Stack rows from several sources/channels with the union(...) TVF",
|
|
1735
|
+
summary: "stacking several sources/channels together as rows",
|
|
1736
|
+
body: "# `union(...)` is a relational table-valued function: it STACKS the rows of TWO OR\n# MORE self-contained `select` arms, positionally (like SQL `UNION ALL`), into one\n# named result. Use it to combine CHANNELS / SOURCES / labeled populations - ONE\n# ARM PER SOURCE, to any amount. It\n# is a row STACK, NOT a key-join: arms are matched by COLUMN POSITION, so every arm\n# must project the same number of columns, in the same order and types, as the\n# trailing `-> (...)` output list.\nimport enrollments as enroll;\n\n# IMPORTANT: each arm is an INLINE, self-contained select written directly inside\n# the `union(...)` - you CANNOT pass the name of a previously-defined rowset as an\n# arm; put the full select logic (its own `where`, its own aggregation) in the arm.\n# The arms' i-th column maps to the i-th name in the `-> (...)` output list; the\n# surface names inside each arm need not match - only POSITION does. Each arm may\n# group by its own dims (here `course`), so an arm can emit MANY rows.\nwith by_channel as union(\n (where enroll.department = 'Biology'\n select\n 'Biology' as channel,\n enroll.course as course,\n count(enroll.id) as enrollments,\n ),\n (where enroll.department = 'Chemistry'\n select\n 'Chemistry' as channel,\n enroll.course as course,\n count(enroll.id) as enrollments,\n ),\n (where enroll.department = 'Physics'\n select\n 'Physics' as channel,\n enroll.course as course,\n count(enroll.id) as enrollments,\n )\n) -> (channel, course, enrollments);\n\n# Reference the stacked outputs as `<rowset>.<output>`.\nselect\n by_channel.channel,\n by_channel.course,\n by_channel.enrollments,\norder by by_channel.channel, by_channel.enrollments desc nulls first\nlimit 100;\n\n# ---------------------------------------------------------------------------\n# NOTES:\n# - Pass TWO OR MORE arms - add a fourth/fifth the same way; one arm per source.\n# - The trailing `-> (channel, course, enrollments)` NAMES the positional outputs;\n# column order / count / type must line up across every arm.\n# - Every union output is treated as a KEY (grain component). To RE-AGGREGATE the\n# stack (e.g. a grand total across channels), wrap it in an outer aggregate:\n# select sum(by_channel.enrollments) by rollup by_channel.channel as total;\n# - An arm may carry its OWN query-scoped join (`select ... subset join b = a`\n# after its select list) - localize each source's join to the arm that needs it.\n# - This is NOT the forbidden SQL `UNION` keyword between two selects; it is the\n# `union(...)` function form - the cleanest way to stack rows from several\n# sources.\n# - AGGREGATING across a union/set-op: an arm's `select` dedups to its own output\n# grain BEFORE stacking (all selects are select distinct), \n# so stacking raw per-row values (a measure, a `1` flag)\n# and summing/counting OUTSIDE the union silently undercounts whenever two source\n# rows in one arm project the same tuple (e.g. two same-priced sales in a week).\n# Pre-aggregate INSIDE each arm (`sum(sales) as v`, `sum(1) as cnt` - as this\n# example's `count(enroll.id)` does), or pull the arm's grain key through as an\n# extra output column so every fact row stays distinct (downstream selects can\n# just ignore it). And count a GUARANTEED-non-null key, never a nullable display\n# column: `except`/`intersect` keep all-null rows (NULL-safe), which\n# `count(nullable_col)` then silently skips.\n"
|
|
1737
|
+
},
|
|
1738
|
+
{
|
|
1739
|
+
name: "window-period-over-period",
|
|
1740
|
+
title: "Period-over-period ratio with a window function",
|
|
1741
|
+
summary: "`lead`/`lag` over (order by ...) to compare a row to an offset period; build a scoped set with `<col> ? <cond>` and filter with `in`",
|
|
1742
|
+
body: "# `<col> ? <cond>` builds a filtered set of values you can match with `in`.\n# lead()/lag() over (order by ...) compares each row to an offset row - here\n# each year's enrollment count divided by the PRIOR year's.\nimport enrollments as enroll;\n\nauto recent_years <- enroll.year ? enroll.year >= 2015;\n\ndef vs_prior_year(amt) -> round(amt / (lag(amt, 1) over (order by enroll.year asc)), 2);\n\nwhere enroll.year in recent_years\nselect\n enroll.year,\n @vs_prior_year(count(enroll.id)) as enrollment_growth,\nhaving\n enrollment_growth is not null\norder by enroll.year asc nulls first;\n"
|
|
1743
|
+
}
|
|
1618
1744
|
];
|
|
1619
1745
|
//#endregion
|
|
1620
1746
|
//#region dashboards/prompts.ts
|
|
@@ -1688,25 +1814,25 @@ function ft(e) {
|
|
|
1688
1814
|
}
|
|
1689
1815
|
var pt = `You are a world-class assistant for generating queries in Trilogy, a SQL inspired language with similar syntax, Use the the syntax description below and field information to answer the user request. Return your answer with a short reasoning and a valid Trilogy query in triple double quotes.
|
|
1690
1816
|
|
|
1691
|
-
${
|
|
1817
|
+
${A}
|
|
1692
1818
|
|
|
1693
1819
|
For any response to the user, use this format -> put your actual response within triple double quotes with thinking and justification before it, in this format (replace placeholders with relevant content): Reasoning: {{reasoning}} """{{ response }}"""
|
|
1694
1820
|
`;
|
|
1695
|
-
function
|
|
1821
|
+
function j(e) {
|
|
1696
1822
|
return ft(e).map((e) => `[name: ${e.name} | type:${e.type}${e.calculation ? " | alias_for:" + e.calculation : ""}${e.description ? " | description:" + e.description : ""}]`).join(", ");
|
|
1697
1823
|
}
|
|
1698
1824
|
function mt(e, t) {
|
|
1699
|
-
return `${pt}. Using these base and aliased calculations, derivations thereof created with valid Trilogy, and any extra context you have: ${
|
|
1825
|
+
return `${pt}. Using these base and aliased calculations, derivations thereof created with valid Trilogy, and any extra context you have: ${j(t)}, create the best valid Trilogy query to answer the following user input: "${e}" Return the query within triple double quotes with your thinking and justification before it, so of this form as a jinja template: Reasoning: {{reasoning_placeholder}} """{{ trilogy }}""". Example: Because the user asked for sales by year, and revenue is the best sales related field available, we can aggregate revenue by year: """SELECT order.year, sum(revenue) as year_revenue order by order.year asc;"""`;
|
|
1700
1826
|
}
|
|
1701
1827
|
function ht(e, t) {
|
|
1702
|
-
return `${pt}. Using these base and aliased calculations, derivations thereof created with valid Trilogy, and any extra context you have: ${
|
|
1828
|
+
return `${pt}. Using these base and aliased calculations, derivations thereof created with valid Trilogy, and any extra context you have: ${j(t)}, create the closest matching syntactically valid WHERE CLAUSE to filter a dashboard from this request """${e}""". Return only the portion of a SQL query representing the where clause, excluding any ordering. Do not include comments. After you generate the code, review it carefully to make sure it is only the partial where clause of a select, starting with WHERE (imagine if you put a "select 1 ..." in front of it.), with no semicolon at the end. Return your generated code within triple double quotes with thinking and justification before it in the form (as a jinja example) Reasoning: {{reasoning}} """{{ trilogy }}""". Example: Reasoning: Because the user asked for rainbow colors, we can filter to where the color is a primary color """where color in ('red', 'blue', 'violet')"""`;
|
|
1703
1829
|
}
|
|
1704
1830
|
function gt(e, t) {
|
|
1705
|
-
return ct(e,
|
|
1831
|
+
return ct(e, j(t));
|
|
1706
1832
|
}
|
|
1707
1833
|
//#endregion
|
|
1708
1834
|
//#region llm/sharedToolSchemas.ts
|
|
1709
|
-
var
|
|
1835
|
+
var M = [
|
|
1710
1836
|
"line",
|
|
1711
1837
|
"bar",
|
|
1712
1838
|
"barh",
|
|
@@ -1720,7 +1846,7 @@ var _t = [
|
|
|
1720
1846
|
"boxplot",
|
|
1721
1847
|
"treemap",
|
|
1722
1848
|
"beeswarm"
|
|
1723
|
-
],
|
|
1849
|
+
], _t = {
|
|
1724
1850
|
chartType: "bar",
|
|
1725
1851
|
xField: "category",
|
|
1726
1852
|
yField: "revenue",
|
|
@@ -1737,13 +1863,13 @@ var _t = [
|
|
|
1737
1863
|
scaleX: "linear",
|
|
1738
1864
|
scaleY: "linear",
|
|
1739
1865
|
linkY2: !1
|
|
1740
|
-
},
|
|
1866
|
+
}, vt = {
|
|
1741
1867
|
type: "object",
|
|
1742
1868
|
description: "One layer of a layered chart, drawn over the same result set.",
|
|
1743
1869
|
properties: {
|
|
1744
1870
|
chartType: {
|
|
1745
1871
|
type: "string",
|
|
1746
|
-
enum:
|
|
1872
|
+
enum: M,
|
|
1747
1873
|
description: "Type of chart to render"
|
|
1748
1874
|
},
|
|
1749
1875
|
xField: {
|
|
@@ -1776,7 +1902,7 @@ var _t = [
|
|
|
1776
1902
|
description: "Which side to draw this layer value axis on (optional)"
|
|
1777
1903
|
}
|
|
1778
1904
|
}
|
|
1779
|
-
},
|
|
1905
|
+
}, yt = {
|
|
1780
1906
|
chartType: "bar",
|
|
1781
1907
|
showTitle: !0,
|
|
1782
1908
|
layers: [{
|
|
@@ -1793,13 +1919,13 @@ var _t = [
|
|
|
1793
1919
|
value: 1e3,
|
|
1794
1920
|
label: "target"
|
|
1795
1921
|
}]
|
|
1796
|
-
},
|
|
1922
|
+
}, N = {
|
|
1797
1923
|
type: "object",
|
|
1798
1924
|
description: "Chart configuration object",
|
|
1799
1925
|
properties: {
|
|
1800
1926
|
chartType: {
|
|
1801
1927
|
type: "string",
|
|
1802
|
-
enum:
|
|
1928
|
+
enum: M,
|
|
1803
1929
|
description: "Type of chart to render"
|
|
1804
1930
|
},
|
|
1805
1931
|
xField: {
|
|
@@ -1874,7 +2000,7 @@ var _t = [
|
|
|
1874
2000
|
},
|
|
1875
2001
|
layers: {
|
|
1876
2002
|
type: "array",
|
|
1877
|
-
items:
|
|
2003
|
+
items: vt,
|
|
1878
2004
|
description: "Draw several marks over one result set, e.g. bars for a total with a line for an average. When set, the chart type and field bindings above are ignored and each layer supplies its own; the settings above (title, legend, scales) still apply to the chart as a whole. Only line, bar, barh, area, point, boxplot, donut and heatmap can be layers. Cannot be combined with trellisField/trellisRowField. Leave unset for an ordinary single-mark chart."
|
|
1879
2005
|
},
|
|
1880
2006
|
placements: {
|
|
@@ -1904,7 +2030,7 @@ var _t = [
|
|
|
1904
2030
|
description: "Force layers to share one y-axis (true) or get independent ones (false). Omit to let the chart decide from the layers value formats (optional)"
|
|
1905
2031
|
}
|
|
1906
2032
|
}
|
|
1907
|
-
},
|
|
2033
|
+
}, bt = M.map((e) => `'${e}'`).join(", "), P = `Chart type is auto-detected based on data shape unless you specify a chartConfig. Only provide chartConfig if the user specifically requests a chart type or configuration; otherwise let auto-detection handle it.
|
|
1908
2034
|
|
|
1909
2035
|
[Important!] Hiding fields used only for filtering/query structure from output - such as ranks - with the "--" trilogy select syntax may be useful to get the right auto-formatting.
|
|
1910
2036
|
|
|
@@ -1913,12 +2039,12 @@ You DO NOT need to specify colorField as the hexField if it is included; it will
|
|
|
1913
2039
|
You can also add a default available Trilogy import "import std.color;" at the top of a query and cast a string case statement of hex values to ::string::hex to create mappings yourself.
|
|
1914
2040
|
Do this if the user asks for specific coloring.
|
|
1915
2041
|
|
|
1916
|
-
Example chartConfig: ${JSON.stringify(
|
|
2042
|
+
Example chartConfig: ${JSON.stringify(_t)}
|
|
1917
2043
|
|
|
1918
2044
|
To draw more than one mark over the same results — bars for a total with a line for an average, say — set 'layers' instead of the top-level chart type and fields. Each layer names its own chartType and fields; title, legend and scale settings stay on the parent. Add 'placements' for constant reference lines.
|
|
1919
|
-
Example layered chartConfig: ${JSON.stringify(
|
|
2045
|
+
Example layered chartConfig: ${JSON.stringify(yt)}
|
|
1920
2046
|
|
|
1921
|
-
Available chartTypes: ${
|
|
2047
|
+
Available chartTypes: ${bt}`, F = {
|
|
1922
2048
|
name: "connect_data_connection",
|
|
1923
2049
|
description: "Connect or reconnect a data connection that is not currently active. Use this when a query fails because the connection is not active, or when you need to establish a connection before running queries.",
|
|
1924
2050
|
input_schema: {
|
|
@@ -1932,16 +2058,16 @@ Available chartTypes: ${xt}`, N = {
|
|
|
1932
2058
|
};
|
|
1933
2059
|
//#endregion
|
|
1934
2060
|
//#region llm/chatAgentPrompt.ts
|
|
1935
|
-
function
|
|
2061
|
+
function xt(e) {
|
|
1936
2062
|
return e({
|
|
1937
|
-
trilogySyntaxReference:
|
|
1938
|
-
rulesInput:
|
|
1939
|
-
aggFunctions:
|
|
1940
|
-
functions:
|
|
1941
|
-
datatypes:
|
|
2063
|
+
trilogySyntaxReference: A,
|
|
2064
|
+
rulesInput: tt,
|
|
2065
|
+
aggFunctions: nt,
|
|
2066
|
+
functions: rt,
|
|
2067
|
+
datatypes: it
|
|
1942
2068
|
});
|
|
1943
2069
|
}
|
|
1944
|
-
var
|
|
2070
|
+
var I = {
|
|
1945
2071
|
name: "return_to_user",
|
|
1946
2072
|
description: "Signal that you are done and return control to the user. You MUST call this tool when you have completed all requested tasks and are ready for the user's next input. Provide a brief summary of what was accomplished. Never end a turn with plain text — always call this tool when finished.",
|
|
1947
2073
|
input_schema: {
|
|
@@ -1952,7 +2078,7 @@ var Ct = {
|
|
|
1952
2078
|
} },
|
|
1953
2079
|
required: ["message"]
|
|
1954
2080
|
}
|
|
1955
|
-
},
|
|
2081
|
+
}, St = [
|
|
1956
2082
|
{
|
|
1957
2083
|
name: "run_trilogy_query",
|
|
1958
2084
|
description: "Execute a Trilogy query and return tabular results. Use this for data exploration, aggregations, and when the user wants to see raw data in a table format.",
|
|
@@ -1973,7 +2099,7 @@ var Ct = {
|
|
|
1973
2099
|
},
|
|
1974
2100
|
{
|
|
1975
2101
|
name: "chart_trilogy_query",
|
|
1976
|
-
description: `Execute a Trilogy query and display results as a chart. ${
|
|
2102
|
+
description: `Execute a Trilogy query and display results as a chart. ${P}`,
|
|
1977
2103
|
input_schema: {
|
|
1978
2104
|
type: "object",
|
|
1979
2105
|
properties: {
|
|
@@ -1986,7 +2112,7 @@ var Ct = {
|
|
|
1986
2112
|
description: "Data connection name to run the query against"
|
|
1987
2113
|
},
|
|
1988
2114
|
chartConfig: {
|
|
1989
|
-
...
|
|
2115
|
+
...N,
|
|
1990
2116
|
description: "Optional chart configuration. Only provide if user specifically requests a chart type or configuration."
|
|
1991
2117
|
}
|
|
1992
2118
|
},
|
|
@@ -2014,7 +2140,7 @@ var Ct = {
|
|
|
2014
2140
|
required: []
|
|
2015
2141
|
}
|
|
2016
2142
|
},
|
|
2017
|
-
|
|
2143
|
+
F,
|
|
2018
2144
|
{
|
|
2019
2145
|
name: "create_markdown",
|
|
2020
2146
|
description: "Create a rich markdown artifact to display formatted text, summaries, reports, or data-driven content. Supports standard markdown syntax (headers, lists, bold, italic, links, tables, code blocks). When a query is provided, data from the query results can be referenced in the markdown template. Use this for narrative reports, executive summaries, annotated insights, or any rich text content.",
|
|
@@ -2107,7 +2233,7 @@ var Ct = {
|
|
|
2107
2233
|
description: "New title for the artifact"
|
|
2108
2234
|
},
|
|
2109
2235
|
chartConfig: {
|
|
2110
|
-
...
|
|
2236
|
+
...N,
|
|
2111
2237
|
description: "New chart configuration (for chart/results artifacts)"
|
|
2112
2238
|
}
|
|
2113
2239
|
},
|
|
@@ -2140,28 +2266,28 @@ var Ct = {
|
|
|
2140
2266
|
required: ["artifact_ids"]
|
|
2141
2267
|
}
|
|
2142
2268
|
},
|
|
2143
|
-
|
|
2269
|
+
I
|
|
2144
2270
|
];
|
|
2145
|
-
function
|
|
2271
|
+
function Ct(e, t) {
|
|
2146
2272
|
if (!t || t.length === 0) return e;
|
|
2147
2273
|
let n = new Set(t);
|
|
2148
2274
|
return e.filter((e) => !n.has(e.name));
|
|
2149
2275
|
}
|
|
2150
|
-
function
|
|
2276
|
+
function wt(e, t) {
|
|
2151
2277
|
if (!t || t.length === 0) return e;
|
|
2152
2278
|
let n = new Set(e.map((e) => e.name)), r = /* @__PURE__ */ new Set(), i = t.map((e) => {
|
|
2153
2279
|
let t = e.definition.name;
|
|
2154
2280
|
if (n.has(t)) throw Error(`Host tool '${t}' shadows a built-in chat tool`);
|
|
2155
2281
|
if (r.has(t)) throw Error(`Host tool '${t}' is declared twice`);
|
|
2156
2282
|
return r.add(t), e.definition;
|
|
2157
|
-
}), a = e.findIndex((e) => e.name ===
|
|
2283
|
+
}), a = e.findIndex((e) => e.name === I.name);
|
|
2158
2284
|
return a === -1 ? [...e, ...i] : [
|
|
2159
2285
|
...e.slice(0, a),
|
|
2160
2286
|
...i,
|
|
2161
2287
|
...e.slice(a)
|
|
2162
2288
|
];
|
|
2163
2289
|
}
|
|
2164
|
-
var
|
|
2290
|
+
var Tt = [
|
|
2165
2291
|
{ text: "- Every query and chart tool call returns an artifact ID. Use these IDs to reference, update, or remove artifacts later." },
|
|
2166
2292
|
{
|
|
2167
2293
|
tool: "create_markdown",
|
|
@@ -2187,13 +2313,13 @@ var Dt = [
|
|
|
2187
2313
|
tool: "create_markdown",
|
|
2188
2314
|
text: "- When the user asks for a summary, report, or narrative, prefer create_markdown over just text responses - it renders in the artifacts panel."
|
|
2189
2315
|
}
|
|
2190
|
-
],
|
|
2316
|
+
], Et = [{
|
|
2191
2317
|
tool: "search_docs",
|
|
2192
2318
|
text: "- When you are unsure how to express something in Trilogy — window functions, date handling, filtering on aggregates, an unfamiliar keyword — call search_docs (kind: language or function) and then read_doc, rather than guessing at syntax. The reference above is a summary; the docs carry the full idioms."
|
|
2193
2319
|
}, {
|
|
2194
2320
|
tool: "read_doc",
|
|
2195
2321
|
text: "- read_doc returns the full article for an id from search_docs; quote the relevant part back to the user when they ask how the language works."
|
|
2196
|
-
}],
|
|
2322
|
+
}], Dt = [
|
|
2197
2323
|
{
|
|
2198
2324
|
tool: "list_artifacts",
|
|
2199
2325
|
text: "Call list_artifacts to see everything currently in the panel."
|
|
@@ -2212,11 +2338,11 @@ var Dt = [
|
|
|
2212
2338
|
},
|
|
2213
2339
|
{ text: "The artifact panel should tell a coherent story that directly answers the user's latest request — not accumulate a growing pile from every prior turn." }
|
|
2214
2340
|
];
|
|
2215
|
-
function
|
|
2341
|
+
function L(e, t) {
|
|
2216
2342
|
return e.filter((e) => !e.tool || !t.includes(e.tool));
|
|
2217
2343
|
}
|
|
2218
|
-
function
|
|
2219
|
-
let { dataConnectionName: t, availableConnections: n, availableConcepts: r, activeImports: i = [], availableImportsForConnection: a = [], isDataConnectionActive: o = !0, disabledTools: s = [] } = e, c =
|
|
2344
|
+
function Ot(e) {
|
|
2345
|
+
let { dataConnectionName: t, availableConnections: n, availableConcepts: r, activeImports: i = [], availableImportsForConnection: a = [], isDataConnectionActive: o = !0, disabledTools: s = [] } = e, c = L(Tt, s).map((e) => e.text).join("\n"), l = L(Dt, s).map((e, t) => `${t + 1}. ${e.text}`).join("\n"), u = L(Et, s), d = u.length > 0 ? `\n\nDOCUMENTATION:\n${u.map((e) => e.text).join("\n")}` : "", f = r && r.length > 0 ? `\n\nAVAILABLE FIELDS FOR QUERIES:\n${j(r)}` : "", p = !s.includes("connect_data_connection"), m = t && !o ? p ? " (NOT CONNECTED - use connect_data_connection tool to connect before running queries)" : " (NOT CONNECTED - queries will fail; tell the user the data connection is not available and call return_to_user)" : "", h = p ? "\n8. If the data connection is not active, use connect_data_connection to establish the connection before running queries" : "", g = t ? `ACTIVE DATA CONNECTION: ${t}${m}` : "No data connection currently selected. Ask the user which connection to use.", _ = i.length > 0 ? `\nACTIVE DATA SOURCE: ${i[0].name}` : "\nNo data source currently selected. Use select_active_import to select a data source, or use list_available_imports to see what is available.", v = a.length > 0 ? `\n\nAVAILABLE DATA SOURCES:\n${a.map((e) => `- ${e.name}${i.some((t) => t.id === e.id) ? " (active)" : ""}`).join("\n")}` : "";
|
|
2220
2346
|
return `You are a data analysis assistant with access to Trilogy query capabilities.
|
|
2221
2347
|
|
|
2222
2348
|
You have access to tools that can select datasources, execute queries and generate visualizations. When a user asks about data, analyze their request, make sure you are looking at the right source, write a valid Trilogy query, and use the appropriate tool.
|
|
@@ -2226,7 +2352,7 @@ AVAILABLE DATA CONNECTIONS: ${n.length > 0 ? n.join(", ") : "None configured - u
|
|
|
2226
2352
|
${_}${v}
|
|
2227
2353
|
|
|
2228
2354
|
TRILOGY LANGUAGE REFERENCE:
|
|
2229
|
-
${
|
|
2355
|
+
${A}
|
|
2230
2356
|
${f}
|
|
2231
2357
|
|
|
2232
2358
|
IMPORTANT GUIDELINES:
|
|
@@ -2253,9 +2379,9 @@ COMPLETING YOUR RESPONSE:
|
|
|
2253
2379
|
}
|
|
2254
2380
|
//#endregion
|
|
2255
2381
|
//#region llm/subchatSummarize.ts
|
|
2256
|
-
var
|
|
2257
|
-
async function
|
|
2258
|
-
let r =
|
|
2382
|
+
var kt = "You are summarizing a subchat that has just finished. Read the transcript below (messages and tool calls) and produce a 2–4 sentence summary of what was accomplished, what (if anything) failed or remains, and any concrete artifacts (file names, queries, errors). Be specific. The overseer will use this report to update the user — there is no other source of truth, so do not omit important results.", At = "You are summarizing a subchat's progress for an overseer agent. Read the transcript below (messages and tool calls) and produce a concise 2–4 sentence summary of what has happened so far, what has been accomplished, and what (if anything) remains. Be specific about file names, queries, or errors. Do not editorialize.";
|
|
2383
|
+
async function jt(e, t, n = "completion") {
|
|
2384
|
+
let r = Mt(t.messages), i = n === "completion" ? kt : At, a = e.getFastModel(), o = e.model, s = !!a && a !== o;
|
|
2259
2385
|
try {
|
|
2260
2386
|
return s && (e.model = a), (await e.generateCompletion({
|
|
2261
2387
|
prompt: "",
|
|
@@ -2268,42 +2394,42 @@ async function Nt(e, t, n = "completion") {
|
|
|
2268
2394
|
s && (e.model = o);
|
|
2269
2395
|
}
|
|
2270
2396
|
}
|
|
2271
|
-
function
|
|
2397
|
+
function Mt(e) {
|
|
2272
2398
|
return e.map((e, t) => {
|
|
2273
|
-
let n = [`[${t}] ${e.role}: ${
|
|
2274
|
-
if (e.toolCalls && e.toolCalls.length > 0) for (let t of e.toolCalls) n.push(` → ${t.name}(${
|
|
2275
|
-
if (e.toolResults && e.toolResults.length > 0) for (let t of e.toolResults) n.push(` ← ${t.toolName}: ${
|
|
2399
|
+
let n = [`[${t}] ${e.role}: ${Nt(e.content || "", 800)}`];
|
|
2400
|
+
if (e.toolCalls && e.toolCalls.length > 0) for (let t of e.toolCalls) n.push(` → ${t.name}(${Nt(JSON.stringify(t.input), 200)})`);
|
|
2401
|
+
if (e.toolResults && e.toolResults.length > 0) for (let t of e.toolResults) n.push(` ← ${t.toolName}: ${Nt(t.result, 300)}`);
|
|
2276
2402
|
return n.join("\n");
|
|
2277
2403
|
}).join("\n\n");
|
|
2278
2404
|
}
|
|
2279
|
-
function
|
|
2405
|
+
function Nt(e, t) {
|
|
2280
2406
|
return e.length <= t ? e : e.slice(0, t - 1) + "…";
|
|
2281
2407
|
}
|
|
2282
|
-
var
|
|
2283
|
-
function
|
|
2408
|
+
var Pt = 4, Ft = 8, It = 1600;
|
|
2409
|
+
function R(e) {
|
|
2284
2410
|
return e ? Math.ceil(e.length / 4) : 0;
|
|
2285
2411
|
}
|
|
2286
|
-
function
|
|
2287
|
-
let t =
|
|
2412
|
+
function Lt(e) {
|
|
2413
|
+
let t = Pt + R(e.content);
|
|
2288
2414
|
for (let n of e.toolCalls ?? []) {
|
|
2289
|
-
t +=
|
|
2415
|
+
t += Ft + R(n.name);
|
|
2290
2416
|
try {
|
|
2291
|
-
t +=
|
|
2417
|
+
t += R(JSON.stringify(n.input));
|
|
2292
2418
|
} catch {}
|
|
2293
2419
|
}
|
|
2294
|
-
for (let n of e.toolResults ?? []) t +=
|
|
2420
|
+
for (let n of e.toolResults ?? []) t += Ft + R(n.toolName), t += R(n.result), n.imageData && (t += It);
|
|
2295
2421
|
return t;
|
|
2296
2422
|
}
|
|
2297
|
-
function
|
|
2298
|
-
return e.reduce((e, t) => e +
|
|
2423
|
+
function Rt(e) {
|
|
2424
|
+
return e.reduce((e, t) => e + Lt(t), 0);
|
|
2299
2425
|
}
|
|
2300
2426
|
//#endregion
|
|
2301
2427
|
//#region llm/toolLoopCore.ts
|
|
2302
|
-
var
|
|
2303
|
-
function
|
|
2428
|
+
var zt = "<user_input>", Bt = "</user_input>", z = "<system_input>", B = "</system_input>";
|
|
2429
|
+
function Vt(e) {
|
|
2304
2430
|
return e.replace(/<\/?(?:user_input|system_input)>/gi, "");
|
|
2305
2431
|
}
|
|
2306
|
-
function
|
|
2432
|
+
function Ht(e) {
|
|
2307
2433
|
let t = e?.data;
|
|
2308
2434
|
return !Array.isArray(t) || t.length <= 100 ? {
|
|
2309
2435
|
head: t ?? [],
|
|
@@ -2317,11 +2443,11 @@ function Ut(e) {
|
|
|
2317
2443
|
cutCount: t.length - 100
|
|
2318
2444
|
};
|
|
2319
2445
|
}
|
|
2320
|
-
function
|
|
2446
|
+
function Ut(e) {
|
|
2321
2447
|
if (e.success) if (e.artifact) {
|
|
2322
2448
|
let t = e.artifact.config, n = e.artifact.data, r = e.artifact.id, i = "";
|
|
2323
2449
|
if (n) {
|
|
2324
|
-
let e = typeof n.toJSON == "function" ? n.toJSON() : n, a = t?.resultSize ?? (Array.isArray(e?.data) ? e.data.length : 0), { head: o, tail: s, cutCount: c } =
|
|
2450
|
+
let e = typeof n.toJSON == "function" ? n.toJSON() : n, a = t?.resultSize ?? (Array.isArray(e?.data) ? e.data.length : 0), { head: o, tail: s, cutCount: c } = Ht(e);
|
|
2325
2451
|
if (c > 0) {
|
|
2326
2452
|
let t = JSON.stringify({
|
|
2327
2453
|
...e,
|
|
@@ -2339,7 +2465,7 @@ function Wt(e) {
|
|
|
2339
2465
|
else return "Success.";
|
|
2340
2466
|
else return `Error: ${e.error}`;
|
|
2341
2467
|
}
|
|
2342
|
-
function
|
|
2468
|
+
function Wt(e) {
|
|
2343
2469
|
if (e instanceof Error) return e.message;
|
|
2344
2470
|
if (typeof e == "string") return e;
|
|
2345
2471
|
try {
|
|
@@ -2348,18 +2474,18 @@ function Gt(e) {
|
|
|
2348
2474
|
return String(e);
|
|
2349
2475
|
}
|
|
2350
2476
|
}
|
|
2351
|
-
async function
|
|
2477
|
+
async function Gt(e, t, n, r, i, a, o) {
|
|
2352
2478
|
let s = o.maxIterations ?? 20, c = r.getMessages().slice(0, -1).map((e) => e.role === "user" && !e.hidden && e.content ? {
|
|
2353
2479
|
...e,
|
|
2354
|
-
content: `${
|
|
2355
|
-
} : e), l = `${
|
|
2480
|
+
content: `${zt}${Vt(e.content)}${Bt}`
|
|
2481
|
+
} : e), l = `${zt}${Vt(e)}${Bt}`, u = l, d = !1, f = 0;
|
|
2356
2482
|
for (let e = 0; e < s; e++) {
|
|
2357
2483
|
if (a.checkAborted()) return e === 0 && r.addMessage({
|
|
2358
2484
|
role: "assistant",
|
|
2359
2485
|
content: "(Response in progress when stopped)"
|
|
2360
2486
|
}), r.addMessage({
|
|
2361
2487
|
role: "user",
|
|
2362
|
-
content: `${
|
|
2488
|
+
content: `${z}[User requested pause - conversation can be continued]${B}`,
|
|
2363
2489
|
hidden: !0
|
|
2364
2490
|
}), {
|
|
2365
2491
|
terminated: !1,
|
|
@@ -2372,7 +2498,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2372
2498
|
content: "(Response in progress when stopped)"
|
|
2373
2499
|
}), r.addMessage({
|
|
2374
2500
|
role: "user",
|
|
2375
|
-
content: `${
|
|
2501
|
+
content: `${z}[User requested pause - conversation can be continued]${B}`,
|
|
2376
2502
|
hidden: !0
|
|
2377
2503
|
}), {
|
|
2378
2504
|
terminated: !1,
|
|
@@ -2389,7 +2515,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2389
2515
|
content: g
|
|
2390
2516
|
}), r.addMessage({
|
|
2391
2517
|
role: "user",
|
|
2392
|
-
content: `${
|
|
2518
|
+
content: `${z}[User requested pause - conversation can be continued]${B}`,
|
|
2393
2519
|
hidden: !0
|
|
2394
2520
|
}), {
|
|
2395
2521
|
terminated: !1,
|
|
@@ -2410,7 +2536,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2410
2536
|
};
|
|
2411
2537
|
let e = o.noToolCallReminder ?? "You must call a tool to proceed. If you are finished, call the appropriate completion tool to return control to the user. Do not respond with text only.";
|
|
2412
2538
|
console.log("[toolLoopCore] No tool call — re-prompting agent to use a tool");
|
|
2413
|
-
let t = `${
|
|
2539
|
+
let t = `${z}${e}${B}`;
|
|
2414
2540
|
r.addMessage({
|
|
2415
2541
|
role: "user",
|
|
2416
2542
|
content: t,
|
|
@@ -2448,7 +2574,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2448
2574
|
hidden: !0
|
|
2449
2575
|
}), r.addMessage({
|
|
2450
2576
|
role: "user",
|
|
2451
|
-
content: `${
|
|
2577
|
+
content: `${z}[User requested pause - conversation can be continued]${B}`,
|
|
2452
2578
|
hidden: !0
|
|
2453
2579
|
}), {
|
|
2454
2580
|
terminated: !1,
|
|
@@ -2462,7 +2588,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2462
2588
|
} catch (n) {
|
|
2463
2589
|
t = {
|
|
2464
2590
|
success: !1,
|
|
2465
|
-
error: `Tool "${e.name}" failed: ${
|
|
2591
|
+
error: `Tool "${e.name}" failed: ${Wt(n)}`
|
|
2466
2592
|
};
|
|
2467
2593
|
}
|
|
2468
2594
|
let n = {
|
|
@@ -2519,7 +2645,7 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2519
2645
|
finalMessage: t.message
|
|
2520
2646
|
};
|
|
2521
2647
|
}
|
|
2522
|
-
let i =
|
|
2648
|
+
let i = Ut(t);
|
|
2523
2649
|
v.push({
|
|
2524
2650
|
toolCallId: e.id,
|
|
2525
2651
|
toolName: e.name,
|
|
@@ -2566,26 +2692,26 @@ async function Kt(e, t, n, r, i, a, o) {
|
|
|
2566
2692
|
}
|
|
2567
2693
|
//#endregion
|
|
2568
2694
|
//#region llm/compaction.ts
|
|
2569
|
-
var
|
|
2570
|
-
function
|
|
2695
|
+
var Kt = 2e4, qt = 6, Jt = 20, Yt = 6, Xt = "You are a context summarization assistant. You are given the earlier portion of an assistant conversation that is about to fall out of the context window. Produce a structured summary that the assistant will rely on INSTEAD of those messages.\n\nUse exactly these sections:\n## Goal — what the user is ultimately trying to accomplish.\n## Progress — what was built, changed, or discovered, with exact names and ids (editors, dashboards, jobs, connections, queries, fields).\n## Key decisions — choices made and why, including approaches tried and rejected.\n## Current state — where the work stands right now.\n## Open items — unresolved questions, known failures, and obvious next steps.\n\nBe concrete: prefer exact identifiers, query text and error messages over paraphrase. Aim for 300-600 words. Omit nothing the assistant would need to continue seamlessly. Do not editorialize and do not add recommendations of your own.";
|
|
2696
|
+
function Zt(e) {
|
|
2571
2697
|
return e.role === "user" && !(e.toolResults && e.toolResults.length > 0);
|
|
2572
2698
|
}
|
|
2573
|
-
function
|
|
2574
|
-
let n = t.keepRecentTokens ?? 2e4, r = t.minKeepRecentCount ??
|
|
2575
|
-
for (let t = e.length - 1; t >= 0 && !(o >= i || o >= r && s >= n); t--) s +=
|
|
2699
|
+
function Qt(e, t) {
|
|
2700
|
+
let n = t.keepRecentTokens ?? 2e4, r = t.minKeepRecentCount ?? qt, i = t.maxKeepRecentCount ?? Jt, a = t.minArchiveCount ?? Yt, o = 0, s = 0;
|
|
2701
|
+
for (let t = e.length - 1; t >= 0 && !(o >= i || o >= r && s >= n); t--) s += Lt(e[t]), o++;
|
|
2576
2702
|
let c = e.length - o;
|
|
2577
|
-
for (; c > 0 && !
|
|
2703
|
+
for (; c > 0 && !Zt(e[c]);) c--;
|
|
2578
2704
|
return c < a ? -1 : c;
|
|
2579
2705
|
}
|
|
2580
|
-
function
|
|
2581
|
-
return !t || t <= 0 ? !1 : (n ??
|
|
2706
|
+
function $t(e, t, n) {
|
|
2707
|
+
return !t || t <= 0 ? !1 : (n ?? Rt(e.getLLMMessages())) > t;
|
|
2582
2708
|
}
|
|
2583
|
-
async function
|
|
2584
|
-
let r =
|
|
2709
|
+
async function en(e, t, n) {
|
|
2710
|
+
let r = Mt(t), i = e.getFastModel(), a = e.model, o = !!i && i !== a;
|
|
2585
2711
|
try {
|
|
2586
2712
|
return o && (e.model = i), (await e.generateCompletion({
|
|
2587
2713
|
prompt: "",
|
|
2588
|
-
systemPrompt: n ? `${
|
|
2714
|
+
systemPrompt: n ? `${Xt}\n\nAdditional emphasis requested: ${n}` : Xt
|
|
2589
2715
|
}, [{
|
|
2590
2716
|
role: "user",
|
|
2591
2717
|
content: `--- TRANSCRIPT ---\n${r}`
|
|
@@ -2594,22 +2720,22 @@ async function tn(e, t, n) {
|
|
|
2594
2720
|
o && (e.model = a);
|
|
2595
2721
|
}
|
|
2596
2722
|
}
|
|
2597
|
-
function
|
|
2723
|
+
function tn(e) {
|
|
2598
2724
|
return {
|
|
2599
2725
|
role: "user",
|
|
2600
2726
|
hidden: !0,
|
|
2601
2727
|
content: e
|
|
2602
2728
|
};
|
|
2603
2729
|
}
|
|
2604
|
-
async function
|
|
2605
|
-
let r = t.getLLMMessages(), i =
|
|
2730
|
+
async function nn(e, t, n = {}) {
|
|
2731
|
+
let r = t.getLLMMessages(), i = Qt(r, n);
|
|
2606
2732
|
if (i < 0) return null;
|
|
2607
|
-
let a = r.slice(0, i), o = await
|
|
2733
|
+
let a = r.slice(0, i), o = await en(e, a, n.focus), s = t.messages.indexOf(r[i]);
|
|
2608
2734
|
if (s < 0) return null;
|
|
2609
2735
|
a.forEach((e) => {
|
|
2610
2736
|
e.archived = !0;
|
|
2611
2737
|
});
|
|
2612
|
-
let c = n.createSummaryMessage ??
|
|
2738
|
+
let c = n.createSummaryMessage ?? tn, l = `${z}[Conversation compacted. Summary of the earlier conversation:]\n\n${o}${n.appendix ?? ""}${B}`;
|
|
2613
2739
|
t.messages.splice(s, 0, c(l));
|
|
2614
2740
|
let u = {
|
|
2615
2741
|
archivedCount: a.length,
|
|
@@ -2617,17 +2743,17 @@ async function rn(e, t, n = {}) {
|
|
|
2617
2743
|
};
|
|
2618
2744
|
return n.onCompacted?.(u), u;
|
|
2619
2745
|
}
|
|
2620
|
-
function
|
|
2746
|
+
function rn(e) {
|
|
2621
2747
|
let t = e.artifacts.filter((e) => !e.hidden);
|
|
2622
2748
|
return t.length === 0 ? "" : `\n\nArtifacts still available via get_artifact/get_artifact_rows:\n${t.map((e) => {
|
|
2623
2749
|
let t = e.config?.title || e.config?.query?.slice(0, 60) || "", n = e.config?.resultSize == null ? "" : `, ${e.config.resultSize} rows`;
|
|
2624
2750
|
return `- [${e.id}] ${e.type}${t ? ` "${t}"` : ""}${n}`;
|
|
2625
2751
|
}).join("\n")}`;
|
|
2626
2752
|
}
|
|
2627
|
-
async function
|
|
2628
|
-
return
|
|
2753
|
+
async function an(e, t, n = {}) {
|
|
2754
|
+
return nn(e, t, {
|
|
2629
2755
|
...n,
|
|
2630
|
-
appendix: n.appendix ??
|
|
2756
|
+
appendix: n.appendix ?? rn(t),
|
|
2631
2757
|
createSummaryMessage: n.createSummaryMessage ?? ((e) => ({
|
|
2632
2758
|
role: "user",
|
|
2633
2759
|
hidden: !0,
|
|
@@ -2639,10 +2765,10 @@ async function on(e, t, n = {}) {
|
|
|
2639
2765
|
}
|
|
2640
2766
|
});
|
|
2641
2767
|
}
|
|
2642
|
-
async function
|
|
2643
|
-
if (
|
|
2768
|
+
async function on(e, t, n = {}) {
|
|
2769
|
+
if (!$t(t, e.getCompactionThresholdTokens(), t.lastContextTokens)) return null;
|
|
2644
2770
|
try {
|
|
2645
|
-
let r = await
|
|
2771
|
+
let r = await an(e, t, n);
|
|
2646
2772
|
return r && (t.lastContextTokens = void 0), r;
|
|
2647
2773
|
} catch (e) {
|
|
2648
2774
|
return console.error("Automatic conversation compaction failed:", e), null;
|
|
@@ -2650,7 +2776,7 @@ async function sn(e, t, n = {}) {
|
|
|
2650
2776
|
}
|
|
2651
2777
|
//#endregion
|
|
2652
2778
|
//#region llm/editorRefinementTools.ts
|
|
2653
|
-
function
|
|
2779
|
+
function sn(e) {
|
|
2654
2780
|
return e.filter((e) => e.trilogyType === "concept").map((e) => ({
|
|
2655
2781
|
name: e.label,
|
|
2656
2782
|
type: e.datatype || e.type,
|
|
@@ -2660,7 +2786,7 @@ function z(e) {
|
|
|
2660
2786
|
}));
|
|
2661
2787
|
}
|
|
2662
2788
|
function cn(e) {
|
|
2663
|
-
return
|
|
2789
|
+
return j(sn(e));
|
|
2664
2790
|
}
|
|
2665
2791
|
var ln = [
|
|
2666
2792
|
{
|
|
@@ -2703,10 +2829,10 @@ var ln = [
|
|
|
2703
2829
|
name: "edit_chart_config",
|
|
2704
2830
|
description: `Update the chart configuration for visualizing query results. Use this when the user wants to change how results are displayed.
|
|
2705
2831
|
|
|
2706
|
-
${
|
|
2832
|
+
${P}`,
|
|
2707
2833
|
input_schema: {
|
|
2708
2834
|
type: "object",
|
|
2709
|
-
properties: { chartConfig:
|
|
2835
|
+
properties: { chartConfig: N },
|
|
2710
2836
|
required: ["chartConfig"]
|
|
2711
2837
|
}
|
|
2712
2838
|
},
|
|
@@ -2784,7 +2910,7 @@ ${M}`,
|
|
|
2784
2910
|
required: []
|
|
2785
2911
|
}
|
|
2786
2912
|
},
|
|
2787
|
-
|
|
2913
|
+
F
|
|
2788
2914
|
], un = [
|
|
2789
2915
|
{
|
|
2790
2916
|
name: "browse_database_tree",
|
|
@@ -2898,7 +3024,7 @@ ${M}`,
|
|
|
2898
3024
|
required: []
|
|
2899
3025
|
}
|
|
2900
3026
|
},
|
|
2901
|
-
|
|
3027
|
+
F
|
|
2902
3028
|
], dn = ln;
|
|
2903
3029
|
function fn(e) {
|
|
2904
3030
|
return e === "sql" ? un : ln;
|
|
@@ -2912,7 +3038,7 @@ ${i}
|
|
|
2912
3038
|
` : "", c = a ? `
|
|
2913
3039
|
CURRENT CHART CONFIG:
|
|
2914
3040
|
${JSON.stringify(a, null, 2)}
|
|
2915
|
-
` : "", l =
|
|
3041
|
+
` : "", l = sn(o), u = l.length > 0 ? `
|
|
2916
3042
|
AVAILABLE FIELDS FOR QUERIES (${l.length} concepts):
|
|
2917
3043
|
${cn(o)}
|
|
2918
3044
|
` : "";
|
|
@@ -2968,7 +3094,7 @@ AVAILABLE TOOLS:
|
|
|
2968
3094
|
- close_session: Immediately close (only after user confirms or says "done")
|
|
2969
3095
|
${u}
|
|
2970
3096
|
TRILOGY LANGUAGE REFERENCE:
|
|
2971
|
-
${
|
|
3097
|
+
${A}
|
|
2972
3098
|
|
|
2973
3099
|
WORKFLOW:
|
|
2974
3100
|
1. Understand what the user wants to change
|
|
@@ -2990,13 +3116,13 @@ IMPORTANT:
|
|
|
2990
3116
|
}
|
|
2991
3117
|
//#endregion
|
|
2992
3118
|
//#region chats/chat.ts
|
|
2993
|
-
function
|
|
3119
|
+
function V() {
|
|
2994
3120
|
return `art-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 6)}`;
|
|
2995
3121
|
}
|
|
2996
|
-
function
|
|
3122
|
+
function mn() {
|
|
2997
3123
|
return `Chat ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`;
|
|
2998
3124
|
}
|
|
2999
|
-
var
|
|
3125
|
+
var hn = class e {
|
|
3000
3126
|
id;
|
|
3001
3127
|
name;
|
|
3002
3128
|
dataConnectionName;
|
|
@@ -3020,7 +3146,7 @@ var mn = class e {
|
|
|
3020
3146
|
changed;
|
|
3021
3147
|
deleted;
|
|
3022
3148
|
constructor(e = {}) {
|
|
3023
|
-
this.id = e.id || `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, this.name = e.name ||
|
|
3149
|
+
this.id = e.id || `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, this.name = e.name || mn(), this.dataConnectionName = e.dataConnectionName || "", this.dataConnectionId = e.dataConnectionId || "", this.llmConnectionName = e.llmConnectionName || "", this.messages = e.messages || [], this.artifacts = e.artifacts || [], this.activeArtifactIndex = e.activeArtifactIndex ?? -1, this.imports = e.imports || [], this.createdAt = e.createdAt || /* @__PURE__ */ new Date(), this.updatedAt = e.updatedAt || /* @__PURE__ */ new Date(), this.storage = e.storage || "local", this.source = e.source || "user", this.sourceRefId = e.sourceRefId ?? null, this.kind = e.kind || "user", this.parentChatId = e.parentChatId ?? null, this.parentProjectId = e.parentProjectId ?? null, this.pendingInjections = e.pendingInjections || [], this.pendingContextNote = e.pendingContextNote ?? null, this.lastContextTokens = e.lastContextTokens ?? void 0, this.changed = e.changed ?? !0, this.deleted = e.deleted ?? !1;
|
|
3024
3150
|
}
|
|
3025
3151
|
setName(e) {
|
|
3026
3152
|
this.name !== e && (this.name = e, this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0);
|
|
@@ -3035,7 +3161,7 @@ var mn = class e {
|
|
|
3035
3161
|
this.messages.push(e), this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0;
|
|
3036
3162
|
}
|
|
3037
3163
|
addArtifact(e) {
|
|
3038
|
-
return e.id ||=
|
|
3164
|
+
return e.id ||= V(), this.artifacts.push(e), this.activeArtifactIndex = this.artifacts.length - 1, this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0, this.activeArtifactIndex;
|
|
3039
3165
|
}
|
|
3040
3166
|
getArtifactById(e) {
|
|
3041
3167
|
return this.artifacts.find((t) => t.id === e) || null;
|
|
@@ -3069,7 +3195,7 @@ var mn = class e {
|
|
|
3069
3195
|
return this.activeArtifactIndex >= 0 && this.activeArtifactIndex < this.artifacts.length ? this.artifacts[this.activeArtifactIndex] : null;
|
|
3070
3196
|
}
|
|
3071
3197
|
clearMessages() {
|
|
3072
|
-
this.messages = [], this.artifacts = [], this.activeArtifactIndex = -1, this.lastContextTokens = void 0, this.name =
|
|
3198
|
+
this.messages = [], this.artifacts = [], this.activeArtifactIndex = -1, this.lastContextTokens = void 0, this.name = mn(), this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0;
|
|
3073
3199
|
}
|
|
3074
3200
|
addImport(e) {
|
|
3075
3201
|
return this.imports.some((t) => t.id === e.id) ? !1 : (this.imports.push(e), this.updatedAt = /* @__PURE__ */ new Date(), this.changed = !0, !0);
|
|
@@ -3122,19 +3248,19 @@ var mn = class e {
|
|
|
3122
3248
|
changed: !1
|
|
3123
3249
|
});
|
|
3124
3250
|
}
|
|
3125
|
-
},
|
|
3251
|
+
}, gn = [
|
|
3126
3252
|
"line",
|
|
3127
3253
|
"area",
|
|
3128
3254
|
"bar",
|
|
3129
3255
|
"barh",
|
|
3130
3256
|
"point",
|
|
3131
3257
|
"heatmap"
|
|
3132
|
-
],
|
|
3258
|
+
], _n = [
|
|
3133
3259
|
"geo-map",
|
|
3134
3260
|
"tree",
|
|
3135
3261
|
"donut",
|
|
3136
3262
|
"headline"
|
|
3137
|
-
],
|
|
3263
|
+
], vn = [
|
|
3138
3264
|
"bar",
|
|
3139
3265
|
"barh",
|
|
3140
3266
|
"line",
|
|
@@ -3143,7 +3269,7 @@ var mn = class e {
|
|
|
3143
3269
|
"boxplot",
|
|
3144
3270
|
"donut",
|
|
3145
3271
|
"heatmap"
|
|
3146
|
-
],
|
|
3272
|
+
], yn = [
|
|
3147
3273
|
{
|
|
3148
3274
|
id: "group-by",
|
|
3149
3275
|
label: "Group By",
|
|
@@ -3278,7 +3404,7 @@ var mn = class e {
|
|
|
3278
3404
|
field: "trellisField",
|
|
3279
3405
|
columnFilter: "categorical",
|
|
3280
3406
|
allowEmpty: !0,
|
|
3281
|
-
visibleFor:
|
|
3407
|
+
visibleFor: gn,
|
|
3282
3408
|
filterGroup: "appearance"
|
|
3283
3409
|
},
|
|
3284
3410
|
{
|
|
@@ -3287,7 +3413,7 @@ var mn = class e {
|
|
|
3287
3413
|
field: "trellisRowField",
|
|
3288
3414
|
columnFilter: "categorical",
|
|
3289
3415
|
allowEmpty: !0,
|
|
3290
|
-
visibleFor:
|
|
3416
|
+
visibleFor: gn,
|
|
3291
3417
|
filterGroup: "appearance"
|
|
3292
3418
|
},
|
|
3293
3419
|
{
|
|
@@ -3418,7 +3544,7 @@ var mn = class e {
|
|
|
3418
3544
|
],
|
|
3419
3545
|
filterGroup: "appearance"
|
|
3420
3546
|
}
|
|
3421
|
-
],
|
|
3547
|
+
], bn = [
|
|
3422
3548
|
{
|
|
3423
3549
|
value: "bar",
|
|
3424
3550
|
label: "Bar Chart",
|
|
@@ -3479,7 +3605,7 @@ var mn = class e {
|
|
|
3479
3605
|
label: "Beeswarm",
|
|
3480
3606
|
icon: "mdi mdi-chart-bubble"
|
|
3481
3607
|
}
|
|
3482
|
-
],
|
|
3608
|
+
], xn = "#1f77b4", Sn = "#9467bd", Cn = "#FF7F7F", H = (e, t) => e[t], wn = [
|
|
3483
3609
|
"year",
|
|
3484
3610
|
"week",
|
|
3485
3611
|
"day",
|
|
@@ -3487,7 +3613,7 @@ var mn = class e {
|
|
|
3487
3613
|
"minute",
|
|
3488
3614
|
"second",
|
|
3489
3615
|
"quarter"
|
|
3490
|
-
],
|
|
3616
|
+
], Tn = [
|
|
3491
3617
|
"decade",
|
|
3492
3618
|
"year",
|
|
3493
3619
|
"month",
|
|
@@ -3497,50 +3623,50 @@ var mn = class e {
|
|
|
3497
3623
|
"minute",
|
|
3498
3624
|
"second",
|
|
3499
3625
|
"day_of_week"
|
|
3500
|
-
], U = (e) => e.traits ? e.traits.some((e) =>
|
|
3626
|
+
], U = (e) => e.traits ? e.traits.some((e) => Tn.some((t) => e === t || e.endsWith("_" + t))) : !1, En = (e, t) => {
|
|
3501
3627
|
let n = t.get(e);
|
|
3502
3628
|
return n ? U(n) : !1;
|
|
3503
|
-
},
|
|
3629
|
+
}, Dn = [
|
|
3504
3630
|
"rank",
|
|
3505
3631
|
"index",
|
|
3506
3632
|
"grade",
|
|
3507
3633
|
"flag",
|
|
3508
3634
|
"letter_grade"
|
|
3509
|
-
],
|
|
3510
|
-
...
|
|
3511
|
-
...
|
|
3635
|
+
], On = [
|
|
3636
|
+
...wn,
|
|
3637
|
+
...Dn,
|
|
3512
3638
|
"identifier",
|
|
3513
3639
|
"decade"
|
|
3514
3640
|
];
|
|
3515
|
-
function
|
|
3641
|
+
function kn(e) {
|
|
3516
3642
|
return new Date(e);
|
|
3517
3643
|
}
|
|
3518
3644
|
var W = (e, t, n) => {
|
|
3519
3645
|
if (!e || !t.get(e)) return !1;
|
|
3520
3646
|
let r = t.get(e);
|
|
3521
|
-
return r ?
|
|
3522
|
-
},
|
|
3647
|
+
return r ? An(r, n) : !1;
|
|
3648
|
+
}, An = (e, t) => !!(e.traits && e.traits.some((e) => e.endsWith(t))), G = (e) => e.purpose === "key" || U(e) ? !1 : [
|
|
3523
3649
|
n.NUMBER,
|
|
3524
3650
|
n.INTEGER,
|
|
3525
3651
|
n.FLOAT
|
|
3526
|
-
].includes(e.type) && !e.traits?.some((e) => e.endsWith("latitude") || e.endsWith("longitude")),
|
|
3652
|
+
].includes(e.type) && !e.traits?.some((e) => e.endsWith("latitude") || e.endsWith("longitude")), jn = (e) => !!(e.traits && Dn.some((t) => e.traits?.includes(t))), Mn = (e) => !!([n.STRING].includes(e.type) && e.traits?.some((e) => e.endsWith("url_image"))), K = (e) => !!([
|
|
3527
3653
|
n.DATE,
|
|
3528
3654
|
n.DATETIME,
|
|
3529
3655
|
n.TIME,
|
|
3530
3656
|
n.TIMESTAMP
|
|
3531
|
-
].includes(e.type) || e.traits &&
|
|
3657
|
+
].includes(e.type) || e.traits && wn.some((t) => e.traits?.includes(t))), Nn = (e) => !!([n.STRING, n.BOOLEAN].includes(e.type) || e.purpose === "key" || e.traits && On.some((t) => e.traits?.includes(t))), Pn = (e) => e.traits?.includes("hex") ?? !1, q = (e, t) => e.traits ? e.traits.some((e) => e.endsWith(t)) : !1, J = (e, t) => {
|
|
3532
3658
|
let n = [];
|
|
3533
3659
|
return t.forEach((t, r) => {
|
|
3534
|
-
(e === "all" || e === "numeric" && G(t) && !K(t) || e === "categorical" &&
|
|
3660
|
+
(e === "all" || e === "numeric" && G(t) && !K(t) || e === "categorical" && Nn(t) || e === "temporal" && K(t) || e === "latitude" && q(t, "latitude") || e === "longitude" && q(t, "longitude") || e === "geographic" && t.traits?.some((e) => e.endsWith("state") || e.endsWith("state_short") || e.endsWith("country"))) && n.push(t);
|
|
3535
3661
|
}), n;
|
|
3536
|
-
},
|
|
3662
|
+
}, Fn = (e, t) => {
|
|
3537
3663
|
let n = t.filter((t) => t.name !== e.xField && t.name !== e.yField);
|
|
3538
3664
|
return n.length > 0 ? n[0] : t[0];
|
|
3539
|
-
},
|
|
3665
|
+
}, In = (e, t, n) => {
|
|
3540
3666
|
let r = {
|
|
3541
3667
|
showTitle: !1,
|
|
3542
3668
|
hideLegend: !1
|
|
3543
|
-
}, i = J("numeric", t), a = J("categorical", t).filter((e) => !
|
|
3669
|
+
}, i = J("numeric", t), a = J("categorical", t).filter((e) => !Pn(e)), o = J("temporal", t), s = J("latitude", t), c = J("longitude", t), l = J("geographic", t);
|
|
3544
3670
|
if (t.size === 0) return {};
|
|
3545
3671
|
if (!n && i.length === 0 && l.length === 0 && c.length === 0 && s.length === 0) return r.chartType = "headline", r;
|
|
3546
3672
|
let u = a.filter((e) => U(e));
|
|
@@ -3555,7 +3681,7 @@ var W = (e, t, n) => {
|
|
|
3555
3681
|
n.size > 7 ? r.chartType = "barh" : r.chartType = "bar";
|
|
3556
3682
|
} else i.length >= 2 ? e.length === 1 ? r.chartType = "headline" : r.chartType = "point" : a.length >= 2 && i.length > 0 ? r.chartType = "heatmap" : i.length > 0 && a.length == 0 && (r.chartType = "headline");
|
|
3557
3683
|
if (r.chartType === "barh") {
|
|
3558
|
-
r.yField = H(a, 0)?.name, i.length > 0 && (r.xField =
|
|
3684
|
+
r.yField = H(a, 0)?.name, i.length > 0 && (r.xField = Fn(r, i).name);
|
|
3559
3685
|
let e = a.filter((e) => e.name !== r.yField && e.name !== r.xField);
|
|
3560
3686
|
e.length > 0 && (r.colorField = e[0].name), i.length > 1 && !r.colorField && (r.colorField = i[1].name);
|
|
3561
3687
|
let t = a.filter((e) => e.name !== r.yField && e.name !== r.xField && e.name !== r.colorField);
|
|
@@ -3603,7 +3729,7 @@ var W = (e, t, n) => {
|
|
|
3603
3729
|
n.length > 0 && (r.trellisRowField = n[0].name), n.length > 1 && (r.trellisField = n[1].name);
|
|
3604
3730
|
}
|
|
3605
3731
|
return r;
|
|
3606
|
-
},
|
|
3732
|
+
}, Ln = (e, t) => {
|
|
3607
3733
|
let n = J("numeric", t), r = J("categorical", t), i = J("temporal", t), a = J("latitude", t), o = J("longitude", t), s = J("geographic", t), c = [];
|
|
3608
3734
|
if (r.length > 0 && c.push("beeswarm"), n.length === 0) return (a.length > 0 && o.length > 0 || s.length > 0) && c.push("geo-map"), c.push("headline"), c;
|
|
3609
3735
|
let l = r.filter((e) => U(e));
|
|
@@ -3612,19 +3738,19 @@ var W = (e, t, n) => {
|
|
|
3612
3738
|
for (let r = 0; r < Math.min(e.length, 50); r++) e[r] && e[r][t] !== void 0 && n.add(e[r][t]);
|
|
3613
3739
|
c.push("bar"), c.push("barh"), c.push("donut");
|
|
3614
3740
|
}
|
|
3615
|
-
return n.length >= 2 && c.push("point"), r.length >= 2 && n.length > 0 && c.push("heatmap"), n.length > 0 && (c.push("boxplot"), c.push("headline")), (a.length > 0 && o.length > 0 || s.length > 0) && c.push("geo-map"), c.filter((e) =>
|
|
3616
|
-
},
|
|
3741
|
+
return n.length >= 2 && c.push("point"), r.length >= 2 && n.length > 0 && c.push("heatmap"), n.length > 0 && (c.push("boxplot"), c.push("headline")), (a.length > 0 && o.length > 0 || s.length > 0) && c.push("geo-map"), c.filter((e) => bn.map((e) => e.value).includes(e));
|
|
3742
|
+
}, Rn = (e) => q(e, "latitude") ? "latitude" : q(e, "longitude") ? "longitude" : e.traits?.some((e) => e.endsWith("state") || e.endsWith("state_short") || e.endsWith("country")) ? "geographic" : K(e) ? "temporal" : U(e) ? "categorical" : G(e) ? "numeric" : Nn(e) ? "categorical" : "other", zn = (e) => {
|
|
3617
3743
|
let t = [];
|
|
3618
3744
|
return e.forEach((e) => {
|
|
3619
|
-
t.push(`${e.name} (${
|
|
3745
|
+
t.push(`${e.name} (${Rn(e)})`);
|
|
3620
3746
|
}), t.length > 0 ? t.join(", ") : "no fields";
|
|
3621
|
-
},
|
|
3622
|
-
let i =
|
|
3747
|
+
}, Bn = (e, n, r) => {
|
|
3748
|
+
let i = Ln(e, n), a = [];
|
|
3623
3749
|
if (r.layers && r.layers.length > 0) {
|
|
3624
3750
|
let e = [], a = Array.from(n.keys());
|
|
3625
3751
|
return r.layers.forEach((r, i) => {
|
|
3626
3752
|
let o = `Layer ${i + 1}`;
|
|
3627
|
-
|
|
3753
|
+
vn.includes(r.chartType) || e.push(`${o}: chart type "${r.chartType}" cannot be used as a layer. Layerable types: ${vn.join(", ")}.`);
|
|
3628
3754
|
for (let i of t) {
|
|
3629
3755
|
let t = r[i];
|
|
3630
3756
|
typeof t == "string" && t !== "" && !n.has(t) && e.push(`${o}: field "${t}" (${i}) does not exist in the query results. Available columns: ${a.join(", ")}.`);
|
|
@@ -3638,16 +3764,16 @@ var W = (e, t, n) => {
|
|
|
3638
3764
|
};
|
|
3639
3765
|
}
|
|
3640
3766
|
if (!i.includes(r.chartType)) {
|
|
3641
|
-
let t =
|
|
3767
|
+
let t = zn(n);
|
|
3642
3768
|
return {
|
|
3643
3769
|
valid: !1,
|
|
3644
3770
|
chartTypeError: `Chart type "${r.chartType}" is not compatible with the available fields (${t}). Eligible chart types: ${i.join(", ")}.`,
|
|
3645
3771
|
fieldErrors: [],
|
|
3646
3772
|
eligibleChartTypes: i,
|
|
3647
|
-
suggestedConfig:
|
|
3773
|
+
suggestedConfig: In(e, n)
|
|
3648
3774
|
};
|
|
3649
3775
|
}
|
|
3650
|
-
let o =
|
|
3776
|
+
let o = In(e, n, [
|
|
3651
3777
|
"line",
|
|
3652
3778
|
"bar",
|
|
3653
3779
|
"barh",
|
|
@@ -3674,7 +3800,7 @@ var W = (e, t, n) => {
|
|
|
3674
3800
|
let t = r[e];
|
|
3675
3801
|
typeof t == "string" && t !== "" && !n.has(t) && a.push(`Field "${t}" (${e}) does not exist in the query results. Available columns: ${s.join(", ")}.`);
|
|
3676
3802
|
}
|
|
3677
|
-
let c =
|
|
3803
|
+
let c = yn.filter((e) => !e.allowEmpty && e.visibleFor.includes(r.chartType));
|
|
3678
3804
|
for (let e of c) {
|
|
3679
3805
|
let t = r[e.field];
|
|
3680
3806
|
(typeof t != "string" || t === "") && a.push(`Chart type "${r.chartType}" requires ${e.label} (${String(e.field)}) to be specified, but it is missing.`);
|
|
@@ -3685,7 +3811,7 @@ var W = (e, t, n) => {
|
|
|
3685
3811
|
}
|
|
3686
3812
|
if ((r.chartType === "line" || r.chartType === "area") && r.xField && n.has(r.xField)) {
|
|
3687
3813
|
let e = n.get(r.xField);
|
|
3688
|
-
!K(e) && !
|
|
3814
|
+
!K(e) && !Nn(e) && !G(e) && a.push(`For ${r.chartType} charts, xField should typically be a temporal or categorical column.`);
|
|
3689
3815
|
}
|
|
3690
3816
|
let l = { ...o };
|
|
3691
3817
|
for (let [e, t] of Object.entries(r)) t != null && (typeof t == "string" && t === "" || (l[e] = t));
|
|
@@ -3695,7 +3821,7 @@ var W = (e, t, n) => {
|
|
|
3695
3821
|
eligibleChartTypes: i,
|
|
3696
3822
|
suggestedConfig: l
|
|
3697
3823
|
};
|
|
3698
|
-
},
|
|
3824
|
+
}, Vn = (e) => `Chart configuration is invalid for the data:\n${[e.chartTypeError, ...e.fieldErrors].filter(Boolean).map((e) => ` - ${e}`).join("\n")}\nEligible chart types for this data: ${e.eligibleChartTypes.join(", ") || "none"}.\nSuggested auto-detected config: ${JSON.stringify(e.suggestedConfig)}\nTip: Omit chartConfig to let auto-detection choose the best chart type and field mapping.`, Y = (e, t) => {
|
|
3699
3825
|
if (!e || !t.get(e)) return {};
|
|
3700
3826
|
let r = t.get(e);
|
|
3701
3827
|
if (!r) return {};
|
|
@@ -3720,7 +3846,7 @@ var W = (e, t, n) => {
|
|
|
3720
3846
|
case n.INTEGER: return {};
|
|
3721
3847
|
default: return {};
|
|
3722
3848
|
}
|
|
3723
|
-
},
|
|
3849
|
+
}, Hn = (e, t, n = null) => {
|
|
3724
3850
|
if (!e || !t.get(e)) return {};
|
|
3725
3851
|
let r = t.get(e);
|
|
3726
3852
|
return r ? W(e, t, "day_of_week_name") ? { sort: [
|
|
@@ -3731,7 +3857,7 @@ var W = (e, t, n) => {
|
|
|
3731
3857
|
"Thursday",
|
|
3732
3858
|
"Friday",
|
|
3733
3859
|
"Saturday"
|
|
3734
|
-
] } :
|
|
3860
|
+
] } : jn(r) || U(r) || K(r) ? { sort: {
|
|
3735
3861
|
field: e,
|
|
3736
3862
|
order: "ascending"
|
|
3737
3863
|
} } : G(r) ? n ? { sort: {
|
|
@@ -3756,19 +3882,19 @@ var W = (e, t, n) => {
|
|
|
3756
3882
|
n.DATETIME,
|
|
3757
3883
|
n.TIMESTAMP,
|
|
3758
3884
|
n.INTEGER
|
|
3759
|
-
].includes(r.type) || [n.DATE].includes(r.type) ? "temporal" : "ordinal" :
|
|
3760
|
-
},
|
|
3885
|
+
].includes(r.type) || [n.DATE].includes(r.type) ? "temporal" : "ordinal" : jn(r) || U(r) ? "ordinal" : G(r) ? "quantitative" : "nominal" : "nominal";
|
|
3886
|
+
}, Un = (e, t, n = {}, i = !0, a = {}) => e ? {
|
|
3761
3887
|
field: e,
|
|
3762
3888
|
type: X(e, t),
|
|
3763
3889
|
title: r(t.get(e)?.description || e),
|
|
3764
3890
|
...Y(e, t),
|
|
3765
3891
|
...n,
|
|
3766
|
-
...i ?
|
|
3892
|
+
...i ? Hn(e, t) : {},
|
|
3767
3893
|
...a.scale !== void 0 || a.zero !== void 0 ? { scale: {
|
|
3768
3894
|
...a.scale !== void 0 && { type: a.scale },
|
|
3769
3895
|
...a.zero !== void 0 && { zero: a.zero }
|
|
3770
3896
|
} } : {}
|
|
3771
|
-
} : {},
|
|
3897
|
+
} : {}, Wn = (e = 0) => e > 0 ? `_l${e}` : "", Z = (e, t = "") => `${e}${t}`, Gn = (e = "") => ({
|
|
3772
3898
|
fillOpacity: {
|
|
3773
3899
|
condition: {
|
|
3774
3900
|
param: Z("select", e),
|
|
@@ -3788,7 +3914,7 @@ var W = (e, t, n) => {
|
|
|
3788
3914
|
}],
|
|
3789
3915
|
value: 0
|
|
3790
3916
|
}
|
|
3791
|
-
}),
|
|
3917
|
+
}), Kn = (e, t, n, r = "color") => {
|
|
3792
3918
|
let i = !1;
|
|
3793
3919
|
return n === "quantitative" && r === "color" && (i = !0), e && e.length > 10 ? {
|
|
3794
3920
|
orient: t ? "bottom" : "right",
|
|
@@ -3801,14 +3927,14 @@ var W = (e, t, n) => {
|
|
|
3801
3927
|
direction: t ? "horizontal" : "vertical",
|
|
3802
3928
|
titleFontSize: t ? 10 : 12
|
|
3803
3929
|
};
|
|
3804
|
-
},
|
|
3805
|
-
let l = { tickCount:
|
|
3930
|
+
}, qn = 15, Jn = (e, t, n, i = !1, a = "light", o = !1, s = [], c = "") => {
|
|
3931
|
+
let l = { tickCount: qn }, u = [], d = [], f = s || [], p = Array.from(n.entries()).filter(([e, t]) => t.traits?.includes("hex")).map(([e, t]) => e);
|
|
3806
3932
|
if (t) {
|
|
3807
3933
|
let e = X(t, n);
|
|
3808
3934
|
l = {
|
|
3809
3935
|
...l,
|
|
3810
|
-
...
|
|
3811
|
-
}, (e === "nominal" || e === "ordinal") && (d = Array.from(new Set(f.map((e) => String(e[t] ?? "")))).sort((e, t) => e.localeCompare(t))), u = d.slice(0,
|
|
3936
|
+
...Kn(t, i, e)
|
|
3937
|
+
}, (e === "nominal" || e === "ordinal") && (d = Array.from(new Set(f.map((e) => String(e[t] ?? "")))).sort((e, t) => e.localeCompare(t))), u = d.slice(0, qn);
|
|
3812
3938
|
}
|
|
3813
3939
|
let m = (e) => (!o && Object.keys(l).length > 0 ? e.legend = l : o && (e.legend = null), e);
|
|
3814
3940
|
if (t && n.get(t)) {
|
|
@@ -3837,23 +3963,23 @@ var W = (e, t, n) => {
|
|
|
3837
3963
|
...c === null ? {} : { condition: [{
|
|
3838
3964
|
param: Z("highlight", c),
|
|
3839
3965
|
empty: !1,
|
|
3840
|
-
value:
|
|
3966
|
+
value: Cn
|
|
3841
3967
|
}, {
|
|
3842
3968
|
param: Z("select", c),
|
|
3843
3969
|
empty: !1,
|
|
3844
|
-
value:
|
|
3970
|
+
value: Cn
|
|
3845
3971
|
}] },
|
|
3846
3972
|
...Y(t, n)
|
|
3847
3973
|
});
|
|
3848
3974
|
}
|
|
3849
3975
|
return m({});
|
|
3850
|
-
},
|
|
3976
|
+
}, Yn = (e, t, n = !1, i = !1) => {
|
|
3851
3977
|
let a = { tickCount: 5 };
|
|
3852
3978
|
if (e) {
|
|
3853
3979
|
let r = X(e, t);
|
|
3854
3980
|
a = {
|
|
3855
3981
|
...a,
|
|
3856
|
-
...
|
|
3982
|
+
...Kn(e, n, r, "size")
|
|
3857
3983
|
};
|
|
3858
3984
|
}
|
|
3859
3985
|
return e && t.get(e) ? ((e) => (!i && Object.keys(a).length > 0 ? e.legend = a : i && (e.legend = null), e))({
|
|
@@ -3868,7 +3994,7 @@ var W = (e, t, n) => {
|
|
|
3868
3994
|
};
|
|
3869
3995
|
//#endregion
|
|
3870
3996
|
//#region llm/importConcepts.ts
|
|
3871
|
-
async function
|
|
3997
|
+
async function Xn(e, t, n) {
|
|
3872
3998
|
let { connectionStore: r, editorStore: i, queryExecutionService: a } = e;
|
|
3873
3999
|
try {
|
|
3874
4000
|
if (!i?.editors[t.id]?.contents) return "No fields found in this data source.";
|
|
@@ -3911,7 +4037,7 @@ function Q(e) {
|
|
|
3911
4037
|
function $(e, t) {
|
|
3912
4038
|
return e.connections[t] || e.connectionByName(t) || null;
|
|
3913
4039
|
}
|
|
3914
|
-
async function
|
|
4040
|
+
async function Zn(e, t, n) {
|
|
3915
4041
|
if (!t || typeof t != "string") return {
|
|
3916
4042
|
success: !1,
|
|
3917
4043
|
error: `Connection name is required. Available connections: ${Q(e) || "None"}`
|
|
@@ -3938,7 +4064,7 @@ async function Xn(e, t, n) {
|
|
|
3938
4064
|
};
|
|
3939
4065
|
}
|
|
3940
4066
|
}
|
|
3941
|
-
function
|
|
4067
|
+
function Qn(e, t, n, r) {
|
|
3942
4068
|
let i = /* @__PURE__ */ new Map(), a = $(e, n), o = a?.id || n;
|
|
3943
4069
|
return e.getConnectionSources(o).forEach((e) => i.set(e.alias, e.contents)), t && a && Object.values(t.editors).filter((e) => !e.deleted && e.connectionId === a.id).forEach((e) => {
|
|
3944
4070
|
i.set(e.name, e.contents);
|
|
@@ -3950,7 +4076,7 @@ function Zn(e, t, n, r) {
|
|
|
3950
4076
|
contents: t
|
|
3951
4077
|
}));
|
|
3952
4078
|
}
|
|
3953
|
-
function
|
|
4079
|
+
function $n(e, t, n, r) {
|
|
3954
4080
|
let i = typeof t?.toJSON == "function" ? t.toJSON() : t, a = i?.data;
|
|
3955
4081
|
if (!Array.isArray(a)) return {
|
|
3956
4082
|
success: !1,
|
|
@@ -3974,7 +4100,7 @@ function Qn(e, t, n, r) {
|
|
|
3974
4100
|
}, null, 2)
|
|
3975
4101
|
};
|
|
3976
4102
|
}
|
|
3977
|
-
function
|
|
4103
|
+
function er(e, t, n) {
|
|
3978
4104
|
if (!e) return [];
|
|
3979
4105
|
let r = n ? $(n, t) : null;
|
|
3980
4106
|
return Object.values(e.editors).filter((e) => e.deleted ? !1 : r ? e.connectionId === r.id : e.connection === t).sort((e, t) => e.name.localeCompare(t.name)).map((e) => ({
|
|
@@ -3983,7 +4109,7 @@ function $n(e, t, n) {
|
|
|
3983
4109
|
alias: ""
|
|
3984
4110
|
}));
|
|
3985
4111
|
}
|
|
3986
|
-
async function
|
|
4112
|
+
async function tr(e, t, n, r, i) {
|
|
3987
4113
|
let a = t.getConnectionName(), o = i && a ? $(i, a) : null;
|
|
3988
4114
|
if (!e || e.trim() === "") return t.setActiveImports([]), {
|
|
3989
4115
|
success: !0,
|
|
@@ -3994,27 +4120,27 @@ async function er(e, t, n, r, i) {
|
|
|
3994
4120
|
success: !1,
|
|
3995
4121
|
error: "No data connection selected. Please select a data connection first."
|
|
3996
4122
|
};
|
|
3997
|
-
let s =
|
|
4123
|
+
let s = er(r, o?.id || a, i), c = s.find((t) => t.name === e || t.name.endsWith(`.${e}`));
|
|
3998
4124
|
if (!c) return {
|
|
3999
4125
|
success: !1,
|
|
4000
4126
|
error: `Import "${e}" not found. Available imports: ${s.map((e) => e.name).join(", ") || "none"}`
|
|
4001
4127
|
};
|
|
4002
4128
|
let l = t.getActiveImports(), u = l.length === 1 && l[0].id === c.id;
|
|
4003
4129
|
u || t.setActiveImports([c]);
|
|
4004
|
-
let d = await
|
|
4130
|
+
let d = await Xn(n, c, o?.id || a);
|
|
4005
4131
|
return {
|
|
4006
4132
|
success: !0,
|
|
4007
4133
|
message: `${u ? `"${c.name}" is already the active data source.` : `Successfully selected "${c.name}" as the active data source.`}\n\n${d}`,
|
|
4008
4134
|
triggersSymbolRefresh: !u
|
|
4009
4135
|
};
|
|
4010
4136
|
}
|
|
4011
|
-
function
|
|
4137
|
+
function nr(e, t, n) {
|
|
4012
4138
|
let r = e.getConnectionName(), i = n && r ? $(n, r) : null, a = i?.name || r;
|
|
4013
4139
|
if (!r) return {
|
|
4014
4140
|
success: !1,
|
|
4015
4141
|
error: "No data connection selected. Please select a data connection first."
|
|
4016
4142
|
};
|
|
4017
|
-
let o =
|
|
4143
|
+
let o = er(t, i?.id || r, n), s = e.getActiveImports(), c = s.length > 0 ? s[0] : null;
|
|
4018
4144
|
return o.length === 0 ? {
|
|
4019
4145
|
success: !0,
|
|
4020
4146
|
message: `No data sources available on connection "${a}".`
|
|
@@ -4023,7 +4149,7 @@ function tr(e, t, n) {
|
|
|
4023
4149
|
message: `${c ? `Current active data source: ${c.name}\n\n` : "No data source currently selected.\n\n"}Available data sources for connection "${a}":\n${o.map((e) => `- ${e.name}${c?.id === e.id ? " (active)" : ""}`).join("\n")}\n\nUse select_active_import to select a data source.`
|
|
4024
4150
|
};
|
|
4025
4151
|
}
|
|
4026
|
-
async function
|
|
4152
|
+
async function rr(e, t, n, r, i, a) {
|
|
4027
4153
|
if (!i || typeof i != "string" || i.trim() === "") return {
|
|
4028
4154
|
success: !1,
|
|
4029
4155
|
error: "Query is required and must be a non-empty string",
|
|
@@ -4045,7 +4171,7 @@ async function nr(e, t, n, r, i, a) {
|
|
|
4045
4171
|
error: `Connection "${o.name}" is not connected. Use connect_data_connection first.`,
|
|
4046
4172
|
query: i
|
|
4047
4173
|
};
|
|
4048
|
-
let s =
|
|
4174
|
+
let s = Qn(t, n, o.id, a), c = (a || []).map((e) => ({
|
|
4049
4175
|
name: e.name,
|
|
4050
4176
|
alias: e.alias || ""
|
|
4051
4177
|
})), l = {
|
|
@@ -4082,13 +4208,13 @@ async function nr(e, t, n, r, i, a) {
|
|
|
4082
4208
|
}
|
|
4083
4209
|
//#endregion
|
|
4084
4210
|
//#region llm/editorRefinementToolExecutor.ts
|
|
4085
|
-
function
|
|
4211
|
+
function ir() {
|
|
4086
4212
|
return {
|
|
4087
4213
|
artifactRegistry: /* @__PURE__ */ new Map(),
|
|
4088
4214
|
lastQueryResults: { value: null }
|
|
4089
4215
|
};
|
|
4090
4216
|
}
|
|
4091
|
-
var
|
|
4217
|
+
var ar = class {
|
|
4092
4218
|
queryExecutionService;
|
|
4093
4219
|
connectionStore;
|
|
4094
4220
|
editorStore;
|
|
@@ -4097,7 +4223,7 @@ var ir = class {
|
|
|
4097
4223
|
lastQueryResults;
|
|
4098
4224
|
constructor(e, t, n, r, i) {
|
|
4099
4225
|
this.queryExecutionService = e, this.connectionStore = t, this.editorContext = n, this.editorStore = r || null;
|
|
4100
|
-
let a = i ??
|
|
4226
|
+
let a = i ?? ir();
|
|
4101
4227
|
this.artifactRegistry = a.artifactRegistry, this.lastQueryResults = a.lastQueryResults;
|
|
4102
4228
|
}
|
|
4103
4229
|
async executeToolCall(e, t) {
|
|
@@ -4250,7 +4376,7 @@ var ir = class {
|
|
|
4250
4376
|
generatedSql: i.generatedSql
|
|
4251
4377
|
};
|
|
4252
4378
|
let a = {
|
|
4253
|
-
id:
|
|
4379
|
+
id: V(),
|
|
4254
4380
|
type: "results",
|
|
4255
4381
|
data: i.results,
|
|
4256
4382
|
config: {
|
|
@@ -4316,7 +4442,7 @@ var ir = class {
|
|
|
4316
4442
|
};
|
|
4317
4443
|
let t = this.editorContext.getCurrentResults?.() ?? this.lastQueryResults.value ?? null;
|
|
4318
4444
|
if (t && t.headers) {
|
|
4319
|
-
let n =
|
|
4445
|
+
let n = Bn(t.data, t.headers, e);
|
|
4320
4446
|
if (!n.valid) {
|
|
4321
4447
|
let e = n.suggestedConfig;
|
|
4322
4448
|
try {
|
|
@@ -4324,7 +4450,7 @@ var ir = class {
|
|
|
4324
4450
|
} catch {}
|
|
4325
4451
|
return {
|
|
4326
4452
|
success: !1,
|
|
4327
|
-
error: "Auto-corrected to default configuration.\n" +
|
|
4453
|
+
error: "Auto-corrected to default configuration.\n" + Vn(n)
|
|
4328
4454
|
};
|
|
4329
4455
|
}
|
|
4330
4456
|
}
|
|
@@ -4393,7 +4519,7 @@ var ir = class {
|
|
|
4393
4519
|
generatedSql: t.generatedSql
|
|
4394
4520
|
};
|
|
4395
4521
|
let n = {
|
|
4396
|
-
id:
|
|
4522
|
+
id: V(),
|
|
4397
4523
|
type: "results",
|
|
4398
4524
|
data: t.results,
|
|
4399
4525
|
config: {
|
|
@@ -4423,7 +4549,7 @@ var ir = class {
|
|
|
4423
4549
|
}
|
|
4424
4550
|
getArtifactRows(e, t, n) {
|
|
4425
4551
|
let r = this.artifactRegistry.get(e);
|
|
4426
|
-
return r ?
|
|
4552
|
+
return r ? $n(e, r.data, t, n) : {
|
|
4427
4553
|
success: !1,
|
|
4428
4554
|
error: `Artifact "${e}" not found. Only artifacts created by run_query or run_active_editor_query in this session are available.`
|
|
4429
4555
|
};
|
|
@@ -4450,7 +4576,7 @@ var ir = class {
|
|
|
4450
4576
|
}
|
|
4451
4577
|
}
|
|
4452
4578
|
async connectDataConnection(e) {
|
|
4453
|
-
return
|
|
4579
|
+
return Zn(this.connectionStore, e);
|
|
4454
4580
|
}
|
|
4455
4581
|
serializeDatabaseTree(e, t, n) {
|
|
4456
4582
|
let r = this.connectionStore.connectionByName(this.editorContext.connectionName), i = r?.databases || [], a = (e ? i.filter((t) => t.name === e) : i).map((e) => {
|
|
@@ -4491,8 +4617,8 @@ var ir = class {
|
|
|
4491
4617
|
};
|
|
4492
4618
|
}
|
|
4493
4619
|
buildEditorExtraContent() {
|
|
4494
|
-
return
|
|
4620
|
+
return Qn(this.connectionStore, this.editorStore, this.editorContext.connectionName);
|
|
4495
4621
|
}
|
|
4496
4622
|
};
|
|
4497
4623
|
//#endregion
|
|
4498
|
-
export {
|
|
4624
|
+
export { Vt as $, ie as $t, bn as A, qe as At, dn as B, Re as Bt, X as C, Xe as Ct, Wn as D, $e as Dt, Mn as E, Qe as Et, Sn as F, D as Ft, an as G, ae as Gt, sn as H, O as Ht, xn as I, Be as It, $t as J, ne as Jt, nn as K, te as Kt, hn as L, Ve as Lt, vn as M, Ue as Mt, _n as N, We as Nt, Z as O, k as Ot, gn as P, Le as Pt, Gt as Q, le as Qt, mn as R, ke as Rt, Hn as S, Ze as St, Pn as T, et as Tt, fn as U, He as Ut, pn as V, ze as Vt, Kt as W, De as Wt, z as X, re as Xt, B as Y, se as Yt, Ut as Z, b as Zt, Ln as _, mt as _t, rr as a, m as an, St as at, W as b, at as bt, nr as c, d as cn, xt as ct, kn as d, P as dt, Ce as en, Ht as et, Jn as f, N as ft, In as g, ht as gt, Yn as h, gt as ht, Zn as i, y as in, jt as it, yn as j, Ge as jt, Bn as k, Je as kt, tr as l, u as ln, Ct as lt, Gn as m, j as mt, ir as n, ye as nn, Lt as nt, $n as o, f as on, I as ot, Un as p, F as pt, on as q, oe as qt, Qn as r, Se as rn, R as rt, er as s, c as sn, Ot as st, ar as t, xe as tn, Rt as tt, Cn as u, wt as ut, J as v, ut as vt, En as w, Ye as wt, Y as x, A as xt, Vn as y, ot as yt, V as z, Fe as zt };
|