localgreenchain/lib/analytics/cache.ts
Claude 816c3b3f2e
Implement Agent 7: Advanced Analytics Dashboard
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
2025-11-23 04:02:07 +00:00

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
}