Add comprehensive analytics system with: - Analytics data layer (aggregator, metrics, trends, cache) - 6 API endpoints (overview, plants, transport, farms, sustainability, export) - 6 chart components (LineChart, BarChart, PieChart, AreaChart, Gauge, Heatmap) - 5 dashboard widgets (KPICard, TrendIndicator, DataTable, DateRangePicker, FilterPanel) - 5 dashboard pages (overview, plants, transport, farms, sustainability) - Export functionality (CSV, JSON) Dependencies added: recharts, d3, date-fns Also includes minor fixes: - Fix EnvironmentalForm spread type error - Fix AgentOrchestrator Map iteration issues - Fix next.config.js image domains undefined error - Add downlevelIteration to tsconfig
189 lines
3.8 KiB
TypeScript
189 lines
3.8 KiB
TypeScript
/**
|
|
* Cache Management for Analytics
|
|
* Provides caching for expensive analytics calculations
|
|
*/
|
|
|
|
interface CacheEntry<T> {
|
|
data: T;
|
|
timestamp: number;
|
|
expiresAt: number;
|
|
}
|
|
|
|
class AnalyticsCache {
|
|
private cache: Map<string, CacheEntry<any>> = new Map();
|
|
private defaultTTL: number = 5 * 60 * 1000; // 5 minutes
|
|
|
|
/**
|
|
* Get cached data
|
|
*/
|
|
get<T>(key: string): T | null {
|
|
const entry = this.cache.get(key);
|
|
if (!entry) return null;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.cache.delete(key);
|
|
return null;
|
|
}
|
|
|
|
return entry.data as T;
|
|
}
|
|
|
|
/**
|
|
* Set cached data
|
|
*/
|
|
set<T>(key: string, data: T, ttlMs: number = this.defaultTTL): void {
|
|
const now = Date.now();
|
|
this.cache.set(key, {
|
|
data,
|
|
timestamp: now,
|
|
expiresAt: now + ttlMs,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if key exists and is valid
|
|
*/
|
|
has(key: string): boolean {
|
|
const entry = this.cache.get(key);
|
|
if (!entry) return false;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.cache.delete(key);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete a specific key
|
|
*/
|
|
delete(key: string): boolean {
|
|
return this.cache.delete(key);
|
|
}
|
|
|
|
/**
|
|
* Clear all cached data
|
|
*/
|
|
clear(): void {
|
|
this.cache.clear();
|
|
}
|
|
|
|
/**
|
|
* Clear expired entries
|
|
*/
|
|
cleanup(): number {
|
|
const now = Date.now();
|
|
let cleaned = 0;
|
|
|
|
for (const [key, entry] of this.cache.entries()) {
|
|
if (now > entry.expiresAt) {
|
|
this.cache.delete(key);
|
|
cleaned++;
|
|
}
|
|
}
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
/**
|
|
* Get cache statistics
|
|
*/
|
|
getStats(): {
|
|
size: number;
|
|
validEntries: number;
|
|
expiredEntries: number;
|
|
} {
|
|
const now = Date.now();
|
|
let valid = 0;
|
|
let expired = 0;
|
|
|
|
for (const entry of this.cache.values()) {
|
|
if (now > entry.expiresAt) {
|
|
expired++;
|
|
} else {
|
|
valid++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
size: this.cache.size,
|
|
validEntries: valid,
|
|
expiredEntries: expired,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate cache key from object
|
|
*/
|
|
static generateKey(prefix: string, params: Record<string, any>): string {
|
|
const sortedParams = Object.keys(params)
|
|
.sort()
|
|
.map((key) => `${key}:${JSON.stringify(params[key])}`)
|
|
.join('|');
|
|
return `${prefix}:${sortedParams}`;
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const analyticsCache = new AnalyticsCache();
|
|
|
|
/**
|
|
* Cache decorator for async functions
|
|
*/
|
|
export function cached<T>(
|
|
keyGenerator: (...args: any[]) => string,
|
|
ttlMs: number = 5 * 60 * 1000
|
|
) {
|
|
return function (
|
|
target: any,
|
|
propertyKey: string,
|
|
descriptor: PropertyDescriptor
|
|
) {
|
|
const originalMethod = descriptor.value;
|
|
|
|
descriptor.value = async function (...args: any[]) {
|
|
const cacheKey = keyGenerator(...args);
|
|
const cached = analyticsCache.get<T>(cacheKey);
|
|
|
|
if (cached !== null) {
|
|
return cached;
|
|
}
|
|
|
|
const result = await originalMethod.apply(this, args);
|
|
analyticsCache.set(cacheKey, result, ttlMs);
|
|
return result;
|
|
};
|
|
|
|
return descriptor;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Higher-order function for caching async functions
|
|
*/
|
|
export function withCache<T, A extends any[]>(
|
|
fn: (...args: A) => Promise<T>,
|
|
keyGenerator: (...args: A) => string,
|
|
ttlMs: number = 5 * 60 * 1000
|
|
): (...args: A) => Promise<T> {
|
|
return async (...args: A): Promise<T> => {
|
|
const cacheKey = keyGenerator(...args);
|
|
const cached = analyticsCache.get<T>(cacheKey);
|
|
|
|
if (cached !== null) {
|
|
return cached;
|
|
}
|
|
|
|
const result = await fn(...args);
|
|
analyticsCache.set(cacheKey, result, ttlMs);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
// Schedule periodic cleanup
|
|
if (typeof setInterval !== 'undefined') {
|
|
setInterval(() => {
|
|
analyticsCache.cleanup();
|
|
}, 60 * 1000); // Run cleanup every minute
|
|
}
|