Skip to content

Commit 1a4d417

Browse files
committed
get numeric domain for display in chat
1 parent 766c135 commit 1a4d417

4 files changed

Lines changed: 144 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"dependencies": {
1717
"@quasar/extras": "^1.16.4",
1818
"@types/lodash-es": "^4.17.12",
19+
"arquero": "^8.0.3",
1920
"lodash-es": "^4.17.21",
2021
"ollama": "^0.5.10",
2122
"openai": "^5.5.1",

src/components/IntervalFilterComponent.vue

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import { on } from 'events';
33
import { range } from 'lodash-es';
44
import { ref, computed, watch, onMounted, reactive } from 'vue';
5+
import { useDataPackageStore } from 'src/stores/dataPackageStore';
6+
const dataPackageStore = useDataPackageStore();
57
68
interface IntervalFilterComponentProps {
79
entity: string;
@@ -30,18 +32,59 @@ const rangeModel = ref<{ min: number; max: number }>({
3032
min: props.minInitial,
3133
max: props.maxInitial,
3234
});
35+
36+
const rangeMinMax = computed<{ min: number; max: number }>(() => {
37+
const domain = dataPackageStore.getDomainForField(props.entity, props.field);
38+
if (domain && domain.type === 'interval') {
39+
return {
40+
min: domain.domain.min,
41+
max: domain.domain.max,
42+
};
43+
}
44+
return {
45+
min: 0,
46+
max: 100,
47+
};
48+
});
49+
50+
const minDisplayText = computed(() => {
51+
if (rangeModel.value.min <= rangeMinMax.value.min) {
52+
return 'min';
53+
}
54+
return `${rangeModel.value.min}`;
55+
});
56+
57+
const maxDisplayText = computed(() => {
58+
if (rangeModel.value.max >= rangeMinMax.value.max) {
59+
return 'max';
60+
}
61+
return `${rangeModel.value.max}`;
62+
});
3363
</script>
3464

3565
<template>
3666
<div class="q-mx-sm">
3767
Filtered <span class="emphasized q-mr-xs">{{ props.entity }}</span>
3868
<span class="emphasized">{{ props.field }}</span
39-
>, <span class="emphasized">{{ rangeModel.min }}</span> to
40-
<span class="emphasized">{{ rangeModel.max }}</span
69+
>, <span class="emphasized">{{ minDisplayText }}</span> to
70+
<span class="emphasized">{{ maxDisplayText }}</span
4171
>:
4272
</div>
43-
<div class="q-mx-sm">
44-
<q-range v-model="rangeModel" :min="0" :max="100" />
73+
<div
74+
v-if="
75+
dataPackageStore.isValidIntervalFilter(
76+
props.entity,
77+
props.field,
78+
rangeModel.min,
79+
rangeModel.max,
80+
).isValid === 'yes'
81+
"
82+
class="q-mx-sm"
83+
>
84+
<q-range v-model="rangeModel" :min="rangeMinMax.min" :max="rangeMinMax.max" />
85+
</div>
86+
<div v-else class="q-mx-sm">
87+
<span class="text-negative">Error: Invalid filter. </span>
4588
</div>
4689
</template>
4790

src/stores/dataPackageStore.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { ref, computed } from 'vue';
22
import { defineStore } from 'pinia';
3+
// import { parse, type ParseResult } from 'papaparse';
4+
import { loadCSV, agg, op, from, bin, rolling, escape, desc, type ColumnTable } from 'arquero';
5+
import { data } from 'autoprefixer';
36

47
export const useDataPackageStore = defineStore('dataPackageStore', () => {
58
const dataPackagePath = './data/hubmap_2025-05-05/datapackage_udi.json';
@@ -10,11 +13,102 @@ export const useDataPackageStore = defineStore('dataPackageStore', () => {
1013
.then((response) => response.json())
1114
.then((json) => {
1215
dataPackage.value = json;
16+
initializeDataFieldDomains();
1317
})
1418
.catch((error) => {
1519
console.error('Error loading data package:', error);
1620
});
1721

22+
interface DataFieldDomain {
23+
entity: string;
24+
field: string;
25+
domain: IntervalDomain | CategoricalDomain;
26+
}
27+
28+
interface IntervalDomain {
29+
type: 'interval';
30+
min: number;
31+
max: number;
32+
}
33+
34+
interface CategoricalDomain {
35+
values: string[];
36+
}
37+
38+
const dataFieldDomains = ref<DataFieldDomain[]>([]);
39+
40+
function initializeDataFieldDomains() {
41+
console.log('init thingy');
42+
if (!dataPackage.value || !dataPackage.value.resources) return;
43+
const folderPath = dataPackage.value['udi:path'];
44+
for (const resource of dataPackage.value.resources) {
45+
const entityName = resource.name;
46+
const dataPath = resource.path;
47+
const fullPath = `${folderPath}/${dataPath}`;
48+
addDataDomains(fullPath, entityName);
49+
}
50+
return;
51+
}
52+
53+
function getDomainForField(entity: string, field: string): DataFieldDomain | undefined {
54+
return dataFieldDomains.value.find(
55+
(domain) => domain.entity === entity && domain.field === field,
56+
);
57+
}
58+
59+
interface ValidStatus {
60+
isValid: 'yes' | 'no' | 'unknown';
61+
}
62+
63+
function isValidIntervalFilter(
64+
entity: string,
65+
field: string,
66+
min: number,
67+
max: number,
68+
): ValidStatus {
69+
if (!dataPackage.value || !dataPackage.value.resources) {
70+
return { isValid: 'unknown' };
71+
}
72+
const domain = getDomainForField(entity, field);
73+
if (!domain) {
74+
return { isValid: 'no' };
75+
}
76+
return { isValid: 'yes' };
77+
}
78+
79+
async function addDataDomains(path: string, entity: string): Promise<void> {
80+
const table = await loadCSV(path);
81+
82+
// Get column names
83+
const cols = table.columnNames();
84+
85+
// Build domain info
86+
const domains: DataFieldDomain[] = [];
87+
88+
for (const col of cols) {
89+
const series = table.array(col); // raw column values
90+
const isNumeric = series.every((v) => v == null || !isNaN(+v));
91+
92+
if (isNumeric) {
93+
const stats = table
94+
.rollup({
95+
min: `(d) => op.min(d["${col}"])`,
96+
max: `(d) => op.max(d["${col}"])`,
97+
})
98+
.objects()[0];
99+
domains.push({
100+
entity,
101+
field: col,
102+
type: 'interval',
103+
domain: { min: stats.min, max: stats.max },
104+
});
105+
}
106+
// TODO: handle categorical data
107+
}
108+
dataFieldDomains.value.push(...domains);
109+
return;
110+
}
111+
18112
function removeVestigialInfo(data: object): object {
19113
// remove udi:overlapping_fields
20114
// for each resource.schema
@@ -65,5 +159,5 @@ export const useDataPackageStore = defineStore('dataPackageStore', () => {
65159
return fieldsMap;
66160
});
67161

68-
return { dataPackage, dataPackageString, sourceFields };
162+
return { dataPackage, dataPackageString, sourceFields, isValidIntervalFilter, getDomainForField };
69163
});

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ argparse@^2.0.1:
12451245
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
12461246
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
12471247

1248-
arquero@^8.0.1:
1248+
arquero@^8.0.1, arquero@^8.0.3:
12491249
version "8.0.3"
12501250
resolved "https://registry.yarnpkg.com/arquero/-/arquero-8.0.3.tgz#ac72842df9143cbb3b9d215792344d3c6b14cef6"
12511251
integrity sha512-7YQwe/GPVBUiahaPwEwgvu6VHyuhX0Ut61JZlIJYsAobOH5unLBwTmh43BobhX/N4dW1sb8WyKlQ8GjFq2whzQ==

0 commit comments

Comments
 (0)