/** * Fairness Orchestration Engine Client * This is a client-side wrapper that would call the API in production */ export interface OutputType { id: string; name: string; weight: number; description: string; } export interface InputSpec { dataset: string; dateRange?: { start: string; end: string }; filters?: Record; sensitiveAttributes: string[]; estimatedSize?: number; } export interface TimelineSpec { mode: 'now' | 'scheduled' | 'continuous'; sla?: string; deadline?: string; } export interface OrchestrationRequest { input: InputSpec; outputs: string[]; timeline: TimelineSpec; } export interface OrchestrationResult { totalLoad: number; inputLoad: number; outputLoad: number; estimatedTime: number; feasible: boolean; warnings: string[]; suggestions: string[]; } // Output type definitions export const OUTPUT_TYPES: Record = { 'fairness-audit-pdf': { id: 'fairness-audit-pdf', name: 'Fairness Audit PDF', weight: 2.5, description: 'Comprehensive fairness audit report in PDF format' }, 'metrics-export': { id: 'metrics-export', name: 'Metrics Export (SPD, TPR, FPR)', weight: 1.0, description: 'Statistical parity difference, true positive rate, false positive rate metrics' }, 'flagged-cases-csv': { id: 'flagged-cases-csv', name: 'Flagged Cases CSV', weight: 1.5, description: 'Export of cases flagged for potential bias issues' }, 'exec-summary-slides': { id: 'exec-summary-slides', name: 'Executive Summary Slide Pack', weight: 2.0, description: 'Executive presentation slides with key findings' }, 'detailed-report-json': { id: 'detailed-report-json', name: 'Detailed Report (JSON)', weight: 1.2, description: 'Machine-readable detailed fairness analysis' }, 'alerts-config': { id: 'alerts-config', name: 'Alert Configuration', weight: 0.8, description: 'Automated alert rules for ongoing monitoring' }, 'dashboard-export': { id: 'dashboard-export', name: 'Dashboard Export', weight: 1.8, description: 'Interactive dashboard with fairness metrics' }, 'compliance-report': { id: 'compliance-report', name: 'Compliance Report', weight: 2.2, description: 'Regulatory compliance documentation' } }; const INPUT_PASS_MULTIPLIER = 2.0; const TOTAL_LOAD_MULTIPLIER = 3.2; const OUTPUT_TARGET_MULTIPLIER = 1.2; const BASE_PROCESSING_RATE = 10; const INPUT_PROCESSING_RATE = 15; const OUTPUT_PROCESSING_RATE = 8; export function calculateInputLoad(input: InputSpec): number { if (input.estimatedSize) { return input.estimatedSize; } let baseSize = 100; baseSize += input.sensitiveAttributes.length * 20; if (input.dateRange) { const start = new Date(input.dateRange.start); const end = new Date(input.dateRange.end); const days = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); baseSize += days * 5; } if (input.filters) { baseSize += Object.keys(input.filters).length * 10; } return baseSize; } export function calculateOutputLoad(outputIds: string[]): number { return outputIds.reduce((total, outputId) => { const output = OUTPUT_TYPES[outputId]; if (!output) return total; return total + output.weight; }, 0); } export function calculateTotalLoad(inputLoad: number, outputLoad: number): number { const inputPasses = inputLoad * INPUT_PASS_MULTIPLIER; return outputLoad + inputPasses; } export function estimateProcessingTime(totalLoad: number): number { const avgRate = (INPUT_PROCESSING_RATE + OUTPUT_PROCESSING_RATE) / 2; return totalLoad / avgRate; } function parseSLAToSeconds(sla: string): number { const match = sla.match(/(\d+)\s*(hour|hours|day|days|minute|minutes|min|mins|second|seconds|sec|secs)/i); if (!match) return 3600; const value = parseInt(match[1], 10); const unit = match[2].toLowerCase(); const multipliers: Record = { second: 1, seconds: 1, sec: 1, secs: 1, minute: 60, minutes: 60, min: 60, mins: 60, hour: 3600, hours: 3600, day: 86400, days: 86400 }; return value * (multipliers[unit] || 3600); } function formatTime(seconds: number): string { if (seconds < 60) return `${Math.ceil(seconds)} seconds`; if (seconds < 3600) return `${(seconds / 60).toFixed(1)} minutes`; if (seconds < 86400) return `${(seconds / 3600).toFixed(1)} hours`; return `${(seconds / 86400).toFixed(1)} days`; } export function checkFeasibility( totalLoad: number, estimatedTime: number, timeline: TimelineSpec ): { feasible: boolean; warnings: string[]; suggestions: string[] } { const warnings: string[] = []; const suggestions: string[] = []; let maxTimeSeconds: number | null = null; if (timeline.sla) { maxTimeSeconds = parseSLAToSeconds(timeline.sla); } else if (timeline.deadline) { const now = Date.now(); const deadline = new Date(timeline.deadline).getTime(); maxTimeSeconds = Math.max(0, (deadline - now) / 1000); } const inputLoad = totalLoad / TOTAL_LOAD_MULTIPLIER; const outputLoad = totalLoad - (inputLoad * INPUT_PASS_MULTIPLIER); const targetOutputLoad = inputLoad * OUTPUT_TARGET_MULTIPLIER; if (outputLoad > targetOutputLoad * 1.5) { warnings.push( `Output complexity (${outputLoad.toFixed(1)} units) is significantly higher than recommended (${targetOutputLoad.toFixed(1)} units)` ); suggestions.push('Consider reducing the number of outputs or simplifying output requirements'); } if (maxTimeSeconds !== null) { if (estimatedTime > maxTimeSeconds) { warnings.push( `Estimated processing time (${formatTime(estimatedTime)}) exceeds requested timeline (${formatTime(maxTimeSeconds)})` ); suggestions.push(`Consider extending timeline to ${formatTime(estimatedTime * 1.2)} or reducing outputs`); } else if (estimatedTime > maxTimeSeconds * 0.8) { warnings.push( `Estimated processing time (${formatTime(estimatedTime)}) is close to timeline limit (${formatTime(maxTimeSeconds)})` ); suggestions.push('Consider adding buffer time or reducing outputs for safety'); } } const expectedTotalLoad = inputLoad * TOTAL_LOAD_MULTIPLIER; if (totalLoad > expectedTotalLoad * 1.3) { warnings.push( `Total process load (${totalLoad.toFixed(1)} units) is higher than expected (${expectedTotalLoad.toFixed(1)} units)` ); } const feasible = warnings.length === 0 || warnings.every(w => !w.includes('exceeds')); return { feasible, warnings, suggestions }; } export function orchestrate(request: OrchestrationRequest): OrchestrationResult { const inputLoad = calculateInputLoad(request.input); const outputLoad = calculateOutputLoad(request.outputs); const totalLoad = calculateTotalLoad(inputLoad, outputLoad); const estimatedTime = estimateProcessingTime(totalLoad); const { feasible, warnings, suggestions } = checkFeasibility( totalLoad, estimatedTime, request.timeline ); return { totalLoad, inputLoad, outputLoad, estimatedTime, feasible, warnings, suggestions }; } export function getUserMessage(result: OrchestrationResult, request: OrchestrationRequest): string { const { inputLoad, outputLoad, estimatedTime, feasible, warnings } = result; if (feasible && warnings.length === 0) { return `This fairness audit will process approximately ${inputLoad.toFixed(0)} input units and generate ${outputLoad.toFixed(1)} output units, taking approximately ${formatTime(estimatedTime)} to complete.`; } if (feasible) { return `This audit is feasible but has some considerations: ${warnings.join('; ')}. Estimated time: ${formatTime(estimatedTime)}.`; } return `This audit configuration may not be feasible within the requested timeline. ${warnings.join('; ')}. Estimated time: ${formatTime(estimatedTime)}.`; } export function getAvailableOutputs(): OutputType[] { return Object.values(OUTPUT_TYPES); } export function getOutputType(id: string): OutputType | undefined { return OUTPUT_TYPES[id]; }