Skip to main content
Glama

compare_kpis

Calculate Pearson correlation between two Swedish municipal KPIs to identify statistical relationships. Analyze how indicators like teacher density and school results correlate for data-driven insights.

Instructions

Beräkna Pearson-korrelation mellan två KPIs för att se om det finns samband. T.ex. korrelation mellan lärartäthet och skolresultat. Värden nära 1 = starkt positivt samband, nära -1 = starkt negativt, nära 0 = inget samband.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
kpi_id_1YesFörsta KPI-ID för korrelationsanalys
kpi_id_2YesAndra KPI-ID för korrelationsanalys
yearYesÅr att analysera
genderNoKön: T=Totalt, M=Män, K=KvinnorT
municipality_typeNoKommuntyp: K=Kommun, L=Region, all=allaK

Implementation Reference

  • The handler function for the 'compare_kpis' tool. It fetches KPI data for two specified KPIs across municipalities for a given year and gender, computes the Pearson correlation coefficient between them using common municipalities with data, provides an interpretation of the correlation strength and direction, and returns structured results or errors if insufficient data.
    handler: async (args: z.infer<typeof compareKpisSchema>): Promise<ToolResult> => { const startTime = Date.now(); const { kpi_id_1, kpi_id_2, year, gender, municipality_type } = args; logger.toolCall('compare_kpis', { kpi_id_1, kpi_id_2, year, gender, municipality_type }); try { // Fetch all municipalities const municipalities = await dataCache.getOrFetch( 'municipalities-full', () => koladaClient.fetchAllData<Municipality>('/municipality'), 86400000 ); // Filter by type const filteredMunicipalities = municipality_type === 'all' ? municipalities : municipalities.filter((m) => m.type === municipality_type); // Fetch data for both KPIs in batches const BATCH_SIZE = 25; const municipalityIdChunks = chunkArray(filteredMunicipalities.map((m) => m.id), BATCH_SIZE); // Helper to fetch all batches for a KPI const fetchKpiData = async (kpiId: string): Promise<KPIData[]> => { const allData: KPIData[] = []; for (const chunk of municipalityIdChunks) { const endpoint = `/data/kpi/${kpiId}/municipality/${chunk.join(',')}/year/${year}`; const batchData = await koladaClient.fetchAllData<KPIData>(endpoint); allData.push(...batchData); } return allData; }; const [data1, data2] = await Promise.all([ fetchKpiData(kpi_id_1), fetchKpiData(kpi_id_2), ]); // Create maps for matching const values1: Record<string, number> = {}; const values2: Record<string, number> = {}; for (const d of data1) { if (d.municipality) { const v = extractValue(d, gender); if (v !== null) values1[d.municipality] = v; } } for (const d of data2) { if (d.municipality) { const v = extractValue(d, gender); if (v !== null) values2[d.municipality] = v; } } // Find common municipalities const commonIds = Object.keys(values1).filter((id) => id in values2); if (commonIds.length < 3) { return { content: [ { type: 'text', text: JSON.stringify({ error: 'INSUFFICIENT_DATA', message: `För få kommuner med data för båda KPIs (${commonIds.length} hittades, minst 3 krävs)`, suggestion: 'Försök med ett annat år eller andra KPIs.', }), }, ], isError: true, }; } const x = commonIds.map((id) => values1[id]); const y = commonIds.map((id) => values2[id]); const correlation = pearsonCorrelation(x, y); // Interpret correlation let interpretation: string; const absCorr = Math.abs(correlation); if (absCorr >= 0.8) { interpretation = correlation > 0 ? 'Mycket starkt positivt samband' : 'Mycket starkt negativt samband'; } else if (absCorr >= 0.6) { interpretation = correlation > 0 ? 'Starkt positivt samband' : 'Starkt negativt samband'; } else if (absCorr >= 0.4) { interpretation = correlation > 0 ? 'Måttligt positivt samband' : 'Måttligt negativt samband'; } else if (absCorr >= 0.2) { interpretation = correlation > 0 ? 'Svagt positivt samband' : 'Svagt negativt samband'; } else { interpretation = 'Inget eller mycket svagt samband'; } logger.toolResult('compare_kpis', true, Date.now() - startTime); return { content: [ { type: 'text', text: JSON.stringify( { kpi_id_1, kpi_id_2, year, gender, municipality_type, correlation: { pearson_coefficient: parseFloat(correlation.toFixed(4)), interpretation, municipalities_compared: commonIds.length, }, note: 'Korrelation innebär inte kausalitet - två variabler kan vara korrelerade utan att den ena orsakar den andra.', source: 'Kolada - Källa: Kolada', }, null, 2 ), }, ], }; } catch (error) { logger.toolResult('compare_kpis', false, Date.now() - startTime); throw error; } },
  • Zod input schema for the 'compare_kpis' tool defining parameters: kpi_id_1, kpi_id_2 (required strings), year (number), gender (enum T/M/K, default T), municipality_type (enum K/L/all, default K).
    const compareKpisSchema = z.object({ kpi_id_1: z.string().describe('Första KPI-ID för korrelationsanalys'), kpi_id_2: z.string().describe('Andra KPI-ID för korrelationsanalys'), year: z.number().describe('År att analysera'), gender: z.enum(['T', 'M', 'K']).default('T').describe('Kön: T=Totalt, M=Män, K=Kvinnor'), municipality_type: z.enum(['K', 'L', 'all']).default('K').describe('Kommuntyp: K=Kommun, L=Region, all=alla'), });
  • Registration of the 'compare_kpis' tool in the analysisTools export object, including description, input schema reference, read-only annotations, and inline handler function.
    compare_kpis: { description: 'Beräkna Pearson-korrelation mellan två KPIs för att se om det finns samband. T.ex. korrelation mellan lärartäthet och skolresultat. Värden nära 1 = starkt positivt samband, nära -1 = starkt negativt, nära 0 = inget samband.', inputSchema: compareKpisSchema, annotations: READ_ONLY_ANNOTATIONS, handler: async (args: z.infer<typeof compareKpisSchema>): Promise<ToolResult> => { const startTime = Date.now(); const { kpi_id_1, kpi_id_2, year, gender, municipality_type } = args; logger.toolCall('compare_kpis', { kpi_id_1, kpi_id_2, year, gender, municipality_type }); try { // Fetch all municipalities const municipalities = await dataCache.getOrFetch( 'municipalities-full', () => koladaClient.fetchAllData<Municipality>('/municipality'), 86400000 ); // Filter by type const filteredMunicipalities = municipality_type === 'all' ? municipalities : municipalities.filter((m) => m.type === municipality_type); // Fetch data for both KPIs in batches const BATCH_SIZE = 25; const municipalityIdChunks = chunkArray(filteredMunicipalities.map((m) => m.id), BATCH_SIZE); // Helper to fetch all batches for a KPI const fetchKpiData = async (kpiId: string): Promise<KPIData[]> => { const allData: KPIData[] = []; for (const chunk of municipalityIdChunks) { const endpoint = `/data/kpi/${kpiId}/municipality/${chunk.join(',')}/year/${year}`; const batchData = await koladaClient.fetchAllData<KPIData>(endpoint); allData.push(...batchData); } return allData; }; const [data1, data2] = await Promise.all([ fetchKpiData(kpi_id_1), fetchKpiData(kpi_id_2), ]); // Create maps for matching const values1: Record<string, number> = {}; const values2: Record<string, number> = {}; for (const d of data1) { if (d.municipality) { const v = extractValue(d, gender); if (v !== null) values1[d.municipality] = v; } } for (const d of data2) { if (d.municipality) { const v = extractValue(d, gender); if (v !== null) values2[d.municipality] = v; } } // Find common municipalities const commonIds = Object.keys(values1).filter((id) => id in values2); if (commonIds.length < 3) { return { content: [ { type: 'text', text: JSON.stringify({ error: 'INSUFFICIENT_DATA', message: `För få kommuner med data för båda KPIs (${commonIds.length} hittades, minst 3 krävs)`, suggestion: 'Försök med ett annat år eller andra KPIs.', }), }, ], isError: true, }; } const x = commonIds.map((id) => values1[id]); const y = commonIds.map((id) => values2[id]); const correlation = pearsonCorrelation(x, y); // Interpret correlation let interpretation: string; const absCorr = Math.abs(correlation); if (absCorr >= 0.8) { interpretation = correlation > 0 ? 'Mycket starkt positivt samband' : 'Mycket starkt negativt samband'; } else if (absCorr >= 0.6) { interpretation = correlation > 0 ? 'Starkt positivt samband' : 'Starkt negativt samband'; } else if (absCorr >= 0.4) { interpretation = correlation > 0 ? 'Måttligt positivt samband' : 'Måttligt negativt samband'; } else if (absCorr >= 0.2) { interpretation = correlation > 0 ? 'Svagt positivt samband' : 'Svagt negativt samband'; } else { interpretation = 'Inget eller mycket svagt samband'; } logger.toolResult('compare_kpis', true, Date.now() - startTime); return { content: [ { type: 'text', text: JSON.stringify( { kpi_id_1, kpi_id_2, year, gender, municipality_type, correlation: { pearson_coefficient: parseFloat(correlation.toFixed(4)), interpretation, municipalities_compared: commonIds.length, }, note: 'Korrelation innebär inte kausalitet - två variabler kan vara korrelerade utan att den ena orsakar den andra.', source: 'Kolada - Källa: Kolada', }, null, 2 ), }, ], }; } catch (error) { logger.toolResult('compare_kpis', false, Date.now() - startTime); throw error; } }, },
  • Helper function 'pearsonCorrelation' that calculates the Pearson correlation coefficient between two arrays of numbers, directly used in the compare_kpis handler to compute correlation between KPI values.
    function pearsonCorrelation(x: number[], y: number[]): number { const n = x.length; if (n !== y.length || n < 2) return NaN; const sumX = x.reduce((a, b) => a + b, 0); const sumY = y.reduce((a, b) => a + b, 0); const sumXY = x.reduce((acc, xi, i) => acc + xi * y[i], 0); const sumX2 = x.reduce((acc, xi) => acc + xi * xi, 0); const sumY2 = y.reduce((acc, yi) => acc + yi * yi, 0); const numerator = n * sumXY - sumX * sumY; const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)); if (denominator === 0) return NaN; return numerator / denominator; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/isakskogstad/Kolada-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server