diff --git a/scripts/__tests__/calculations.test.ts b/scripts/__tests__/calculations.test.ts new file mode 100644 index 0000000..096dac0 --- /dev/null +++ b/scripts/__tests__/calculations.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from 'vitest'; +import { calculateLakeMetrics, LakeCalculationConfig } from '../utils/calculations'; + +describe('calculateLakeMetrics', () => { + const config: LakeCalculationConfig = { + minLevel: 100, + maxLevel: 110, + storageLevel: 108, + maxVolume: 50, + }; + + it('should calculate capacity based on reported volume when available', () => { + // 25 / 50 = 50% + const result = calculateLakeMetrics(105, 25, config); + expect(result.capacity).toBe(50); + expect(result.volume).toBe(25); + }); + + it('should cap capacity at 100% when volume exceeds maxVolume', () => { + const result = calculateLakeMetrics(111, 55, config); + expect(result.capacity).toBe(100); + expect(result.volume).toBe(55); + }); + + it('should floor capacity at 0% when volume is negative', () => { + const result = calculateLakeMetrics(99, -5, config); + expect(result.capacity).toBe(0); + expect(result.volume).toBe(-5); + }); + + it('should estimate capacity and volume from level when reported volume is 0', () => { + // Level 105 is exactly halfway between 100 and 110 -> 50% + // 50% of 50 maxVolume = 25 + const result = calculateLakeMetrics(105, 0, config); + expect(result.capacity).toBe(50); + expect(result.volume).toBe(25); + }); + + it('should cap estimated capacity at 100% when level exceeds maxLevel', () => { + const result = calculateLakeMetrics(115, 0, config); + expect(result.capacity).toBe(100); + expect(result.volume).toBe(50); // 100% of 50 + }); + + it('should floor estimated capacity at 0% when level is below minLevel', () => { + const result = calculateLakeMetrics(90, 0, config); + expect(result.capacity).toBe(0); + expect(result.volume).toBe(0); // 0% of 50 + }); + + it('should correctly calculate storageDiff', () => { + const result = calculateLakeMetrics(106, 25, config); + // 106 - 108 = -2.00 + expect(result.storageDiff).toBe(-2); + }); + + it('should calculate positive storageDiff when above storageLevel', () => { + const result = calculateLakeMetrics(109, 25, config); + // 109 - 108 = 1.00 + expect(result.storageDiff).toBe(1); + }); + + it('should handle missing config gracefully', () => { + const emptyConfig: LakeCalculationConfig = {}; + const result = calculateLakeMetrics(105, 0, emptyConfig); + expect(result.capacity).toBe(0); + expect(result.volume).toBe(0); + expect(result.storageDiff).toBe(0); + }); +}); diff --git a/scripts/buildIndex.ts b/scripts/buildIndex.ts index 1445ad4..43af9df 100644 --- a/scripts/buildIndex.ts +++ b/scripts/buildIndex.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { lakesConfig } from './lakesConfig'; +import { calculateLakeMetrics } from './utils/calculations'; interface DataRecord { timestamp: string; @@ -53,22 +54,7 @@ const lakes = lakesConfig.map(lake => { } } - if (volume > 0 && lake.maxVolume && lake.maxVolume > 0) { - capacity = Math.max(0, Math.min(100, Math.round((volume / lake.maxVolume) * 1000) / 10)); - } else if (lake.minLevel && lake.maxLevel && currentLevel > 0) { - const percentage = ((currentLevel - lake.minLevel) / (lake.maxLevel - lake.minLevel)) * 100; - capacity = Math.max(0, Math.min(100, Math.round(percentage * 10) / 10)); // Round to 1 decimal place - if (volume === 0) { - volume = Number(((capacity / 100) * (lake.maxVolume || 0)).toFixed(1)); - } - } else { - if (volume === 0) volume = lake.maxVolume || 0; - } - - let storageDiff = 0; - if (lake.storageLevel && currentLevel > 0) { - storageDiff = Number((currentLevel - lake.storageLevel).toFixed(2)); - } + const metrics = calculateLakeMetrics(currentLevel, volume, lake); return { id: lake.id, @@ -76,11 +62,11 @@ const lakes = lakesConfig.map(lake => { river: lake.text.includes('-') ? lake.text.split('-')[1].trim() : '', priority: lake.priority || false, level: currentLevel.toFixed(2), - capacity: capacity, - storageDiff: storageDiff, + capacity: metrics.capacity, + storageDiff: metrics.storageDiff, inflow: inflow.toFixed(1), outflow: currentFlow.toFixed(1), - volume: volume, + volume: metrics.volume, maxVolume: lake.maxVolume || 0, lat: lake.coords[0], lng: lake.coords[1], diff --git a/scripts/utils/calculations.ts b/scripts/utils/calculations.ts new file mode 100644 index 0000000..dd1b0bd --- /dev/null +++ b/scripts/utils/calculations.ts @@ -0,0 +1,52 @@ +export interface LakeCalculationConfig { + maxVolume?: number; + minLevel?: number; + maxLevel?: number; + storageLevel?: number; +} + +export interface LakeMetrics { + capacity: number; // 0-100 percentage + volume: number; // in mil. m3 + storageDiff: number; // in meters +} + +export function calculateLakeMetrics( + currentLevel: number, + reportedVolume: number, + config: LakeCalculationConfig +): LakeMetrics { + let capacity = 0; + let volume = reportedVolume; + let storageDiff = 0; + + // 1. Calculate capacity and volume + if (volume > 0 && config.maxVolume && config.maxVolume > 0) { + // If real volume is available, calculate capacity from volume + capacity = Math.max(0, Math.min(100, Math.round((volume / config.maxVolume) * 1000) / 10)); + } else if (config.minLevel && config.maxLevel && currentLevel > 0) { + // Fallback: estimate capacity and volume from level difference + const percentage = ((currentLevel - config.minLevel) / (config.maxLevel - config.minLevel)) * 100; + capacity = Math.max(0, Math.min(100, Math.round(percentage * 10) / 10)); // Round to 1 decimal place + + if (volume === 0) { + volume = Number(((capacity / 100) * (config.maxVolume || 0)).toFixed(1)); + } + } else { + // Missing required config data or bad level + if (volume === 0) { + volume = config.maxVolume || 0; + } + } + + // 2. Calculate storage difference + if (config.storageLevel && currentLevel > 0) { + storageDiff = Number((currentLevel - config.storageLevel).toFixed(2)); + } + + return { + capacity, + volume, + storageDiff + }; +} diff --git a/src/components/__tests__/KpiCards.test.tsx b/src/components/__tests__/KpiCards.test.tsx index 315a403..e7adbea 100644 --- a/src/components/__tests__/KpiCards.test.tsx +++ b/src/components/__tests__/KpiCards.test.tsx @@ -36,6 +36,8 @@ describe('KpiCards Component', () => { const noDiffData = { ...mockData, storageDiff: 0, fullness: 85.5 }; render(); - expect(screen.getByText('85.5%')).toBeInTheDocument(); + const elements = screen.getAllByText('85.5%'); + expect(elements.length).toBeGreaterThan(0); + expect(elements[0]).toBeInTheDocument(); }); });