import { promises as fs } from 'fs'; import { load } from 'js-yaml'; import path from 'path'; import { z } from 'zod'; const ToolSchema = z.object({ name: z.string(), icon: z.string().optional().nullable(), type: z.enum(['software', 'method', 'concept']), description: z.string(), domains: z.array(z.string()).optional().nullable().default([]), phases: z.array(z.string()).optional().nullable().default([]), platforms: z.array(z.string()).default([]), skillLevel: z.string(), url: z.string(), license: z.string().optional().nullable(), tags: z.array(z.string()).default([]), projectUrl: z.string().optional().nullable(), knowledgebase: z.boolean().optional().nullable(), statusUrl: z.string().optional().nullable(), accessType: z.string().optional().nullable(), 'domain-agnostic-software': z.array(z.string()).optional().nullable(), related_concepts: z.array(z.string()).optional().nullable().default([]), related_software: z.array(z.string()).optional().nullable().default([]), // Added this line }); const ToolsDataSchema = z.object({ tools: z.array(ToolSchema), domains: z.array(z.object({ id: z.string(), name: z.string() })), phases: z.array(z.object({ id: z.string(), name: z.string(), description: z.string().optional() })), 'domain-agnostic-software': z.array(z.object({ id: z.string(), name: z.string(), description: z.string().optional() })).optional().default([]), scenarios: z.array(z.object({ id: z.string(), icon: z.string(), friendly_name: z.string() })).optional().default([]), }); interface ToolsData { tools: any[]; domains: any[]; phases: any[]; 'domain-agnostic-software': any[]; scenarios: any[]; } interface CompressedToolsData { tools: any[]; concepts: any[]; domains: any[]; phases: any[]; 'domain-agnostic-software': any[]; } let cachedData: ToolsData | null = null; let cachedRandomizedData: ToolsData | null = null; let cachedCompressedData: CompressedToolsData | null = null; let lastRandomizationDate: string | null = null; function seededRandom(seed: number): () => number { let x = Math.sin(seed) * 10000; return function() { x = Math.sin(x) * 10000; return x - Math.floor(x); }; } function getDailySeed(): number { const today = new Date().toDateString(); const processStart = process.uptime(); return today.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) + Math.floor(processStart); } function shuffleArray(array: T[], randomFn: () => number): T[] { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(randomFn() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } async function loadRawData(): Promise { if (!cachedData) { const yamlPath = path.join(process.cwd(), 'src/data/tools.yaml'); const yamlContent = await fs.readFile(yamlPath, 'utf8'); const rawData = load(yamlContent); try { cachedData = ToolsDataSchema.parse(rawData); } catch (error) { console.error('YAML validation failed:', error); throw new Error('Invalid tools.yaml structure'); } } return cachedData; } export async function getToolsData(): Promise { const today = new Date().toDateString(); if (!cachedRandomizedData || lastRandomizationDate !== today) { const rawData = await loadRawData(); const seed = getDailySeed(); const randomFn = seededRandom(seed); const randomizedTools = shuffleArray(rawData.tools, randomFn); cachedRandomizedData = { ...rawData, tools: randomizedTools }; lastRandomizationDate = today; cachedCompressedData = null; } return cachedRandomizedData; } export async function getCompressedToolsDataForAI(): Promise { if (!cachedCompressedData) { const data = await getToolsData(); const compressedTools = data.tools .filter(tool => tool.type !== 'concept') .map(tool => { const { projectUrl, statusUrl, ...compressedTool } = tool; return compressedTool; }); const concepts = data.tools .filter(tool => tool.type === 'concept') .map(concept => { const { projectUrl, statusUrl, platforms, accessType, license, ...compressedConcept } = concept; return compressedConcept; }); cachedCompressedData = { tools: compressedTools, concepts: concepts, domains: data.domains, phases: data.phases, 'domain-agnostic-software': data['domain-agnostic-software'] // scenarios intentionally excluded from AI data }; } return cachedCompressedData; } export function clearCache(): void { cachedData = null; cachedRandomizedData = null; cachedCompressedData = null; lastRandomizationDate = null; }