import { type TimeSeriesChartDataPoint } from "@/src/features/dashboard/components/BaseTimeSeriesChart"; import { type FilterState, getGenerationLikeTypes } from "@langfuse/shared"; import { type DatabaseRow } from "@/src/server/api/services/sqlInterface"; import { api } from "@/src/utils/api"; import { mapLegacyUiTableFilterToView } from "@/src/features/query"; export const getAllModels = ( projectId: string, globalFilterState: FilterState, fromTimestamp: Date, toTimestamp: Date, ) => { const allModels = api.dashboard.executeQuery.useQuery( { projectId, query: { view: "observations", dimensions: [{ field: "providedModelName" }], metrics: [], filters: [ ...mapLegacyUiTableFilterToView("observations", globalFilterState), { column: "type", operator: "any of", value: getGenerationLikeTypes(), type: "stringOptions", }, ], timeDimension: null, fromTimestamp: fromTimestamp.toISOString(), toTimestamp: toTimestamp.toISOString(), orderBy: null, }, }, { trpc: { context: { skipBatch: true, }, }, }, ); return allModels.data ? extractAllModels(allModels.data) : []; }; const extractAllModels = ( data: Record[], ): { model: string; count: number }[] => { return data .filter((item) => item.providedModelName !== null) .map((item) => ({ model: item.providedModelName as string, count: item.count as number, })); }; type ChartData = { label: string; value?: number; }; type FieldMappingItem = { uniqueIdentifierColumns: { accessor: string; formatFct?: (value: string) => string; }[]; valueColumn: string; }; function generateChartLabelFromColumns( uniqueIdentifierColumns: FieldMappingItem["uniqueIdentifierColumns"], row: DatabaseRow, ): string { return uniqueIdentifierColumns .map(({ accessor, formatFct }) => { if (row[accessor] === null || row[accessor] === undefined) return null; return formatFct ? formatFct(row[accessor] as string) : (row[accessor] as string); }) .filter((value) => value !== null) .join(" "); } // we get data for time series in the following format: // ts: 123, label1: 1, label2: 2 // ts: 456, label1: 5, label2: 9 // This needs to be mapped to the following format: // [{ts: 123, values: [{label1: 1, label2: 2}]}, {ts: 456, values: [{label1: 5, label2: 9}]] export function extractTimeSeriesData( data: DatabaseRow[], timeColumn: string, mapping: FieldMappingItem[], ): Map { return data.reduce((acc: Map, curr: DatabaseRow) => { const date = new Date(curr[timeColumn] as Date).getTime(); const reducedData: ChartData[] = []; // Map the desired fields from the DatabaseRow to the ChartData based on the mapping provided mapping.forEach((mapItem) => { const chartLabel = generateChartLabelFromColumns( mapItem.uniqueIdentifierColumns, curr, ); const columnValue = curr[mapItem.valueColumn]; if ( chartLabel && columnValue !== undefined && typeof chartLabel === "string" ) { reducedData.push({ label: chartLabel, value: columnValue ? (columnValue as number) : 0, }); } }); const existingData = acc.get(date); if (existingData) { existingData.push(...reducedData); } else { acc.set(date, reducedData); } return acc; }, new Map()); } export function fillMissingValuesAndTransform( inputMap: Map, labelsToAdd: string[] = [], ): TimeSeriesChartDataPoint[] { const result: TimeSeriesChartDataPoint[] = []; inputMap.forEach((chartDataArray, timestamp) => { const existingLabels = chartDataArray.map((value) => value.label); // For each label in labelsToAdd, add a default value of 0 labelsToAdd.forEach((label) => { if (!existingLabels.includes(label)) { chartDataArray.push({ label: label, value: 0 }); } }); result.push({ ts: timestamp, values: chartDataArray, }); }); return result; } export const isEmptyTimeSeries = ({ data, isNullValueAllowed = false, }: { data: TimeSeriesChartDataPoint[]; isNullValueAllowed?: boolean; }) => { return ( data.length === 0 || data.every( (item) => item.values.length === 0 || (isNullValueAllowed ? false : item.values.every((value) => value.value === 0)), ) ); };