From 47d913cf34cf961152a2765ff83dc64fb32c2010 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Sun, 5 Oct 2025 05:14:58 -0700 Subject: [PATCH] feat: Implement AI Assistance Portal with types and components for student requests and insights --- PHASE3_AI_IMPLEMENTATION.md | 683 ++++++++ PHASE3_ARCHITECTURE.md | 668 ++++++++ package-lock.json | 2174 ++++++++++++++++++++++++- package.json | 19 +- src/App.tsx | 1185 +++++++++++++- src/ai/ProcessingPipeline.ts | 418 +++++ src/ai/StudentAssistanceAI.ts | 803 +++++++++ src/ai/types.ts | 191 +++ src/components/AIAssistancePortal.tsx | 721 ++++++++ 9 files changed, 6761 insertions(+), 101 deletions(-) create mode 100644 PHASE3_AI_IMPLEMENTATION.md create mode 100644 PHASE3_ARCHITECTURE.md create mode 100644 src/ai/ProcessingPipeline.ts create mode 100644 src/ai/StudentAssistanceAI.ts create mode 100644 src/ai/types.ts create mode 100644 src/components/AIAssistancePortal.tsx diff --git a/PHASE3_AI_IMPLEMENTATION.md b/PHASE3_AI_IMPLEMENTATION.md new file mode 100644 index 0000000..b318dac --- /dev/null +++ b/PHASE3_AI_IMPLEMENTATION.md @@ -0,0 +1,683 @@ +# Phase 3 Implementation Plan: Enterprise AI Integration + +## 🤖 Priority 1: AI-Powered Student Assistance Matching + +### Implementation Strategy +This document outlines the immediate next steps to begin Phase 3 implementation with the AI-powered student assistance matching engine - the highest impact feature for immediate organizational transformation. + +### Technical Architecture + +#### 1. AI Model Infrastructure +```typescript +// src/ai/StudentMatchingEngine.ts +interface StudentRequest { + id: string + studentId: string + description: string + category: AssistanceCategory + urgency: UrgencyLevel + location: GeographicLocation + constraints: RequestConstraints + deadline?: Date +} + +interface MatchResult { + resourceId: string + confidenceScore: number + estimatedImpact: number + logisticalComplexity: number + volunteerMatch?: VolunteerAssignment + estimatedCost: number + fulfillmentTimeline: Timeline +} + +class StudentAssistanceAI { + private vectorizer: TextVectorizer + private matchingModel: tf.LayersModel + private impactPredictor: tf.LayersModel + + constructor() { + this.initializeModels() + } + + private async initializeModels() { + // Load pre-trained TensorFlow.js models + this.matchingModel = await tf.loadLayersModel('/models/student-matching.json') + this.impactPredictor = await tf.loadLayersModel('/models/impact-prediction.json') + this.vectorizer = new TextVectorizer() + } + + async processRequest(request: StudentRequest): Promise { + // 1. Analyze and vectorize request + const analysis = await this.analyzeRequest(request) + + // 2. Find optimal resource matches + const candidates = await this.findCandidateResources(analysis) + + // 3. Score and rank matches + const scoredMatches = await this.scoreMatches(candidates, analysis) + + // 4. Predict impact and logistics + const enrichedMatches = await this.enrichWithPredictions(scoredMatches) + + return enrichedMatches.sort((a, b) => b.confidenceScore - a.confidenceScore) + } + + private async analyzeRequest(request: StudentRequest): Promise { + // NLP analysis of request description + const textVector = await this.vectorizer.encode(request.description) + + // Extract key features + const features = { + categoryVector: this.encodeCategoryVector(request.category), + urgencyScore: this.encodeUrgency(request.urgency), + locationVector: this.encodeLocation(request.location), + temporalFeatures: this.encodeTemporalConstraints(request.constraints), + semanticFeatures: textVector + } + + return { + primaryNeeds: await this.extractNeedCategories(textVector), + urgencyScore: features.urgencyScore, + complexityEstimate: await this.estimateComplexity(features), + resourceRequirements: await this.estimateResources(features) + } + } + + private async findCandidateResources(analysis: RequestAnalysis): Promise { + // Query available resources based on analysis + const availableResources = await ResourceManager.getAvailableResources({ + categories: analysis.primaryNeeds, + location: analysis.locationConstraints, + availability: analysis.timeConstraints + }) + + // Add volunteer availability + const volunteerCandidates = await VolunteerManager.getAvailableVolunteers({ + skills: analysis.requiredSkills, + location: analysis.locationConstraints, + availability: analysis.timeConstraints + }) + + return this.combineResourcesAndVolunteers(availableResources, volunteerCandidates) + } + + private async scoreMatches(candidates: ResourceCandidate[], analysis: RequestAnalysis): Promise { + const scoredMatches: ScoredMatch[] = [] + + for (const candidate of candidates) { + // Prepare input tensor for ML model + const inputFeatures = this.prepareFeaturesForML(candidate, analysis) + + // Get confidence score from trained model + const prediction = this.matchingModel.predict(inputFeatures) as tf.Tensor + const confidenceScore = await prediction.data() + + scoredMatches.push({ + ...candidate, + confidenceScore: confidenceScore[0], + reasoningFactors: this.explainScore(candidate, analysis) + }) + + prediction.dispose() // Clean up memory + } + + return scoredMatches + } + + async predictImpact(match: ScoredMatch): Promise { + // Use impact prediction model + const impactFeatures = this.prepareImpactFeatures(match) + const impactPrediction = this.impactPredictor.predict(impactFeatures) as tf.Tensor + const impactScore = await impactPrediction.data() + + impactPrediction.dispose() + + return { + estimatedBeneficiaries: Math.round(impactScore[0]), + successProbability: impactScore[1], + timeToImpact: impactScore[2], + sustainabilityScore: impactScore[3], + rippleEffects: await this.predictRippleEffects(match) + } + } +} +``` + +#### 2. Real-time Processing Pipeline +```typescript +// src/ai/ProcessingPipeline.ts +class RealTimeProcessingPipeline { + private queue: Queue + private aiEngine: StudentAssistanceAI + private notificationService: NotificationService + + constructor() { + this.queue = new Queue('assistance-requests') + this.aiEngine = new StudentAssistanceAI() + this.setupQueueProcessors() + } + + private setupQueueProcessors() { + // Process requests as they come in + this.queue.process('analyze-request', 5, async (job) => { + const request = job.data as StudentRequest + + try { + // AI analysis and matching + const matches = await this.aiEngine.processRequest(request) + + // Auto-approval for high-confidence matches + if (matches[0]?.confidenceScore > 0.9) { + await this.autoApproveRequest(request, matches[0]) + } else { + await this.routeForHumanReview(request, matches) + } + + // Update real-time dashboard + await this.updateDashboard(request.id, matches) + + } catch (error) { + await this.handleProcessingError(request, error) + } + }) + } + + async submitRequest(request: StudentRequest): Promise { + // Add to processing queue + const job = await this.queue.add('analyze-request', request, { + priority: this.calculatePriority(request.urgency), + attempts: 3, + backoff: 'exponential' + }) + + // Immediate acknowledgment + await this.sendAcknowledgment(request) + + return job.id + } + + private async autoApproveRequest(request: StudentRequest, match: MatchResult): Promise { + // Create assistance assignment + const assignment = await AssignmentManager.createAssignment({ + requestId: request.id, + resourceId: match.resourceId, + volunteerId: match.volunteerMatch?.id, + scheduledDate: match.fulfillmentTimeline.startDate, + estimatedCost: match.estimatedCost, + approvalStatus: 'auto-approved', + confidence: match.confidenceScore + }) + + // Notify all stakeholders + await Promise.all([ + this.notificationService.notifyStudent(request.studentId, assignment), + this.notificationService.notifyVolunteer(assignment.volunteerId, assignment), + this.notificationService.notifyCoordinators(assignment), + this.notificationService.updateDonors(assignment.estimatedCost) + ]) + + // Track for learning + await this.trackDecision(request, match, 'auto-approved') + } + + private async routeForHumanReview(request: StudentRequest, matches: MatchResult[]): Promise { + // Determine best reviewer based on request type and matches + const reviewer = await this.selectOptimalReviewer(request, matches) + + // Create review assignment + const reviewTask = await ReviewManager.createReviewTask({ + requestId: request.id, + assignedTo: reviewer.id, + aiRecommendations: matches, + priority: this.calculateReviewPriority(request, matches), + deadline: this.calculateReviewDeadline(request.urgency) + }) + + // Notify reviewer with AI insights + await this.notificationService.notifyReviewer(reviewer, reviewTask, { + aiConfidence: matches[0]?.confidenceScore, + recommendedAction: this.generateRecommendation(matches), + riskFactors: this.identifyRiskFactors(request, matches) + }) + } +} +``` + +#### 3. Learning and Improvement System +```typescript +// src/ai/LearningSystem.ts +class ContinuousLearningSystem { + private feedbackCollector: FeedbackCollector + private modelTrainer: ModelTrainer + + async collectOutcome(assignmentId: string, outcome: AssignmentOutcome): Promise { + // Collect real-world outcomes for model improvement + const assignment = await AssignmentManager.getById(assignmentId) + const originalRequest = await RequestManager.getById(assignment.requestId) + const aiDecision = await this.getOriginalAIDecision(assignmentId) + + const trainingExample = { + features: aiDecision.inputFeatures, + prediction: aiDecision.prediction, + actualOutcome: { + success: outcome.successful, + impactAchieved: outcome.measuredImpact, + costActual: outcome.actualCost, + timeToComplete: outcome.completionTime, + satisfactionScore: outcome.satisfactionRatings + } + } + + // Add to training dataset + await this.feedbackCollector.addTrainingExample(trainingExample) + + // Trigger model retraining if sufficient new data + if (await this.shouldRetrain()) { + await this.scheduleRetraining() + } + } + + async identifyImprovementOpportunities(): Promise { + const insights: ImprovementInsight[] = [] + + // Analyze prediction accuracy trends + const accuracyTrends = await this.analyzeAccuracyTrends() + if (accuracyTrends.declining) { + insights.push({ + type: 'accuracy-decline', + severity: accuracyTrends.severity, + recommendation: 'Model retraining recommended', + estimatedImpact: 'High' + }) + } + + // Identify bias in predictions + const biasAnalysis = await this.analyzeBias() + if (biasAnalysis.significantBias) { + insights.push({ + type: 'prediction-bias', + biasFactors: biasAnalysis.factors, + recommendation: 'Implement bias correction', + estimatedImpact: 'Critical' + }) + } + + // Find optimization opportunities + const optimizations = await this.findOptimizations() + insights.push(...optimizations) + + return insights + } + + private async scheduleRetraining(): Promise { + // Schedule model retraining job + const retrainingJob = await this.queue.add('retrain-models', { + modelTypes: ['matching', 'impact-prediction'], + trainingDataVersion: await this.getLatestDataVersion(), + validationSplit: 0.2, + hyperparameterTuning: true + }, { + priority: 1, + delay: 60000 // Start in 1 minute + }) + + await this.notifyAdministrators({ + message: 'AI model retraining initiated', + jobId: retrainingJob.id, + estimatedDuration: '45-60 minutes' + }) + } +} +``` + +#### 4. Frontend Integration Components +```typescript +// src/components/AIAssistancePortal.tsx +import React, { useState, useEffect } from 'react' +import { motion, AnimatePresence } from 'framer-motion' + +interface AIAssistancePortalProps { + userRole: 'student' | 'coordinator' | 'admin' +} + +export function AIAssistancePortal({ userRole }: AIAssistancePortalProps) { + const [requests, setRequests] = useState([]) + const [aiInsights, setAIInsights] = useState([]) + const [processing, setProcessing] = useState(false) + + useEffect(() => { + // Real-time updates via WebSocket + const ws = new WebSocket(`wss://api.miraclesinmotion.org/ai-updates`) + + ws.onmessage = (event) => { + const update = JSON.parse(event.data) + handleRealTimeUpdate(update) + } + + return () => ws.close() + }, []) + + const handleRealTimeUpdate = (update: AIUpdate) => { + switch (update.type) { + case 'request-processed': + setRequests(prev => prev.map(r => + r.id === update.requestId + ? { ...r, status: update.status, aiRecommendations: update.recommendations } + : r + )) + break + + case 'new-insight': + setAIInsights(prev => [update.insight, ...prev.slice(0, 9)]) + break + + case 'auto-approval': + // Show success notification + showNotification({ + type: 'success', + title: 'Request Auto-Approved', + message: `High-confidence match found for ${update.studentName}`, + action: { + label: 'View Details', + onClick: () => navigateToRequest(update.requestId) + } + }) + break + } + } + + return ( +
+ {/* AI Insights Panel */} + +

+ + AI Insights +

+ + + {aiInsights.map((insight) => ( + +
+
+
+

{insight.title}

+

+ {insight.description} +

+ {insight.confidence && ( +
+
+
+
+ + {Math.round(insight.confidence * 100)}% confidence + +
+ )} +
+
+ + ))} + + + + {/* Request Processing Interface */} +
+

Smart Request Processing

+ + {requests.map((request) => ( + + ))} +
+ + {/* Performance Metrics */} + +
+ ) +} + +function RequestCard({ request, onApprove, onModify, showAIRecommendations }: RequestCardProps) { + return ( + +
+
+

{request.description}

+

+ Student: {request.studentName} • {formatDistanceToNow(request.submittedAt)} ago +

+
+ +
+ + {showAIRecommendations && request.aiRecommendations && ( + +
+ + + AI Recommendation + + +
+ +
+ {request.aiRecommendations.slice(0, 2).map((rec, index) => ( +
+ {rec.resourceName} +
+ ${rec.estimatedCost} + {rec.fulfillmentTimeline} +
+
+ ))} +
+ +
+ onApprove(request.id, request.aiRecommendations[0])} + className="btn-primary text-xs px-3 py-1" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + Approve AI Recommendation + + +
+
+ )} + +
+
+ + +
+ + +
+
+ ) +} + +function AIPerformanceMetrics() { + const [metrics, setMetrics] = useState() + + useEffect(() => { + // Fetch AI performance metrics + fetchAIMetrics().then(setMetrics) + }, []) + + if (!metrics) return null + + return ( +
+

AI Performance

+ +
+ + + + +
+
+ ) +} +``` + +## 🚀 Implementation Timeline (Weeks 1-2) + +### Week 1: Foundation Setup +**Days 1-2: Infrastructure** +- Set up TensorFlow.js environment +- Create AI model loading infrastructure +- Implement basic text vectorization system +- Set up Redis for caching ML predictions + +**Days 3-4: Core AI Engine** +- Build `StudentAssistanceAI` class structure +- Implement request analysis pipeline +- Create resource matching algorithms +- Add confidence scoring system + +**Days 5-7: Integration Layer** +- Create processing pipeline with queue system +- Implement WebSocket for real-time updates +- Build AI portal React components +- Add notification integration + +### Week 2: Enhancement & Testing +**Days 8-10: Learning System** +- Implement feedback collection +- Create model retraining pipeline +- Add performance monitoring +- Build improvement insights system + +**Days 11-12: Frontend Polish** +- Complete AI portal interface +- Add visualizations for AI confidence +- Implement real-time updates +- Create admin controls for AI parameters + +**Days 13-14: Testing & Optimization** +- Comprehensive testing with sample data +- Performance optimization +- Security review +- Documentation completion + +## 📊 Expected Impact + +### Immediate Benefits (Week 2) +- **50% faster** request processing +- **30% improvement** in match accuracy +- **Real-time insights** for coordinators +- **Automated low-risk approvals** + +### Short-term Benefits (Month 1) +- **75% reduction** in manual review time +- **90% accuracy** in resource matching +- **Predictive analytics** for resource planning +- **Continuous learning** from outcomes + +### Long-term Benefits (3-6 months) +- **AI-driven optimization** of entire operation +- **Predictive demand forecasting** +- **Automated workflow recommendations** +- **Data-driven program improvements** + +## 💻 Technical Requirements + +### Dependencies to Add +```bash +npm install @tensorflow/tfjs @tensorflow/tfjs-node +npm install bull redis ioredis +npm install ws socket.io-client +npm install natural compromise +npm install ml-matrix +``` + +### Environment Setup +```bash +# Redis for caching and queues +docker run -d -p 6379:6379 redis:alpine + +# GPU support for faster ML (optional) +npm install @tensorflow/tfjs-node-gpu +``` + +### Model Files Structure +``` +/public/models/ + ├── student-matching.json # Core matching model + ├── student-matching.bin # Model weights + ├── impact-prediction.json # Impact prediction model + ├── impact-prediction.bin # Impact weights + └── text-vectorizer.json # Text processing config +``` + +## 🎯 Success Metrics for Phase 3A + +### Technical Metrics +- **Model Accuracy**: >85% initial, >90% after learning +- **Processing Speed**: <2 seconds per request +- **System Uptime**: >99.5% +- **Auto-Approval Rate**: 60-70% of requests + +### Business Metrics +- **Coordinator Efficiency**: 50% time savings +- **Student Satisfaction**: >4.5/5 rating +- **Resource Utilization**: 25% improvement +- **Response Time**: <2 hours for urgent requests + +Ready to begin Phase 3 AI implementation! This foundation will revolutionize how Miracles in Motion matches students with resources, creating unprecedented efficiency and impact measurement capabilities. \ No newline at end of file diff --git a/PHASE3_ARCHITECTURE.md b/PHASE3_ARCHITECTURE.md new file mode 100644 index 0000000..9d583af --- /dev/null +++ b/PHASE3_ARCHITECTURE.md @@ -0,0 +1,668 @@ +# Phase 3: Enterprise Nonprofit Platform Architecture + +## 🏗️ System Architecture Overview + +### Core Enterprise Components + +#### 1. Microservices Backend Architecture +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ API Gateway │ │ Load Balancer │ │ CDN Network │ +│ (Kong/Nginx) │────│ (HAProxy) │────│ (CloudFlare) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Authentication │ │ Donation │ │ Volunteer │ +│ Service │ │ Service │ │ Service │ +│ (Auth0/JWT) │ │ (Stripe API) │ │ (Scheduling) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ CRM Service │ │ Analytics Svc │ │ Notification │ +│ (Salesforce) │ │ (Real-time) │ │ Service │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +#### 2. Data Architecture +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ PostgreSQL │ │ Redis │ │ Elasticsearch │ +│ (Primary DB) │────│ (Cache) │────│ (Search) │ +│ Multi-tenant │ │ Sessions │ │ Analytics │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Data Lake │ │ ML Pipeline │ │ Reporting │ +│ (AWS S3) │ │ (TensorFlow) │ │ (Tableau) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## 🤖 AI & Machine Learning Layer + +### Smart Assistance Matching Engine +```typescript +interface AssistanceAI { + matchStudent(request: StudentRequest): Promise + predictNeeds(studentProfile: StudentProfile): Promise + optimizeResources(availableResources: Resource[]): Promise +} + +class StudentAssistanceAI { + private mlModel: TensorFlow.LayersModel + private vectorizer: TextVectorizer + + async matchStudent(request: StudentRequest): Promise { + // 1. Vectorize request text and categorize needs + const requestVector = await this.vectorizer.encode(request.description) + const category = await this.classifyNeed(requestVector) + + // 2. Find similar past successful matches + const historicalMatches = await this.findSimilarMatches(requestVector) + + // 3. Score available resources + const scoredResources = await this.scoreResources(category, historicalMatches) + + // 4. Consider logistics (location, timing, volunteer availability) + return this.optimizeMatches(scoredResources, request.constraints) + } + + async predictImpact(intervention: Intervention): Promise { + // ML model trained on historical data to predict intervention success + const features = this.extractFeatures(intervention) + const prediction = await this.mlModel.predict(features) + + return { + successProbability: prediction.dataSync()[0], + estimatedBeneficiaries: Math.round(prediction.dataSync()[1]), + timeToImpact: prediction.dataSync()[2], + confidenceInterval: [ + prediction.dataSync()[3], + prediction.dataSync()[4] + ] + } + } +} +``` + +### Donor Engagement Intelligence +```typescript +class DonorEngagementAI { + async predictDonationTiming(donor: DonorProfile): Promise { + // Analyze donor history, external events, seasonal patterns + const features = { + pastDonations: donor.donationHistory, + emailEngagement: donor.emailMetrics, + seasonality: this.getSeasonalFactors(), + externalEvents: await this.getRelevantEvents(donor.interests) + } + + return { + nextOptimalAsk: new Date(prediction.nextAskDate), + suggestedAmount: prediction.suggestedAmount, + preferredChannel: prediction.channel, + confidence: prediction.confidence + } + } + + async generatePersonalizedContent(donor: DonorProfile): Promise { + // Use GPT-style model fine-tuned on successful donor communications + const context = { + donorValues: donor.motivations, + pastSupport: donor.supportedPrograms, + communicationStyle: donor.preferences + } + + return { + emailSubject: await this.generateSubject(context), + bodyContent: await this.generateBody(context), + callToAction: await this.generateCTA(context), + imageRecommendations: await this.selectImages(context) + } + } +} +``` + +## 🔄 Advanced Workflow Automation + +### Intelligent Request Processing +```typescript +class AutomatedRequestProcessor { + private aiMatcher: StudentAssistanceAI + private workflowEngine: WorkflowEngine + + async processRequest(request: AssistanceRequest): Promise { + // 1. Auto-categorization and urgency scoring + const analysis = await this.analyzeRequest(request) + + // 2. Fraud/spam detection + const securityCheck = await this.performSecurityCheck(request) + if (!securityCheck.isValid) { + return this.handleSuspiciousRequest(request, securityCheck) + } + + // 3. Auto-approval for routine requests + if (analysis.confidence > 0.95 && analysis.urgency < 0.3) { + return await this.autoApprove(request, analysis) + } + + // 4. Route to appropriate human reviewer + return await this.routeForReview(request, analysis) + } + + private async autoApprove(request: AssistanceRequest, analysis: RequestAnalysis) { + // Find optimal resource match + const matches = await this.aiMatcher.matchStudent(request) + const bestMatch = matches[0] + + // Auto-assign volunteer and schedule delivery + const assignment = await this.assignVolunteer(bestMatch) + await this.scheduleDelivery(assignment) + + // Generate communications + await this.notifyStudent(request, assignment) + await this.notifyVolunteer(assignment) + await this.notifyDonors(request, assignment.estimatedCost) + + return { + status: 'auto-approved', + assignment, + estimatedFulfillment: assignment.scheduledDate + } + } +} +``` + +### Smart Donation Workflows +```typescript +class SmartDonationWorkflow { + async processDonation(donation: Donation): Promise { + // 1. Real-time fraud detection + const fraudScore = await this.assessFraudRisk(donation) + + // 2. Tax optimization suggestions + const taxAdvice = await this.generateTaxAdvice(donation) + + // 3. Impact prediction and allocation + const impactForecast = await this.predictImpact(donation.amount) + + // 4. Auto-generate personalized thank you + const thankYou = await this.generateThankYou(donation, impactForecast) + + // 5. Schedule follow-up engagement + await this.scheduleFollowUps(donation, impactForecast) + + return { + transactionId: donation.id, + impactForecast, + taxAdvice, + thankYou, + nextEngagement: await this.getNextEngagement(donation.donor) + } + } + + async optimizeRecurringGifts(donor: DonorProfile): Promise { + // Analyze optimal frequency and amounts based on donor behavior + const analysis = await this.analyzeDonorCapacity(donor) + + return { + recommendedFrequency: analysis.optimalFrequency, + suggestedAmount: analysis.optimalAmount, + projectedAnnualIncrease: analysis.growthPotential, + retentionProbability: analysis.retentionRisk + } + } +} +``` + +## 🏢 Enterprise Integration Hub + +### CRM Integration Layer +```typescript +interface CRMConnector { + // Salesforce Nonprofit Cloud Integration + salesforce: { + contacts: ContactManager + opportunities: OpportunityManager + campaigns: CampaignManager + grants: GrantManager + } + + // HubSpot Nonprofit Integration + hubspot: { + contacts: HubSpotContactAPI + deals: HubSpotDealsAPI + workflows: HubSpotWorkflowAPI + } +} + +class SalesforceIntegration implements CRMConnector['salesforce'] { + async syncDonor(donor: DonorProfile): Promise { + // Bi-directional sync with Salesforce NPSP + const contact = await this.salesforceAPI.createOrUpdateContact({ + firstName: donor.firstName, + lastName: donor.lastName, + email: donor.email, + phone: donor.phone, + donorLevel: this.calculateDonorLevel(donor.totalGiving), + lastGift: donor.lastDonation, + lifetimeGiving: donor.totalGiving, + customFields: { + preferredCommunication: donor.communicationPreference, + volunteerInterest: donor.volunteerInterest, + programInterests: donor.programInterests + } + }) + + // Sync donation history + await this.syncDonationHistory(donor.id, contact.id) + + return contact + } + + async createOpportunity(donation: PendingDonation): Promise { + return await this.salesforceAPI.createOpportunity({ + accountId: donation.donor.salesforceId, + amount: donation.amount, + stageName: 'Pledged', + closeDate: donation.expectedDate, + recordType: 'Donation', + campaign: donation.campaign?.salesforceId, + customFields: { + donationSource: donation.source, + paymentMethod: donation.paymentMethod, + isRecurring: donation.recurring + } + }) + } +} +``` + +### Financial System Integration +```typescript +class QuickBooksIntegration { + async recordDonation(donation: CompletedDonation): Promise { + // Auto-categorize donation for proper bookkeeping + const account = await this.categorizeRevenue(donation) + + const transaction = await this.qbAPI.createTransaction({ + type: 'Income', + account: account.id, + amount: donation.netAmount, + description: `Online donation - ${donation.donor.name}`, + class: donation.program?.qbClass, + customer: await this.getOrCreateDonor(donation.donor), + customFields: { + campaignId: donation.campaign?.id, + processingFee: donation.processingFee, + grossAmount: donation.amount + } + }) + + // Auto-generate receipt + await this.generateReceipt(donation, transaction.id) + + return transaction + } + + async reconcilePayments(startDate: Date, endDate: Date): Promise { + // Auto-match bank deposits with recorded donations + const bankDeposits = await this.getBankDeposits(startDate, endDate) + const recordedDonations = await this.getRecordedDonations(startDate, endDate) + + return this.performReconciliation(bankDeposits, recordedDonations) + } +} +``` + +## 📈 Advanced Analytics & Intelligence + +### Real-time Intelligence Dashboard +```typescript +class AdvancedAnalyticsDashboard { + async getRealTimeMetrics(): Promise { + return { + // Live donation tracking + donations: { + todayTotal: await this.getTodayDonations(), + hourlyTrend: await this.getHourlyTrend(), + conversionRate: await this.getLiveConversionRate(), + averageGift: await this.getAverageGift(), + recurringSignups: await this.getRecurringSignups() + }, + + // Volunteer engagement + volunteers: { + activeToday: await this.getActiveVolunteers(), + pendingAssignments: await this.getPendingAssignments(), + completionRate: await this.getCompletionRate(), + responseTime: await this.getAverageResponseTime() + }, + + // Student assistance + students: { + requestsToday: await this.getTodayRequests(), + fulfillmentRate: await this.getFulfillmentRate(), + averageResponseTime: await this.getAverageProcessingTime(), + impactDelivered: await this.getTodayImpact() + }, + + // Predictive insights + predictions: { + monthEndProjection: await this.projectMonthEnd(), + seasonalForecast: await this.getSeasonalForecast(), + churnRisk: await this.getChurnRisk(), + growthOpportunities: await this.getGrowthOpportunities() + } + } + } + + async generateInsights(): Promise { + const insights: AIInsight[] = [] + + // Anomaly detection + const anomalies = await this.detectAnomalies() + insights.push(...anomalies.map(a => ({ + type: 'anomaly', + title: a.title, + description: a.description, + severity: a.severity, + actionItems: a.suggestedActions + }))) + + // Optimization opportunities + const optimizations = await this.findOptimizations() + insights.push(...optimizations.map(o => ({ + type: 'optimization', + title: o.title, + description: o.description, + potentialImpact: o.estimatedBenefit, + actionItems: o.recommendedActions + }))) + + // Trend analysis + const trends = await this.analyzeTrends() + insights.push(...trends.map(t => ({ + type: 'trend', + title: t.title, + description: t.description, + trajectory: t.direction, + confidence: t.confidence + }))) + + return insights + } +} +``` + +### Predictive Analytics Engine +```typescript +class PredictiveAnalytics { + async forecastDonations(timeframe: DateRange): Promise { + // Multi-model ensemble for accurate predictions + const models = [ + await this.seasonalModel.predict(timeframe), + await this.trendModel.predict(timeframe), + await this.eventBasedModel.predict(timeframe), + await this.economicModel.predict(timeframe) + ] + + const ensemble = this.combineModels(models) + + return { + expectedTotal: ensemble.amount, + confidenceInterval: ensemble.interval, + breakdown: { + new: ensemble.newDonors, + recurring: ensemble.recurringDonors, + major: ensemble.majorGifts + }, + riskFactors: await this.identifyRisks(timeframe), + opportunities: await this.identifyOpportunities(timeframe) + } + } + + async predictVolunteerNeeds(): Promise { + // Predict volunteer capacity needs based on: + // - Student request patterns + // - Seasonal variations + // - Volunteer availability trends + // - Special events and campaigns + + const demandForecast = await this.forecastStudentDemand() + const supplyForecast = await this.forecastVolunteerSupply() + + return { + projectedGap: demandForecast.total - supplyForecast.available, + criticalPeriods: this.identifyCriticalPeriods(demandForecast, supplyForecast), + recruitmentNeeds: this.calculateRecruitmentNeeds(), + skillGaps: await this.identifySkillGaps() + } + } +} +``` + +## 🌐 Multi-Tenant Architecture + +### Organization Management System +```typescript +class MultiTenantManager { + async createOrganization(config: OrganizationConfig): Promise { + // Create isolated tenant environment + const org = await this.createTenant({ + name: config.name, + subdomain: config.subdomain, + plan: config.subscriptionPlan, + features: this.getFeaturesByPlan(config.subscriptionPlan) + }) + + // Setup isolated database schema + await this.setupTenantSchema(org.id) + + // Configure branding and customization + await this.setupBranding(org.id, config.branding) + + // Initialize default workflows and settings + await this.initializeDefaults(org.id, config.organizationType) + + return org + } + + async scaleResources(orgId: string, metrics: UsageMetrics): Promise { + // Auto-scale resources based on usage + const currentUsage = await this.getUsageMetrics(orgId) + const prediction = await this.predictGrowth(orgId, currentUsage) + + if (prediction.needsScaling) { + return await this.implementScaling(orgId, prediction.requirements) + } + + return { status: 'no-action-needed', currentCapacity: currentUsage } + } +} +``` + +### Data Isolation & Security +```typescript +class SecureDataManager { + async accessData(request: DataRequest): Promise { + // Tenant isolation validation + await this.validateTenantAccess(request.userId, request.tenantId) + + // Row-level security enforcement + const securityContext = await this.buildSecurityContext(request.userId) + + // Encrypted data access + const encryptedData = await this.queryWithSecurity( + request.query, + securityContext + ) + + // Decrypt for authorized user + return this.decryptForUser(encryptedData, request.userId) + } + + async auditAccess(request: DataRequest, response: DataResponse): Promise { + await this.logAccess({ + userId: request.userId, + tenantId: request.tenantId, + dataAccessed: response.dataTypes, + timestamp: new Date(), + ipAddress: request.ipAddress, + userAgent: request.userAgent + }) + } +} +``` + +## 📱 Native Mobile Applications + +### React Native Cross-Platform Apps +```typescript +// Mobile App Architecture +interface MobileApp { + authentication: OfflineAuthManager + synchronization: OfflineSyncManager + notifications: PushNotificationManager + geolocation: LocationServicesManager + camera: DocumentScanManager +} + +class MiraclesMobileApp { + async initializeApp(): Promise { + // Setup offline-first architecture + await this.setupOfflineStorage() + await this.initializeSyncEngine() + await this.setupPushNotifications() + + // Initialize secure authentication + await this.setupBiometricAuth() + await this.configureSecureStorage() + } + + async syncData(): Promise { + // Intelligent sync based on connection quality + const connectionType = await this.detectConnectionType() + const syncStrategy = this.selectSyncStrategy(connectionType) + + return await this.performSync(syncStrategy) + } +} + +// Volunteer Mobile Features +class VolunteerMobileApp extends MiraclesMobileApp { + async acceptAssignment(assignmentId: string): Promise { + // Offline-capable assignment acceptance + await this.queueAction('accept_assignment', { assignmentId }) + await this.updateLocalState(assignmentId, 'accepted') + await this.notifyCoordinator(assignmentId) + } + + async scanDeliveryReceipt(imageUri: string): Promise { + // AI-powered receipt processing + const ocrResult = await this.processReceiptOCR(imageUri) + const extracted = await this.extractReceiptData(ocrResult) + + return { + vendor: extracted.vendor, + amount: extracted.amount, + items: extracted.items, + date: extracted.date, + confidence: extracted.confidence + } + } + + async trackDelivery(studentId: string): Promise { + // Real-time delivery tracking with geofencing + const location = await this.getCurrentLocation() + await this.updateDeliveryProgress(studentId, location) + + // Auto-complete when near student location + const distance = this.calculateDistance(location, student.location) + if (distance < 50) { // 50 meters + await this.promptDeliveryCompletion(studentId) + } + } +} +``` + +## 🔧 Implementation Roadmap + +### Week 1-2: Foundation Infrastructure +- Microservices architecture setup +- Database partitioning and multi-tenancy +- API Gateway and load balancing +- Redis caching layer implementation + +### Week 3-4: AI/ML Integration +- TensorFlow.js model deployment +- Student assistance matching engine +- Donor prediction models +- Natural language processing setup + +### Week 5-6: Enterprise Integrations +- Salesforce NPSP connector +- QuickBooks API integration +- Email marketing platform sync +- Payment processor enhancements + +### Week 7-8: Advanced Features +- Mobile app development +- Real-time collaboration tools +- Advanced reporting suite +- Workflow automation engine + +### Week 9-10: Security & Compliance +- SOC 2 Type II implementation +- GDPR compliance framework +- Security audit and penetration testing +- Compliance reporting automation + +## 💰 Investment & ROI Analysis + +### Development Investment +- **Infrastructure**: $15K-25K (cloud setup, security) +- **Development**: $40K-60K (full-stack team for 10 weeks) +- **AI/ML Models**: $10K-15K (training data, compute) +- **Integration Costs**: $8K-12K (third-party APIs, licenses) +- **Total Investment**: $73K-112K + +### Projected ROI (Year 1) +- **Operational Efficiency**: 75% reduction in manual tasks +- **Donation Increase**: 40% improvement in conversion rates +- **Cost Savings**: $45K annually in reduced overhead +- **Revenue Growth**: $150K+ additional donations +- **Net ROI**: 180-250% in first year + +### Scalability Benefits +- **Multi-organization Platform**: $50K-100K annual revenue potential +- **Licensing Opportunities**: Additional revenue streams +- **Consulting Services**: Expert implementation support +- **Partnership Revenue**: Integration and referral income + +## 🎯 Success Metrics + +### Operational KPIs +- **Request Processing Time**: <2 hours average +- **Volunteer Response Rate**: >85% +- **Donor Retention Rate**: >75% +- **System Uptime**: 99.9% +- **Mobile App Rating**: >4.5 stars + +### Business Impact KPIs +- **Students Served Growth**: 300% increase capacity +- **Volunteer Engagement**: 60% improvement +- **Donation Efficiency**: 45% better conversion +- **Administrative Overhead**: 70% reduction +- **Compliance Score**: 100% automated compliance + +## 🚀 Next Phase Execution + +Ready to begin Phase 3 implementation! The recommended starting approach: + +1. **Begin with AI Foundation** - Implement the student assistance matching engine +2. **Parallel Infrastructure Setup** - Microservices and database architecture +3. **CRM Integration Priority** - Salesforce connector for immediate impact +4. **Mobile App Development** - Native apps for volunteers and staff +5. **Advanced Analytics** - Real-time intelligence dashboard + +This Phase 3 architecture will position Miracles in Motion as the premier nonprofit technology platform, capable of serving as a model for the entire sector while dramatically increasing impact and efficiency. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 54ac889..dda3ceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,21 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@tensorflow/tfjs": "^4.22.0", + "bull": "^4.16.5", + "compromise": "^14.14.4", + "date-fns": "^4.1.0", "framer-motion": "^10.16.16", + "ioredis": "^5.8.0", "lucide-react": "^0.290.0", + "ml-matrix": "^6.12.1", + "natural": "^8.1.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "redis": "^5.8.3", + "socket.io-client": "^4.8.1", + "uuid": "^13.0.0", + "ws": "^8.18.3" }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", @@ -887,6 +898,12 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@ioredis/commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -984,6 +1001,93 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.1.tgz", + "integrity": "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1033,6 +1137,66 @@ "node": ">=14" } }, + "node_modules/@redis/bloom": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.3.tgz", + "integrity": "sha512-1eldTzHvdW3Oi0TReb8m1yiFt8ZwyF6rv1NpZyG5R4TpCwuAdKQetBKoCw7D96tNFgsVVd6eL+NaGZZCqhRg4g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/client": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.3.tgz", + "integrity": "sha512-MZVUE+l7LmMIYlIjubPosruJ9ltSLGFmJqsXApTqPLyHLjsJUSAbAJb/A3N34fEqean4ddiDkdWzNu4ZKPvRUg==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.3.tgz", + "integrity": "sha512-DRR09fy/u8gynHGJ4gzXYeM7D8nlS6EMv5o+h20ndTJiAc7RGR01fdk2FNjnn1Nz5PjgGGownF+s72bYG4nZKQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/search": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.3.tgz", + "integrity": "sha512-EMIvEeGRR2I0BJEz4PV88DyCuPmMT1rDtznlsHY3cKSDcc9vj0Q411jUnX0iU2vVowUgWn/cpySKjpXdZ8m+5g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/time-series": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.3.tgz", + "integrity": "sha512-5Jwy3ilsUYQjzpE7WZ1lEeG1RkqQ5kHtwV1p8yxXHSEmyUbC/T/AVgyjMcm52Olj/Ov/mhDKjx6ndYUi14bXsw==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1040,6 +1204,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tailwindcss/typography": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", @@ -1053,6 +1223,128 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@tensorflow/tfjs": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", + "license": "Apache-2.0", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", + "license": "Apache-2.0", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", + "license": "Apache-2.0 AND MIT", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1105,6 +1397,37 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz", + "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.13.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1133,6 +1456,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==", + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", @@ -1140,6 +1469,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", @@ -1366,6 +1710,12 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==", + "license": "BSD-3-Clause" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1389,6 +1739,26 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/afinn-165": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/afinn-165/-/afinn-165-1.0.4.tgz", + "integrity": "sha512-7+Wlx3BImrK0HiG6y3lU4xX7SpBPSSu8T9iguPMlaueRFxjbYwAQrp9lqZUuFikqKbd/en8lVREILvP2J80uJA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/afinn-165-financialmarketnews": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/afinn-165-financialmarketnews/-/afinn-165-financialmarketnews-3.0.0.tgz", + "integrity": "sha512-0g9A1S3ZomFIGDTzZ0t6xmv4AuokBvBmpes8htiyHpH7N4xDmvSQL6UxL/Zcs2ypRb3VwgCscaD8Q3zEawKYhw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1410,7 +1780,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1420,7 +1789,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1453,6 +1821,18 @@ "node": ">= 8" } }, + "node_modules/apparatus": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/apparatus/-/apparatus-0.0.10.tgz", + "integrity": "sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==", + "license": "MIT", + "dependencies": { + "sylvester": ">= 0.0.8" + }, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1481,7 +1861,12 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/autoprefixer": { @@ -1539,6 +1924,18 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1609,6 +2006,71 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bull": { + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/bull/-/bull-4.16.5.tgz", + "integrity": "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==", + "license": "MIT", + "dependencies": { + "cron-parser": "^4.9.0", + "get-port": "^5.1.1", + "ioredis": "^5.3.2", + "lodash": "^4.17.21", + "msgpackr": "^1.11.2", + "semver": "^7.5.2", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/bull/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1654,7 +2116,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -1705,11 +2166,67 @@ "node": ">= 6" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1722,9 +2239,20 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", @@ -1742,6 +2270,20 @@ "dev": true, "license": "MIT" }, + "node_modules/compromise": { + "version": "14.14.4", + "resolved": "https://registry.npmjs.org/compromise/-/compromise-14.14.4.tgz", + "integrity": "sha512-QdbJwronwxeqb7a5KFK/+Y5YieZ4PE1f7ai0vU58Pp4jih+soDCBMuKVbhDEPQ+6+vI3vSiG4UAAjTAXLJw1Qw==", + "license": "MIT", + "dependencies": { + "efrt": "2.7.0", + "grad-school": "0.0.5", + "suffix-thumb": "5.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1756,6 +2298,38 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1791,11 +2365,20 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1816,6 +2399,34 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-libc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", + "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1856,6 +2467,32 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1863,6 +2500,15 @@ "dev": true, "license": "MIT" }, + "node_modules/efrt": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/efrt/-/efrt-2.7.0.tgz", + "integrity": "sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.230", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz", @@ -1884,6 +2530,111 @@ "dev": true, "license": "MIT" }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1926,7 +2677,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2143,6 +2893,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2315,6 +3071,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2332,6 +3108,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -2411,12 +3203,20 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2427,6 +3227,64 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gh-pages": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", @@ -2546,6 +3404,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2553,6 +3423,15 @@ "dev": true, "license": "ISC" }, + "node_modules/grad-school": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/grad-school/-/grad-school-0.0.5.tgz", + "integrity": "sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2564,17 +3443,42 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2583,6 +3487,80 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2639,6 +3617,36 @@ "dev": true, "license": "ISC" }, + "node_modules/ioredis": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.0.tgz", + "integrity": "sha512-AUXbKn9gvo9hHKvk6LbZJQSKn/qIfkWXrnsyL9Yrf+oeXmla9Nmf6XEumOddyhM8neynpK5oAV6r9r99KBuwzA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.4.0", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/is-any-array": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz", + "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2682,7 +3690,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2833,6 +3840,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2893,6 +3909,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2900,6 +3934,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2931,6 +3971,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -2957,6 +4006,30 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memjs": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/memjs/-/memjs-1.3.2.tgz", + "integrity": "sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2981,6 +4054,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -2997,6 +4103,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -3007,13 +4122,181 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/ml-array-max": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-1.2.4.tgz", + "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-1.2.3.tgz", + "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz", + "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0", + "ml-array-max": "^1.2.4", + "ml-array-min": "^1.2.3" + } + }, + "node_modules/ml-matrix": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.12.1.tgz", + "integrity": "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.1", + "ml-array-rescale": "^1.3.7" + } + }, + "node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.0.tgz", + "integrity": "sha512-Z4iRiBkC7aR7a/rxQxtUAUBasFdiXkBuv3EY4NwkRbs92xKA4pwzi1Q4D+odFBe+ChahMNAYg2JP+7tWzZM0sQ==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -3045,6 +4328,32 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/natural/-/natural-8.1.0.tgz", + "integrity": "sha512-qHKU+BzPXzEDwToFBzlI+3oI2jeN3xRNP421ifoF2Fw7ej+5zEO3Z5wUKPjz00jhz9/ESerIUGfhPqqkOqlWPA==", + "license": "MIT", + "dependencies": { + "afinn-165": "^1.0.2", + "afinn-165-financialmarketnews": "^3.0.0", + "apparatus": "^0.0.10", + "dotenv": "^16.4.5", + "http-server": "^14.1.1", + "memjs": "^1.3.2", + "mongoose": "^8.2.0", + "pg": "^8.11.3", + "redis": "^4.6.13", + "safe-stable-stringify": "^2.2.0", + "stopwords-iso": "^1.1.0", + "sylvester": "^0.0.12", + "underscore": "^1.9.1", + "uuid": "^9.0.1", + "wordnet-db": "^3.1.11" + }, + "engines": { + "node": ">=0.4.10" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3052,6 +4361,158 @@ "dev": true, "license": "MIT" }, + "node_modules/natural/node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/natural/node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/natural/node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/natural/node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/natural/node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/natural/node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/natural/node_modules/redis": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/natural/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/natural/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.23", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", @@ -3099,6 +4560,18 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3109,6 +4582,15 @@ "wrappy": "1" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3260,6 +4742,95 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3369,6 +4940,19 @@ "node": ">=8" } }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3546,6 +5130,45 @@ "dev": true, "license": "MIT" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3560,12 +5183,26 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3645,6 +5282,64 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.8.3.tgz", + "integrity": "sha512-MfSrfV6+tEfTw8c4W0yFp6XWX8Il4laGU7Bx4kvW4uiYM1AuZ3KGqEGt1LdQHeD1nEyLpIWetZ/SpY3kkbgrYw==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.8.3", + "@redis/client": "5.8.3", + "@redis/json": "5.8.3", + "@redis/search": "5.8.3", + "@redis/time-series": "5.8.3" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -3745,6 +5440,27 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -3754,11 +5470,22 @@ "loose-envify": "^1.1.0" } }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3790,6 +5517,84 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3813,6 +5618,68 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3823,6 +5690,74 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, + "node_modules/stopwords-iso": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stopwords-iso/-/stopwords-iso-1.1.0.tgz", + "integrity": "sha512-I6GPS/E0zyieHehMRPQcqkiBMJKGgLta+1hREixhoLPqEA0AlVFiC43dl8uPpmkkeRdDMzYRWFWk5/l9x7nmNg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3897,7 +5832,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4026,11 +5960,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/suffix-thumb": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/suffix-thumb/-/suffix-thumb-5.0.2.tgz", + "integrity": "sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4052,6 +5991,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sylvester": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.12.tgz", + "integrity": "sha512-SzRP5LQ6Ts2G5NyAa/jg16s8e3R7rfdFjizy1zeoecYWw+nGL+YA1xZvW/+iJmidBGSdLkuvdwTYEyJEb+EiUw==", + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/tailwindcss": { "version": "3.4.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", @@ -4147,6 +6094,18 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -4236,6 +6195,29 @@ "node": ">=14.17" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", + "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "license": "MIT" + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -4287,6 +6269,12 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4294,6 +6282,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vite": { "version": "4.5.14", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", @@ -4350,6 +6351,40 @@ } } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4376,6 +6411,15 @@ "node": ">=0.10.0" } }, + "node_modules/wordnet-db": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/wordnet-db/-/wordnet-db-3.1.14.tgz", + "integrity": "sha512-zVyFsvE+mq9MCmwXUWHIcpfbrHHClZWZiVOzKSxNJruIcFn2RbY55zkhiAMMxM8zCVSmtNiViq8FsAZSFpMYag==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4484,6 +6528,53 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4491,6 +6582,53 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 598b198..484cde8 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,24 @@ }, "homepage": "https://miraclesinmotion.org", "dependencies": { + "@tensorflow/tfjs": "^4.22.0", + "bull": "^4.16.5", + "compromise": "^14.14.4", + "date-fns": "^4.1.0", + "framer-motion": "^10.16.16", + "ioredis": "^5.8.0", + "lucide-react": "^0.290.0", + "ml-matrix": "^6.12.1", + "natural": "^8.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "framer-motion": "^10.16.16", - "lucide-react": "^0.290.0" + "redis": "^5.8.3", + "socket.io-client": "^4.8.1", + "uuid": "^13.0.0", + "ws": "^8.18.3" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.10", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@typescript-eslint/eslint-plugin": "^6.10.0", @@ -49,8 +61,7 @@ "gh-pages": "^6.0.0", "postcss": "^8.4.31", "tailwindcss": "^3.3.5", - "@tailwindcss/typography": "^0.5.10", "typescript": "^5.2.2", "vite": "^4.5.0" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 965861a..42ec96c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, useContext, createContext } from 'react' -import { motion, useMotionValue, useSpring, useTransform, useScroll } from 'framer-motion' +import { motion, useMotionValue, useSpring, useTransform, useScroll, AnimatePresence } from 'framer-motion' import { ArrowRight, Backpack, @@ -43,14 +43,146 @@ import { Database, Check, Clock, + Bell, + BellRing, + BarChart3, + TrendingUp, + Languages, + Brain, + Cpu, + Download, + WifiOff, + ChevronDown, + Eye, + Zap, + Target, + Activity, } from 'lucide-react' +// Phase 3: AI Components +import AIAssistancePortal from './components/AIAssistancePortal' + /** * Miracles in Motion — Complete Non-Profit Website * A comprehensive 501(c)3 organization website with modern design, * donation processing, volunteer management, and impact tracking. */ +/* ===================== Phase 2: Enhanced Context Systems ===================== */ + +// Notification System Context +const NotificationContext = createContext(null) + +function NotificationProvider({ children }: { children: React.ReactNode }) { + const [notifications, setNotifications] = useState([]) + + const addNotification = (notif: Omit) => { + const newNotification: Notification = { + ...notif, + id: Math.random().toString(36), + timestamp: new Date(), + read: false + } + setNotifications(prev => [newNotification, ...prev]) + + // Auto-remove success notifications after 5 seconds + if (notif.type === 'success') { + setTimeout(() => { + setNotifications(prev => prev.filter(n => n.id !== newNotification.id)) + }, 5000) + } + } + + const markAsRead = (id: string) => { + setNotifications(prev => + prev.map(n => n.id === id ? { ...n, read: true } : n) + ) + } + + const clearAll = () => setNotifications([]) + const unreadCount = notifications.filter(n => !n.read).length + + return ( + + {children} + + ) +} + +function useNotifications() { + const context = useContext(NotificationContext) + if (!context) throw new Error('useNotifications must be used within NotificationProvider') + return context +} + +// Language/Internationalization Context +const LanguageContext = createContext<{ + currentLanguage: Language + languages: Language[] + changeLanguage: (code: string) => void + t: (key: string) => string +} | null>(null) + +const languages: Language[] = [ + { code: 'en', name: 'English', nativeName: 'English', flag: '🇺🇸' }, + { code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' }, + { code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' } +] + +const translations: Translations = { + 'nav.home': { en: 'Home', es: 'Inicio', fr: 'Accueil' }, + 'nav.donate': { en: 'Donate', es: 'Donar', fr: 'Faire un don' }, + 'nav.volunteer': { en: 'Volunteer', es: 'Voluntario', fr: 'Bénévole' }, + 'hero.title': { + en: 'Equipping kids for success—school supplies, clothing, & more', + es: 'Equipando a los niños para el éxito: útiles escolares, ropa y más', + fr: 'Équiper les enfants pour réussir — fournitures scolaires, vêtements et plus' + }, + 'donate.title': { en: 'Donate Now', es: 'Donar Ahora', fr: 'Faire un don maintenant' }, + 'impact.students': { en: 'Students Supported', es: 'Estudiantes Apoyados', fr: 'Étudiants Soutenus' } +} + +function LanguageProvider({ children }: { children: React.ReactNode }) { + const [currentLanguage, setCurrentLanguage] = useState(() => { + const saved = localStorage.getItem('mim_language') + if (saved) { + return languages.find(l => l.code === saved) || languages[0] + } + return languages[0] + }) + + const changeLanguage = (code: string) => { + const language = languages.find(l => l.code === code) + if (language) { + setCurrentLanguage(language) + localStorage.setItem('mim_language', code) + trackEvent('language_changed', { from: currentLanguage.code, to: code }) + } + } + + const t = (key: string): string => { + return translations[key]?.[currentLanguage.code] || key + } + + return ( + + {children} + + ) +} + +function useLanguage() { + const context = useContext(LanguageContext) + if (!context) throw new Error('useLanguage must be used within LanguageProvider') + return context +} + /* ===================== Authentication Context ===================== */ const AuthContext = createContext(null) @@ -155,6 +287,175 @@ function trackEvent(eventName: string, properties: Record = {}) { console.log(`Analytics: ${eventName}`, properties) } +/* ===================== Phase 2: Payment Integration System ===================== */ +const paymentMethods: PaymentMethod[] = [ + { + id: 'stripe', + type: 'card', + name: 'Credit/Debit Card', + icon: '💳', + description: 'Secure payment via Stripe', + processingFee: 2.9 + }, + { + id: 'paypal', + type: 'paypal', + name: 'PayPal', + icon: '🌐', + description: 'Pay with PayPal account', + processingFee: 2.9 + }, + { + id: 'venmo', + type: 'venmo', + name: 'Venmo', + icon: '📱', + description: 'Quick mobile payment', + processingFee: 1.9 + }, + { + id: 'bank', + type: 'bank', + name: 'Bank Transfer', + icon: '🏦', + description: 'Direct bank transfer (ACH)', + processingFee: 0.8 + } +] + +class PaymentProcessor { + static async processPayment(amount: number, method: PaymentMethod, _donorInfo: any): Promise<{ + success: boolean + transactionId?: string + error?: string + }> { + // Simulate payment processing + await new Promise(resolve => setTimeout(resolve, 2000)) + + // Simulate different success rates by payment method + const successRate = method.type === 'bank' ? 0.95 : method.type === 'card' ? 0.92 : 0.98 + const success = Math.random() < successRate + + if (success) { + const transactionId = `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` + + // Track successful payment + trackEvent('donation_completed', { + amount, + method: method.id, + transactionId, + processingFee: amount * (method.processingFee / 100) + }) + + return { success: true, transactionId } + } else { + const errors = [ + 'Payment declined by bank', + 'Insufficient funds', + 'Invalid card information', + 'Network timeout - please try again' + ] + return { success: false, error: errors[Math.floor(Math.random() * errors.length)] } + } + } + + static calculateFees(amount: number, method: PaymentMethod) { + const fee = amount * (method.processingFee / 100) + return { + fee: Math.round(fee * 100) / 100, + net: Math.round((amount - fee) * 100) / 100, + feePercentage: method.processingFee + } + } +} + +/* ===================== Advanced Analytics System ===================== */ +function useAnalytics() { + const [analyticsData, setAnalyticsData] = useState(() => ({ + pageViews: [ + { page: 'Home', views: 2847, trend: 12.5 }, + { page: 'Donate', views: 1203, trend: 8.3 }, + { page: 'Volunteer', views: 856, trend: -2.1 }, + { page: 'Stories', views: 645, trend: 15.8 }, + { page: 'About', views: 432, trend: 5.2 } + ], + donationMetrics: { amount: 45280, count: 186, recurring: 67 }, + userEngagement: { sessions: 3241, avgDuration: 185, bounceRate: 0.34 }, + conversionRates: { donation: 0.078, volunteer: 0.032, contact: 0.156 } + })) + + const refreshAnalytics = () => { + // Simulate real-time data updates + setAnalyticsData(prev => ({ + ...prev, + pageViews: prev.pageViews.map(pv => ({ + ...pv, + views: pv.views + Math.floor(Math.random() * 10), + trend: (Math.random() - 0.5) * 20 + })), + donationMetrics: { + ...prev.donationMetrics, + amount: prev.donationMetrics.amount + Math.floor(Math.random() * 500), + count: prev.donationMetrics.count + Math.floor(Math.random() * 3) + } + })) + } + + useEffect(() => { + const interval = setInterval(refreshAnalytics, 30000) // Update every 30 seconds + return () => clearInterval(interval) + }, []) + + return { analyticsData, refreshAnalytics } +} + +/* ===================== PWA Features ===================== */ +function usePWA() { + const [isOnline, setIsOnline] = useState(navigator.onLine) + const [installPrompt, setInstallPrompt] = useState(null) + const [isInstallable, setIsInstallable] = useState(false) + + useEffect(() => { + const handleOnline = () => setIsOnline(true) + const handleOffline = () => setIsOnline(false) + + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) + + // PWA Install Prompt + const handleBeforeInstallPrompt = (e: any) => { + e.preventDefault() + setInstallPrompt(e) + setIsInstallable(true) + } + + window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt) + + return () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt) + } + }, []) + + const installApp = async () => { + if (!installPrompt) return false + + installPrompt.prompt() + const { outcome } = await installPrompt.userChoice + + if (outcome === 'accepted') { + trackEvent('pwa_installed') + setInstallPrompt(null) + setIsInstallable(false) + return true + } + return false + } + + return { isOnline, isInstallable, installApp } +} + /* ===================== SEO Meta Tags Component ===================== */ function SEOHead({ title, description, image }: { title?: string, description?: string, image?: string }) { useEffect(() => { @@ -223,6 +524,54 @@ interface ImpactCalculation { } } +// Phase 2 Interfaces +interface Notification { + id: string + type: 'success' | 'info' | 'warning' | 'error' + title: string + message: string + timestamp: Date + read: boolean + actions?: { label: string; action: () => void }[] +} + +interface NotificationContextType { + notifications: Notification[] + addNotification: (notification: Omit) => void + markAsRead: (id: string) => void + clearAll: () => void + unreadCount: number +} + +interface AnalyticsData { + pageViews: { page: string; views: number; trend: number }[] + donationMetrics: { amount: number; count: number; recurring: number } + userEngagement: { sessions: number; avgDuration: number; bounceRate: number } + conversionRates: { donation: number; volunteer: number; contact: number } +} + +interface PaymentMethod { + id: string + type: 'card' | 'paypal' | 'venmo' | 'bank' + name: string + icon: string + description: string + processingFee: number +} + +interface Language { + code: string + name: string + nativeName: string + flag: string +} + +interface Translations { + [key: string]: { + [langCode: string]: string + } +} + interface TiltCardProps { icon: React.ComponentType title: string @@ -396,7 +745,7 @@ function Floating3DShapes() { } /* ===================== Router ===================== */ -function useHashRoute() { +export function useHashRoute() { const parse = () => (window.location.hash?.slice(1) || "/") const [route, setRoute] = useState(parse()) @@ -409,74 +758,8 @@ function useHashRoute() { return route } -/* ===================== Main App ===================== */ -export default function MiraclesInMotionSite() { - const [darkMode, setDarkMode] = useState(false) - const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const route = useHashRoute() +/* ===================== Legacy Router Component (Repurposed) ===================== */ - useEffect(() => { - document.title = - route === "/" - ? "Miracles in Motion — Essentials for Every Student" - : `Miracles in Motion — ${route.replace("/", "").replace(/-/g, " ").replace(/\b\w/g, (m) => m.toUpperCase())}` - }, [route]) - - useEffect(() => { - if (darkMode) { - document.documentElement.classList.add('dark') - } else { - document.documentElement.classList.remove('dark') - } - }, [darkMode]) - - return ( -
- - - -
-
-
- -
-
- -
- ) -} - -function Router({ route }: { route: string }) { - switch (route) { - case "/": - return - case "/donate": - return - case "/volunteers": - return - case "/sponsors": - return - case "/stories": - return - case "/testimonies": - return - case "/legal": - return - case "/request-assistance": - return - case "/portals": - return - case "/admin-portal": - return - case "/volunteer-portal": - return - case "/resource-portal": - return - default: - return - } -} /* ===================== Shared UI ===================== */ function SkipToContent() { @@ -1499,6 +1782,10 @@ function DonatePage() { const [customAmount, setCustomAmount] = useState('') const [isRecurring, setIsRecurring] = useState(false) const [donorInfo, setDonorInfo] = useState({ email: '', name: '', anonymous: false }) + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(paymentMethods[0]) + const [isProcessing, setIsProcessing] = useState(false) + const { addNotification } = useNotifications() + const { t } = useLanguage() const suggestedAmounts = [ { amount: 25, impact: "School supplies for 1 student", popular: false }, @@ -1528,14 +1815,63 @@ function DonatePage() { trackEvent('donation_page_view', { amount: finalAmount, recurring: isRecurring }) }, []) - const handleDonationSubmit = () => { + const handleDonationSubmit = async () => { + if (finalAmount <= 0) return + + setIsProcessing(true) trackEvent('donation_initiated', { amount: finalAmount, recurring: isRecurring, - anonymous: donorInfo.anonymous + anonymous: donorInfo.anonymous, + method: selectedPaymentMethod.id }) - // This would integrate with Stripe or another payment processor - alert(`Processing ${isRecurring ? 'monthly ' : ''}donation of $${finalAmount}`) + + addNotification({ + type: 'info', + title: 'Processing Payment', + message: `Processing your ${isRecurring ? 'monthly ' : ''}donation of $${finalAmount}...` + }) + + try { + const result = await PaymentProcessor.processPayment(finalAmount, selectedPaymentMethod, donorInfo) + + if (result.success) { + addNotification({ + type: 'success', + title: 'Donation Successful!', + message: `Thank you for your ${isRecurring ? 'monthly ' : ''}donation of $${finalAmount}. Transaction ID: ${result.transactionId}`, + actions: [ + { + label: 'View Receipt', + action: () => { + // In production, would show receipt modal or download PDF + trackEvent('receipt_viewed', { transactionId: result.transactionId }) + alert('Receipt functionality would open here in production') + } + } + ] + }) + + // Reset form + setSelectedAmount(50) + setCustomAmount('') + setDonorInfo({ email: '', name: '', anonymous: false }) + } else { + addNotification({ + type: 'error', + title: 'Payment Failed', + message: result.error || 'Unable to process payment. Please try again.' + }) + } + } catch (error) { + addNotification({ + type: 'error', + title: 'Payment Error', + message: 'An unexpected error occurred. Please try again.' + }) + } finally { + setIsProcessing(false) + } } return ( @@ -1732,19 +2068,61 @@ function DonatePage() {
+ {/* Payment Method Selection */} +
+

Payment Method

+
+ {paymentMethods.map((method) => { + const fees = PaymentProcessor.calculateFees(finalAmount, method) + return ( + setSelectedPaymentMethod(method)} + className={`p-4 rounded-xl border-2 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 text-left ${ + selectedPaymentMethod.id === method.id + ? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20' + : 'border-neutral-200 dark:border-neutral-700 hover:border-primary-300' + }`} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > +
+ {method.icon} +
+
{method.name}
+
{method.description}
+
+ {selectedPaymentMethod.id === method.id && ( + + )} +
+ {finalAmount > 0 && ( +
+ Fee: ${fees.fee} ({method.processingFee}%) • You give: ${fees.net} +
+ )} +
+ ) + })} +
+
+ {/* Enhanced Donation Buttons */}
0 ? { scale: 1.02 } : {}} - whileTap={finalAmount > 0 ? { scale: 0.98 } : {}} + whileHover={finalAmount > 0 && !isProcessing ? { scale: 1.02 } : {}} + whileTap={finalAmount > 0 && !isProcessing ? { scale: 0.98 } : {}} onClick={handleDonationSubmit} - aria-label={`Donate $${finalAmount} ${isRecurring ? 'monthly' : 'one-time'} via credit card`} + aria-label={`Donate $${finalAmount} ${isRecurring ? 'monthly' : 'one-time'} via ${selectedPaymentMethod.name}`} > - - Donate ${finalAmount} {isRecurring ? 'Monthly' : 'Securely'} + {isProcessing ? ( + <> Processing... + ) : ( + <> {t('donate.title')} ${finalAmount} via {selectedPaymentMethod.icon} + )}
@@ -2671,6 +3049,251 @@ function PortalsPage() { ) } +/* ===================== Phase 2: UI Components ===================== */ + +// Real-time Notification System +export function NotificationCenter() { + const { notifications, markAsRead, clearAll, unreadCount } = useNotifications() + const [isOpen, setIsOpen] = useState(false) + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as Element + if (isOpen && !target.closest('[data-notification-center]')) { + setIsOpen(false) + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, [isOpen]) + + return ( +
+ setIsOpen(!isOpen)} + className="relative p-2 text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {unreadCount > 0 ? : } + {unreadCount > 0 && ( + + {unreadCount > 9 ? '9+' : unreadCount} + + )} + + + + {isOpen && ( + +
+
+

Notifications

+ {notifications.length > 0 && ( + + )} +
+
+ +
+ {notifications.length === 0 ? ( +
+ +

No notifications

+
+ ) : ( + notifications.map((notification) => ( + markAsRead(notification.id)} + whileHover={{ x: 4 }} + > +
+
+ {notification.type === 'success' ? : + notification.type === 'error' ? : + notification.type === 'warning' ? : + } +
+
+

{notification.title}

+

{notification.message}

+

+ {new Date(notification.timestamp).toLocaleString()} +

+
+ {!notification.read && ( +
+ )} +
+
+ )) + )} +
+
+ )} +
+
+ ) +} + +// Language Selector Component +export function LanguageSelector() { + const { currentLanguage, languages, changeLanguage } = useLanguage() + const [isOpen, setIsOpen] = useState(false) + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as Element + if (isOpen && !target.closest('[data-language-selector]')) { + setIsOpen(false) + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, [isOpen]) + + return ( +
+ setIsOpen(!isOpen)} + className="flex items-center gap-2 p-2 text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + + {currentLanguage.flag} + + + + + {isOpen && ( + + {languages.map((language) => ( + { + changeLanguage(language.code) + setIsOpen(false) + }} + className={`w-full flex items-center gap-3 p-3 hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors first:rounded-t-xl last:rounded-b-xl ${ + currentLanguage.code === language.code ? 'bg-primary-50 dark:bg-primary-900/20' : '' + }`} + whileHover={{ x: 4 }} + > + {language.flag} +
+
{language.name}
+
{language.nativeName}
+
+ {currentLanguage.code === language.code && ( + + )} +
+ ))} +
+ )} +
+
+ ) +} + +// PWA Install Prompt +function PWAInstallPrompt() { + const { isInstallable, installApp } = usePWA() + const [showPrompt, setShowPrompt] = useState(false) + + useEffect(() => { + if (isInstallable) { + const timer = setTimeout(() => setShowPrompt(true), 3000) + return () => clearTimeout(timer) + } + }, [isInstallable]) + + if (!showPrompt) return null + + return ( + + +
+
+
+ +
+
+

Install Miracles in Motion

+

+ Get faster access and offline features by installing our app +

+
+ +
+
+ + +
+
+
+
+ ) +} + /* ===================== Authentication Components ===================== */ function LoginForm({ requiredRole }: { requiredRole?: 'admin' | 'volunteer' | 'resource' }) { const { login, isLoading } = useAuth() @@ -3234,6 +3857,262 @@ function ResourcePortalPage() { ) } +// Advanced Analytics Dashboard +function AnalyticsDashboard() { + const { user, logout } = useAuth() + const { analyticsData, refreshAnalytics } = useAnalytics() + const { addNotification } = useNotifications() + + useEffect(() => { + trackEvent('analytics_dashboard_view', { user_id: user?.id, user_role: user?.role }) + }, []) + + const handleRefresh = () => { + refreshAnalytics() + addNotification({ + type: 'success', + title: 'Data Refreshed', + message: 'Analytics data has been updated with the latest information' + }) + } + + return ( + + + + + Refresh + + +
+ } + > +
+ {/* Key Metrics */} +
+ +
+
+

Total Donations

+

+ ${analyticsData.donationMetrics.amount.toLocaleString()} +

+
+ +
+
+ +12.5% + vs last month +
+
+ + +
+
+

Active Volunteers

+

247

+
+ +
+
+ +8.3% + vs last month +
+
+ + +
+
+

Students Helped

+

1,847

+
+ +
+
+ +15.2% + vs last month +
+
+ + +
+
+

Conversion Rate

+

+ {(analyticsData.conversionRates.donation * 100).toFixed(1)}% +

+
+ +
+
+ +3.1% + vs last month +
+
+
+ + {/* Page Views Chart */} +
+
+

Page Performance

+
+ + Last 30 days +
+
+
+ {analyticsData.pageViews.map((page, index) => ( + +
+
0 ? 'bg-green-500' : page.trend < 0 ? 'bg-red-500' : 'bg-gray-400' + }`} /> +
+
{page.page}
+
{page.views.toLocaleString()} views
+
+
+
0 ? 'text-green-600' : page.trend < 0 ? 'text-red-600' : 'text-gray-600' + }`}> + + {page.trend > 0 ? '+' : ''}{page.trend.toFixed(1)}% +
+ + ))} +
+
+ + {/* Real-time Activity */} +
+
+

Recent Activity

+
+ {[ + { action: 'New donation', details: '$125 from Sarah M.', time: '2 minutes ago', icon: Heart }, + { action: 'Volunteer signup', details: 'John D. registered', time: '8 minutes ago', icon: Users }, + { action: 'Assistance request', details: 'Roosevelt Elementary', time: '15 minutes ago', icon: School }, + { action: 'Story shared', details: 'Maria\'s success story', time: '1 hour ago', icon: BookOpenText } + ].map((activity, index) => ( + +
+ +
+
+
{activity.action}
+
{activity.details}
+
+
{activity.time}
+
+ ))} +
+
+ +
+

Impact Summary

+
+
+
+ + Backpacks Distributed +
+ 342 +
+
+
+ + Clothing Items +
+ 789 +
+
+
+ + Emergency Responses +
+ 156 +
+
+
+
+
+ + + ) +} + +// Phase 3: AI Portal Page +function AIPortalPage() { + const { user, logout } = useAuth() + + useEffect(() => { + trackEvent('ai_portal_view', { user_id: user?.id, user_role: user?.role }) + }, []) + + return ( + + + + + Model Status + + +
+ } + > + + + + ) +} + function NotFoundPage() { return (
@@ -3364,4 +4243,152 @@ function Magnetic({ children }: { children: React.ReactNode }) { {children}
) +} + +/* ===================== Main App Component ===================== */ +export default function App() { + return ( + + + + + + + + + ) +} + +function AppContent() { + const [currentPath, setCurrentPath] = useState(window.location.hash.slice(1) || '/') + const [darkMode, setDarkMode] = useState(() => { + if (typeof window !== 'undefined') { + return localStorage.getItem('darkMode') === 'true' || + (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches) + } + return false + }) + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) + const { isOnline } = usePWA() + const { addNotification } = useNotifications() + + useEffect(() => { + const handleHashChange = () => setCurrentPath(window.location.hash.slice(1) || '/') + window.addEventListener('hashchange', handleHashChange) + return () => window.removeEventListener('hashchange', handleHashChange) + }, []) + + useEffect(() => { + if (darkMode) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + localStorage.setItem('darkMode', darkMode.toString()) + }, [darkMode]) + + // Online/Offline notifications + useEffect(() => { + const handleOnline = () => { + addNotification({ + type: 'success', + title: 'Back Online', + message: 'Your internet connection has been restored' + }) + } + + const handleOffline = () => { + addNotification({ + type: 'warning', + title: 'Connection Lost', + message: "You're currently offline. Some features may be limited." + }) + } + + if (!isOnline) { + handleOffline() + } + + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) + + return () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + } + }, [isOnline, addNotification]) + + const renderPage = () => { + // Update document title based on current route + useEffect(() => { + document.title = currentPath === "/" + ? "Miracles in Motion — Essentials for Every Student" + : `Miracles in Motion — ${currentPath.replace("/", "").replace(/-/g, " ").replace(/\b\w/g, (m) => m.toUpperCase())}` + }, [currentPath]) + + switch (currentPath) { + case '/': + return + case '/donate': + return + case '/volunteers': + return + case '/sponsors': + return + case '/stories': + return + case '/testimonies': + return + case '/legal': + return + case '/request-assistance': + return + case '/portals': + return + case '/admin-portal': + return + case '/volunteer-portal': + return + case '/resource-portal': + return + case '/analytics': + return + case '/ai-portal': + return + default: + return + } + } + + return ( +
+ + + + {/* Offline Indicator */} + {!isOnline && ( +
+ + You're currently offline +
+ )} + +
+
+ +
+ {renderPage()} +
+ +
+ + +
+ ) } \ No newline at end of file diff --git a/src/ai/ProcessingPipeline.ts b/src/ai/ProcessingPipeline.ts new file mode 100644 index 0000000..d36e4cc --- /dev/null +++ b/src/ai/ProcessingPipeline.ts @@ -0,0 +1,418 @@ +// Phase 3: Real-time Processing Pipeline for AI Assistance Matching +import type { + StudentRequest, + MatchResult, + AIUpdate, + AIInsight, + ProcessingPipelineConfig +} from './types' +import { aiEngine } from './StudentAssistanceAI' + +// Mock Queue Implementation (in production, use Redis + Bull) +class MockQueue { + private jobs: Array<{ id: string; data: T; priority: number }> = [] + private processors: Map Promise> = new Map() + + async add(jobType: string, data: T, options: { priority: number; attempts?: number; backoff?: string } = { priority: 0 }): Promise<{ id: string }> { + const job = { + id: `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + data, + priority: options.priority + } + + this.jobs.push(job) + this.jobs.sort((a, b) => b.priority - a.priority) // Higher priority first + + // Process job immediately for demo + setTimeout(() => this.processNextJob(jobType), 100) + + return job + } + + process(jobType: string, concurrency: number, processor: (job: { id: string; data: T }) => Promise): void { + this.processors.set(jobType, processor) + } + + private async processNextJob(jobType: string): Promise { + const processor = this.processors.get(jobType) + if (!processor || this.jobs.length === 0) return + + const job = this.jobs.shift()! + + try { + await processor(job) + } catch (error) { + console.error(`Error processing job ${job.id}:`, error) + } + } +} + +// Notification Service for real-time updates +class NotificationService { + private subscribers: Set<(update: AIUpdate) => void> = new Set() + + subscribe(callback: (update: AIUpdate) => void): () => void { + this.subscribers.add(callback) + return () => this.subscribers.delete(callback) + } + + notify(update: AIUpdate): void { + this.subscribers.forEach(callback => { + try { + callback(update) + } catch (error) { + console.error('Error in notification callback:', error) + } + }) + } + + async notifyStudent(studentId: string, assignment: any): Promise { + console.log(`📧 Notifying student ${studentId} about assignment`) + // In production: send email, SMS, or push notification + } + + async notifyVolunteer(volunteerId: string, assignment: any): Promise { + console.log(`📧 Notifying volunteer ${volunteerId} about new assignment`) + // In production: send volunteer notification + } + + async notifyCoordinators(assignment: any): Promise { + console.log(`📧 Notifying coordinators about new assignment`) + // In production: alert coordination team + } + + async updateDonors(estimatedCost: number): Promise { + console.log(`💰 Updating donors about $${estimatedCost} impact opportunity`) + // In production: trigger donor engagement campaign + } + + async notifyReviewer(reviewer: any, reviewTask: any, aiInsights: any): Promise { + console.log(`👥 Notifying reviewer ${reviewer.id} about review task with AI confidence: ${aiInsights.aiConfidence}`) + // In production: send detailed review notification with AI recommendations + } +} + +// Assignment and Review Management +class AssignmentManager { + private static assignments: Map = new Map() + + static async createAssignment(assignmentData: any): Promise { + const assignment = { + id: `assign-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + ...assignmentData, + createdAt: new Date(), + status: 'pending' + } + + this.assignments.set(assignment.id, assignment) + console.log(`✅ Created assignment ${assignment.id}`) + return assignment + } + + static async getById(id: string): Promise { + return this.assignments.get(id) || null + } +} + +class ReviewManager { + private static reviewTasks: Map = new Map() + + static async createReviewTask(taskData: any): Promise { + const task = { + id: `review-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + ...taskData, + createdAt: new Date(), + status: 'pending' + } + + this.reviewTasks.set(task.id, task) + console.log(`📋 Created review task ${task.id}`) + return task + } +} + +// Main Processing Pipeline +export class RealTimeProcessingPipeline { + private queue: MockQueue + private notificationService: NotificationService + private config: ProcessingPipelineConfig + + constructor(config: Partial = {}) { + this.queue = new MockQueue() + this.notificationService = new NotificationService() + this.config = { + autoApprovalThreshold: 0.85, + urgencyWeights: { + 'emergency': 1.0, + 'high': 0.8, + 'medium': 0.5, + 'low': 0.2 + }, + categoryWeights: { + 'emergency-housing': 1.0, + 'food-assistance': 0.9, + 'medical-needs': 0.85, + 'clothing': 0.7, + 'school-supplies': 0.6, + 'transportation': 0.5, + 'technology': 0.4, + 'extracurricular': 0.3, + 'other': 0.4 + }, + maxProcessingTime: 5000, // 5 seconds + retryAttempts: 3, + notificationEnabled: true, + ...config + } + + this.setupQueueProcessors() + } + + private setupQueueProcessors(): void { + this.queue.process('analyze-request', 5, async (job) => { + const request = job.data + + try { + console.log(`🔄 Processing request ${request.id} for ${request.studentName}`) + + // AI analysis and matching + const matches = await aiEngine.processRequest(request) + + // Auto-approval for high-confidence matches + if (matches.length > 0 && matches[0].confidenceScore >= this.config.autoApprovalThreshold) { + await this.autoApproveRequest(request, matches[0]) + } else { + await this.routeForHumanReview(request, matches) + } + + // Update real-time dashboard + await this.updateDashboard(request.id, matches) + + // Notify subscribers of processing completion + this.notificationService.notify({ + type: 'request-processed', + requestId: request.id, + studentName: request.studentName, + status: matches.length > 0 && matches[0].confidenceScore >= this.config.autoApprovalThreshold ? 'auto-approved' : 'under-review', + recommendations: matches, + timestamp: new Date() + }) + + } catch (error) { + await this.handleProcessingError(request, error as Error) + } + }) + } + + async submitRequest(request: StudentRequest): Promise { + console.log(`📥 Submitting request from ${request.studentName}: ${request.category}`) + + // Add to processing queue with priority based on urgency + const priority = this.calculatePriority(request.urgency) + const job = await this.queue.add('analyze-request', request, { + priority, + attempts: this.config.retryAttempts, + backoff: 'exponential' + }) + + // Immediate acknowledgment + await this.sendAcknowledgment(request) + + return job.id + } + + private calculatePriority(urgency: string): number { + return this.config.urgencyWeights[urgency as keyof typeof this.config.urgencyWeights] || 0.5 + } + + private async sendAcknowledgment(request: StudentRequest): Promise { + console.log(`✉️ Sending acknowledgment to ${request.studentName}`) + // In production: send immediate confirmation email/SMS + } + + private async autoApproveRequest(request: StudentRequest, match: MatchResult): Promise { + console.log(`🤖 Auto-approving request ${request.id} with ${(match.confidenceScore * 100).toFixed(1)}% confidence`) + + // Create assistance assignment + const assignment = await AssignmentManager.createAssignment({ + requestId: request.id, + studentId: request.studentId, + studentName: request.studentName, + resourceId: match.resourceId, + resourceName: match.resourceName, + volunteerId: match.volunteerMatch?.id, + volunteerName: match.volunteerMatch?.volunteerName, + scheduledDate: new Date(Date.now() + 24 * 60 * 60 * 1000), // Tomorrow + estimatedCost: match.estimatedCost, + approvalStatus: 'auto-approved', + confidence: match.confidenceScore, + aiRecommendation: true, + urgency: request.urgency, + category: request.category + }) + + // Notify all stakeholders + if (this.config.notificationEnabled) { + await Promise.all([ + this.notificationService.notifyStudent(request.studentId, assignment), + match.volunteerMatch ? this.notificationService.notifyVolunteer(assignment.volunteerId, assignment) : Promise.resolve(), + this.notificationService.notifyCoordinators(assignment), + this.notificationService.updateDonors(assignment.estimatedCost) + ]) + } + + // Notify real-time subscribers + this.notificationService.notify({ + type: 'auto-approval', + requestId: request.id, + studentName: request.studentName, + message: `Request automatically approved with ${(match.confidenceScore * 100).toFixed(1)}% confidence`, + timestamp: new Date() + }) + + // Track decision for learning + await this.trackDecision(request, match, 'auto-approved') + } + + private async routeForHumanReview(request: StudentRequest, matches: MatchResult[]): Promise { + console.log(`👤 Routing request ${request.id} for human review`) + + // Determine best reviewer based on request type and matches + const reviewer = await this.selectOptimalReviewer(request, matches) + + // Create review assignment + const reviewTask = await ReviewManager.createReviewTask({ + requestId: request.id, + assignedTo: reviewer.id, + assignedToName: reviewer.name, + aiRecommendations: matches, + priority: this.calculateReviewPriority(request, matches), + deadline: this.calculateReviewDeadline(request.urgency), + studentName: request.studentName, + category: request.category, + urgency: request.urgency + }) + + // Notify reviewer with AI insights + if (this.config.notificationEnabled) { + await this.notificationService.notifyReviewer(reviewer, reviewTask, { + aiConfidence: matches[0]?.confidenceScore || 0, + recommendedAction: this.generateRecommendation(matches), + riskFactors: matches[0]?.riskFactors || [] + }) + } + } + + private async selectOptimalReviewer(request: StudentRequest, matches: MatchResult[]) { + // Mock reviewer selection - in production, this would use actual staff data + const reviewers = [ + { id: 'rev1', name: 'Sarah Martinez', specialties: ['clothing', 'emergency-housing'], workload: 5 }, + { id: 'rev2', name: 'John Davis', specialties: ['food-assistance', 'transportation'], workload: 3 }, + { id: 'rev3', name: 'Lisa Chen', specialties: ['school-supplies', 'technology'], workload: 7 }, + { id: 'rev4', name: 'Mike Johnson', specialties: ['medical-needs', 'other'], workload: 4 } + ] + + // Select reviewer based on specialty and workload + const categoryReviewers = reviewers.filter(r => + r.specialties.includes(request.category) || r.specialties.includes('other') + ) + + // Return reviewer with lowest workload + return categoryReviewers.sort((a, b) => a.workload - b.workload)[0] || reviewers[0] + } + + private calculateReviewPriority(request: StudentRequest, matches: MatchResult[]): number { + let priority = this.config.urgencyWeights[request.urgency as keyof typeof this.config.urgencyWeights] || 0.5 + + // Boost priority for high AI confidence but below threshold + if (matches.length > 0) { + const topMatch = matches[0] + if (topMatch.confidenceScore > 0.7 && topMatch.confidenceScore < this.config.autoApprovalThreshold) { + priority += 0.2 + } + } + + // Boost priority for critical categories + priority += this.config.categoryWeights[request.category as keyof typeof this.config.categoryWeights] || 0 + + return Math.min(priority, 1.0) + } + + private calculateReviewDeadline(urgency: string): Date { + const now = new Date() + switch (urgency) { + case 'emergency': + return new Date(now.getTime() + 30 * 60 * 1000) // 30 minutes + case 'high': + return new Date(now.getTime() + 2 * 60 * 60 * 1000) // 2 hours + case 'medium': + return new Date(now.getTime() + 8 * 60 * 60 * 1000) // 8 hours + case 'low': + default: + return new Date(now.getTime() + 24 * 60 * 60 * 1000) // 24 hours + } + } + + private generateRecommendation(matches: MatchResult[]): string { + if (matches.length === 0) return 'No suitable matches found - manual resource allocation needed' + + const topMatch = matches[0] + if (topMatch.confidenceScore > 0.8) { + return `Strong AI recommendation: ${topMatch.resourceName} (${(topMatch.confidenceScore * 100).toFixed(1)}% confidence)` + } else if (topMatch.confidenceScore > 0.6) { + return `Moderate AI recommendation: ${topMatch.resourceName} - review for accuracy` + } else { + return `Low confidence match: manual evaluation recommended` + } + } + + private async updateDashboard(requestId: string, matches: MatchResult[]): Promise { + console.log(`📊 Updating dashboard for request ${requestId}`) + // In production: update real-time analytics dashboard + } + + private async handleProcessingError(request: StudentRequest, error: Error): Promise { + console.error(`❌ Error processing request ${request.id}:`, error.message) + + // Notify administrators of processing error + this.notificationService.notify({ + type: 'alert', + requestId: request.id, + studentName: request.studentName, + message: `Processing error: ${error.message}`, + timestamp: new Date() + }) + + // Route to manual processing + await this.routeForHumanReview(request, []) + } + + private async trackDecision(request: StudentRequest, match: MatchResult, decision: string): Promise { + console.log(`📈 Tracking decision: ${decision} for request ${request.id}`) + // In production: log decision for ML model training + } + + // Public methods for integration + subscribe(callback: (update: AIUpdate) => void): () => void { + return this.notificationService.subscribe(callback) + } + + async generateInsights(requests: StudentRequest[]): Promise { + return await aiEngine.generateInsights(requests) + } + + getConfig(): ProcessingPipelineConfig { + return { ...this.config } + } + + updateConfig(newConfig: Partial): void { + this.config = { ...this.config, ...newConfig } + console.log('🔧 Pipeline configuration updated') + } +} + +// Export singleton instance +export const processingPipeline = new RealTimeProcessingPipeline() + +// Export classes for testing and advanced usage +export { NotificationService, AssignmentManager, ReviewManager } \ No newline at end of file diff --git a/src/ai/StudentAssistanceAI.ts b/src/ai/StudentAssistanceAI.ts new file mode 100644 index 0000000..6c28c3d --- /dev/null +++ b/src/ai/StudentAssistanceAI.ts @@ -0,0 +1,803 @@ +// Phase 3: AI-Powered Student Assistance Matching Engine +import * as tf from '@tensorflow/tfjs' +import type { + StudentRequest, + MatchResult, + RequestAnalysis, + AIInsight, + LearningFeedback, + NeedCategory, + ResourceRequirement +} from './types' + +// Text Vectorization for NLP Analysis +class TextVectorizer { + private vocabulary: Map = new Map() + private vectorSize: number = 100 + + constructor() { + this.initializeVocabulary() + } + + private initializeVocabulary() { + // Common assistance-related vocabulary + const assistanceVocabulary = [ + // Clothing + 'clothes', 'clothing', 'shirt', 'pants', 'shoes', 'coat', 'jacket', 'uniform', 'dress', + 'socks', 'underwear', 'boots', 'sneakers', 'winter', 'warm', 'size', 'outgrown', + + // School supplies + 'supplies', 'backpack', 'notebook', 'pencil', 'pen', 'binder', 'calculator', 'books', + 'textbook', 'paper', 'folders', 'highlighter', 'ruler', 'glue', 'scissors', + + // Food assistance + 'food', 'hungry', 'lunch', 'breakfast', 'dinner', 'meal', 'snack', 'groceries', + 'nutrition', 'eat', 'starving', 'appetite', 'diet', 'allergic', + + // Transportation + 'transport', 'bus', 'ride', 'car', 'walk', 'distance', 'far', 'pickup', 'drop-off', + 'gas', 'vehicle', 'bicycle', 'train', 'subway', + + // Emergency/housing + 'emergency', 'urgent', 'homeless', 'shelter', 'roof', 'home', 'housing', 'evicted', + 'temporary', 'crisis', 'help', 'desperate', + + // Medical + 'medical', 'doctor', 'medicine', 'sick', 'health', 'prescription', 'hospital', + 'glasses', 'dental', 'therapy', 'insurance', + + // Technology + 'computer', 'laptop', 'tablet', 'phone', 'internet', 'wifi', 'online', 'digital', + 'device', 'charger', 'software', 'access', + + // Emotional/contextual + 'need', 'help', 'family', 'parent', 'mother', 'father', 'guardian', 'sibling', + 'student', 'school', 'grade', 'class', 'teacher', 'principal', 'counselor', + 'poor', 'afford', 'money', 'financial', 'struggle', 'difficult', 'hardship' + ] + + assistanceVocabulary.forEach((word, index) => { + this.vocabulary.set(word.toLowerCase(), index) + }) + } + + async encode(text: string): Promise { + const words = this.preprocessText(text) + const vector = new Array(this.vectorSize).fill(0) + + // Simple bag-of-words with TF-IDF weighting + const wordCounts = new Map() + words.forEach(word => { + wordCounts.set(word, (wordCounts.get(word) || 0) + 1) + }) + + // Create weighted vector + wordCounts.forEach((count, word) => { + const vocabIndex = this.vocabulary.get(word) + if (vocabIndex !== undefined && vocabIndex < this.vectorSize) { + // Simple TF-IDF approximation + const tf = count / words.length + const idf = Math.log(this.vocabulary.size / (count + 1)) + vector[vocabIndex] = tf * idf + } + }) + + // Simple semantic analysis without external NLP library + // Extract entities and sentiment patterns + + // Boost urgency indicators + const urgencyWords = ['urgent', 'emergency', 'immediate', 'asap', 'desperate', 'critical'] + const urgencyScore = urgencyWords.reduce((score, word) => { + return score + (text.toLowerCase().includes(word) ? 1 : 0) + }, 0) + + // Add urgency as a feature + if (vector.length > this.vectorSize - 10) { + vector[this.vectorSize - 1] = urgencyScore / urgencyWords.length + } + + return vector + } + + private preprocessText(text: string): string[] { + return text + .toLowerCase() + .replace(/[^\w\s]/g, ' ') + .split(/\s+/) + .filter(word => word.length > 2) + } +} + +// Core AI Matching Engine +export class StudentAssistanceAI { + private vectorizer: TextVectorizer + private matchingModel: tf.LayersModel | null = null + private impactModel: tf.LayersModel | null = null + private isInitialized = false + + // Mock data for demonstration - in production, this would come from databases + private mockResources = [ + { id: 'r1', name: 'Emergency Clothing Fund', type: 'clothing', capacity: 100, avgCost: 75 }, + { id: 'r2', name: 'School Supply Backpack Program', type: 'supplies', capacity: 50, avgCost: 45 }, + { id: 'r3', name: 'Weekend Food Program', type: 'food', capacity: 200, avgCost: 25 }, + { id: 'r4', name: 'Transportation Assistance', type: 'transport', capacity: 30, avgCost: 40 }, + { id: 'r5', name: 'Technology Access Fund', type: 'technology', capacity: 25, avgCost: 200 } + ] + + private mockVolunteers = [ + { id: 'v1', name: 'Sarah Johnson', skills: ['clothing', 'shopping'], rating: 4.8, location: { city: 'Austin', state: 'TX' } }, + { id: 'v2', name: 'Mike Chen', skills: ['supplies', 'delivery'], rating: 4.9, location: { city: 'Austin', state: 'TX' } }, + { id: 'v3', name: 'Emily Rodriguez', skills: ['food', 'cooking'], rating: 4.7, location: { city: 'Round Rock', state: 'TX' } }, + { id: 'v4', name: 'David Thompson', skills: ['transport', 'technology'], rating: 4.6, location: { city: 'Cedar Park', state: 'TX' } } + ] + + constructor() { + this.vectorizer = new TextVectorizer() + this.initializeModels() + } + + private async initializeModels() { + try { + // Create simple neural network models for demonstration + // In production, these would be pre-trained models loaded from files + + // Matching Model: Input features -> Match probability + this.matchingModel = tf.sequential({ + layers: [ + tf.layers.dense({ inputShape: [110], units: 64, activation: 'relu' }), + tf.layers.dropout({ rate: 0.2 }), + tf.layers.dense({ units: 32, activation: 'relu' }), + tf.layers.dense({ units: 16, activation: 'relu' }), + tf.layers.dense({ units: 1, activation: 'sigmoid' }) + ] + }) + + // Impact Prediction Model: Match features -> Impact score + this.impactModel = tf.sequential({ + layers: [ + tf.layers.dense({ inputShape: [20], units: 32, activation: 'relu' }), + tf.layers.dense({ units: 16, activation: 'relu' }), + tf.layers.dense({ units: 4, activation: 'linear' }) // [beneficiaries, success_prob, time, sustainability] + ] + }) + + // Compile models + this.matchingModel.compile({ + optimizer: tf.train.adam(0.001), + loss: 'binaryCrossentropy', + metrics: ['accuracy'] + }) + + this.impactModel.compile({ + optimizer: tf.train.adam(0.001), + loss: 'meanSquaredError', + metrics: ['mae'] + }) + + this.isInitialized = true + console.log('🤖 AI Models initialized successfully') + + } catch (error) { + console.error('Failed to initialize AI models:', error) + // Fallback to rule-based system + this.isInitialized = false + } + } + + async processRequest(request: StudentRequest): Promise { + console.log(`🧠 Processing request for ${request.studentName}: ${request.description}`) + + try { + // 1. Analyze request with NLP + const analysis = await this.analyzeRequest(request) + + // 2. Find candidate resources + const candidates = await this.findCandidateResources(analysis) + + // 3. Score matches using AI or rule-based fallback + const scoredMatches = this.isInitialized + ? await this.aiScoreMatches(candidates, analysis, request) + : await this.ruleBasedScoring(candidates, analysis, request) + + // 4. Predict impact for top matches + const enrichedMatches = await this.enrichWithPredictions(scoredMatches) + + // 5. Sort by confidence and return top 3 + return enrichedMatches + .sort((a, b) => b.confidenceScore - a.confidenceScore) + .slice(0, 3) + + } catch (error) { + console.error('Error processing request:', error) + return this.generateFallbackMatches() + } + } + + private async analyzeRequest(request: StudentRequest): Promise { + // NLP analysis of request description + await this.vectorizer.encode(request.description) // Process text for any side effects + + // Extract needs using rule-based analysis + NLP + const primaryNeeds = this.extractNeedCategories(request) + const urgencyScore = this.calculateUrgencyScore(request) + const complexityEstimate = this.estimateComplexity(request) + + return { + primaryNeeds, + urgencyScore, + complexityEstimate, + resourceRequirements: this.estimateResourceRequirements(primaryNeeds), + estimatedBudget: this.estimateBudget(primaryNeeds, request.description) + } + } + + private extractNeedCategories(request: StudentRequest): NeedCategory[] { + const categories: NeedCategory[] = [] + const desc = request.description.toLowerCase() + + // Clothing detection + if (desc.includes('clothes') || desc.includes('clothing') || desc.includes('shirt') || + desc.includes('pants') || desc.includes('shoes') || desc.includes('coat')) { + categories.push({ + category: 'clothing', + priority: desc.includes('winter') || desc.includes('cold') ? 0.9 : 0.7, + specifications: { season: desc.includes('winter') ? 'winter' : 'general' } + }) + } + + // School supplies detection + if (desc.includes('supplies') || desc.includes('backpack') || desc.includes('notebook') || + desc.includes('pencil') || desc.includes('books')) { + categories.push({ + category: 'school-supplies', + priority: 0.8, + specifications: { type: 'general' } + }) + } + + // Food assistance detection + if (desc.includes('food') || desc.includes('hungry') || desc.includes('lunch') || + desc.includes('meal') || desc.includes('eat')) { + categories.push({ + category: 'food-assistance', + priority: desc.includes('hungry') || desc.includes('starving') ? 0.95 : 0.75, + specifications: { urgency: desc.includes('hungry') ? 'high' : 'medium' } + }) + } + + // Transportation detection + if (desc.includes('transport') || desc.includes('bus') || desc.includes('ride') || + desc.includes('car') || desc.includes('far')) { + categories.push({ + category: 'transportation', + priority: 0.6, + specifications: { type: 'general' } + }) + } + + // Emergency/housing detection + if (desc.includes('emergency') || desc.includes('urgent') || desc.includes('homeless') || + desc.includes('shelter') || desc.includes('crisis')) { + categories.push({ + category: 'emergency-housing', + priority: 0.98, + specifications: { urgency: 'critical' } + }) + } + + // Technology detection + if (desc.includes('computer') || desc.includes('laptop') || desc.includes('internet') || + desc.includes('online') || desc.includes('device')) { + categories.push({ + category: 'technology', + priority: 0.65, + specifications: { type: 'educational' } + }) + } + + // Default to the request category if nothing else matches + if (categories.length === 0) { + categories.push({ + category: request.category, + priority: 0.5, + specifications: {} + }) + } + + return categories.sort((a, b) => b.priority - a.priority) + } + + private calculateUrgencyScore(request: StudentRequest): number { + let score = 0 + + // Base score from urgency level + switch (request.urgency) { + case 'emergency': score += 1.0; break + case 'high': score += 0.8; break + case 'medium': score += 0.5; break + case 'low': score += 0.2; break + } + + // Boost for urgency keywords in description + const desc = request.description.toLowerCase() + const urgencyKeywords = ['urgent', 'emergency', 'immediate', 'asap', 'desperate', 'critical', 'help', 'need now'] + urgencyKeywords.forEach(keyword => { + if (desc.includes(keyword)) score += 0.1 + }) + + // Boost for deadline + if (request.deadline) { + const daysUntilDeadline = (request.deadline.getTime() - Date.now()) / (1000 * 60 * 60 * 24) + if (daysUntilDeadline < 1) score += 0.3 + else if (daysUntilDeadline < 3) score += 0.2 + else if (daysUntilDeadline < 7) score += 0.1 + } + + return Math.min(score, 1.0) // Cap at 1.0 + } + + private estimateComplexity(request: StudentRequest): number { + let complexity = 0.3 // Base complexity + + // Multiple needs increase complexity + const needCount = this.extractNeedCategories(request).length + complexity += needCount * 0.15 + + // Special constraints increase complexity + if (request.constraints.deliveryMethod === 'delivery') complexity += 0.2 + if (request.constraints.privacyLevel === 'anonymous') complexity += 0.1 + if (request.constraints.specialRequirements?.length) complexity += 0.2 + + // Geographic complexity + if (!request.location.city || !request.location.zipCode) complexity += 0.15 + + return Math.min(complexity, 1.0) + } + + private estimateResourceRequirements(needs: NeedCategory[]): ResourceRequirement[] { + return needs.map(need => ({ + type: need.category, + quantity: 1, + specifications: need.specifications || {}, + estimatedCost: this.estimateCategoryBudget(need.category) + })) + } + + private estimateBudget(needs: NeedCategory[], description: string): number { + let totalBudget = 0 + + needs.forEach(need => { + totalBudget += this.estimateCategoryBudget(need.category) * need.priority + }) + + // Adjust based on description indicators + const desc = description.toLowerCase() + if (desc.includes('multiple') || desc.includes('several')) totalBudget *= 1.5 + if (desc.includes('family') || desc.includes('siblings')) totalBudget *= 2 + if (desc.includes('brand new') || desc.includes('quality')) totalBudget *= 1.3 + + return Math.round(totalBudget) + } + + private estimateCategoryBudget(category: string): number { + const budgets: Record = { + 'clothing': 75, + 'school-supplies': 45, + 'food-assistance': 25, + 'transportation': 40, + 'emergency-housing': 200, + 'medical-needs': 150, + 'technology': 200, + 'extracurricular': 60, + 'other': 50 + } + return budgets[category] || 50 + } + + private async findCandidateResources(analysis: RequestAnalysis) { + // Find resources that match the primary needs + const candidates = this.mockResources.filter(resource => + analysis.primaryNeeds.some(need => + resource.type === need.category || + this.isCompatibleResourceType(resource.type, need.category) + ) + ) + + // Add volunteer matches + const volunteerCandidates = this.mockVolunteers.filter(volunteer => + volunteer.skills.some(skill => + analysis.primaryNeeds.some(need => + skill.includes(need.category) || need.category.includes(skill) + ) + ) + ) + + return candidates.map(resource => ({ + ...resource, + volunteers: volunteerCandidates.filter(v => + v.skills.some(skill => skill.includes(resource.type)) + ) + })) + } + + private isCompatibleResourceType(resourceType: string, needCategory: string): boolean { + const compatibility: Record = { + 'clothing': ['clothing'], + 'supplies': ['school-supplies'], + 'food': ['food-assistance'], + 'transport': ['transportation'], + 'technology': ['technology'], + 'emergency': ['emergency-housing', 'medical-needs'] + } + + return compatibility[resourceType]?.includes(needCategory) || false + } + + private async aiScoreMatches(candidates: any[], analysis: RequestAnalysis, request: StudentRequest): Promise { + if (!this.matchingModel) return this.ruleBasedScoring(candidates, analysis, request) + + const matches: MatchResult[] = [] + + for (const candidate of candidates) { + try { + // Prepare features for ML model + const features = await this.prepareFeaturesForML(candidate, analysis, request) + const featureTensor = tf.tensor2d([features]) + + // Get confidence score from AI model + const prediction = this.matchingModel.predict(featureTensor) as tf.Tensor + const confidenceData = await prediction.data() + const confidenceScore = confidenceData[0] + + // Clean up tensors + featureTensor.dispose() + prediction.dispose() + + // Select best volunteer + const bestVolunteer = candidate.volunteers.length > 0 + ? candidate.volunteers.sort((a: any, b: any) => b.rating - a.rating)[0] + : null + + matches.push({ + resourceId: candidate.id, + resourceName: candidate.name, + resourceType: candidate.type, + confidenceScore, + estimatedImpact: this.estimateImpact(analysis, candidate), + logisticalComplexity: analysis.complexityEstimate, + volunteerMatch: bestVolunteer ? { + id: bestVolunteer.id, + volunteerId: bestVolunteer.id, + volunteerName: bestVolunteer.name, + skills: bestVolunteer.skills, + availability: [], // Would be populated from real data + location: bestVolunteer.location, + rating: bestVolunteer.rating, + completedAssignments: Math.floor(Math.random() * 50) + 10 + } : undefined, + estimatedCost: candidate.avgCost, + fulfillmentTimeline: this.estimateTimeline(analysis.urgencyScore, analysis.complexityEstimate), + reasoningFactors: this.generateReasoningFactors(candidate, analysis), + riskFactors: this.identifyRiskFactors(candidate, analysis) + }) + + } catch (error) { + console.error('Error in AI scoring:', error) + // Fallback to rule-based for this candidate + const ruleBasedScore = this.calculateRuleBasedScore(candidate, analysis, request) + matches.push(ruleBasedScore) + } + } + + return matches + } + + private async prepareFeaturesForML(candidate: any, analysis: RequestAnalysis, request: StudentRequest): Promise { + // Create feature vector for ML model (110 features to match model input) + const features = new Array(110).fill(0) + + // Request analysis features (first 100 from text vectorization) + const textVector = await this.vectorizer.encode(request.description) + for (let i = 0; i < Math.min(textVector.length, 100); i++) { + features[i] = textVector[i] + } + + // Resource matching features + features[100] = analysis.urgencyScore + features[101] = analysis.complexityEstimate + features[102] = analysis.estimatedBudget / 1000 // Normalize + features[103] = candidate.capacity / 100 // Normalize + features[104] = candidate.avgCost / 200 // Normalize + features[105] = candidate.volunteers.length / 10 // Normalize + features[106] = candidate.volunteers.length > 0 ? Math.max(...candidate.volunteers.map((v: any) => v.rating)) / 5 : 0 + features[107] = analysis.primaryNeeds.length / 5 // Normalize + features[108] = request.constraints.maxBudget ? (candidate.avgCost / request.constraints.maxBudget) : 0.5 + features[109] = this.getTimeframeScore(request.constraints.timeframe) + + return features + } + + private getTimeframeScore(timeframe: string): number { + switch (timeframe) { + case 'immediate': return 1.0 + case 'within-week': return 0.7 + case 'within-month': return 0.4 + case 'flexible': return 0.2 + default: return 0.5 + } + } + + private async ruleBasedScoring(candidates: any[], analysis: RequestAnalysis, request: StudentRequest): Promise { + return candidates.map(candidate => this.calculateRuleBasedScore(candidate, analysis, request)) + } + + private calculateRuleBasedScore(candidate: any, analysis: RequestAnalysis, request: StudentRequest): MatchResult { + let score = 0.5 // Base score + + // Category match bonus + const categoryMatch = analysis.primaryNeeds.some(need => + candidate.type === need.category || this.isCompatibleResourceType(candidate.type, need.category) + ) + if (categoryMatch) score += 0.3 + + // Capacity and cost considerations + if (candidate.capacity > 10) score += 0.1 + if (candidate.avgCost <= analysis.estimatedBudget) score += 0.2 + + // Volunteer availability bonus + if (candidate.volunteers?.length > 0) { + score += 0.15 + const avgRating = candidate.volunteers.reduce((sum: number, v: any) => sum + v.rating, 0) / candidate.volunteers.length + score += (avgRating / 5) * 0.1 + } + + // Urgency adjustment + score += analysis.urgencyScore * 0.1 + + // Complexity penalty + score -= analysis.complexityEstimate * 0.05 + + const bestVolunteer = candidate.volunteers?.length > 0 + ? candidate.volunteers.sort((a: any, b: any) => b.rating - a.rating)[0] + : null + + return { + resourceId: candidate.id, + resourceName: candidate.name, + resourceType: candidate.type, + confidenceScore: Math.min(Math.max(score, 0), 1), + estimatedImpact: this.estimateImpact(analysis, candidate), + logisticalComplexity: analysis.complexityEstimate, + volunteerMatch: bestVolunteer ? { + id: bestVolunteer.id, + volunteerId: bestVolunteer.id, + volunteerName: bestVolunteer.name, + skills: bestVolunteer.skills, + availability: [], + location: bestVolunteer.location, + rating: bestVolunteer.rating, + completedAssignments: Math.floor(Math.random() * 50) + 10 + } : undefined, + estimatedCost: candidate.avgCost, + fulfillmentTimeline: this.estimateTimeline(analysis.urgencyScore, analysis.complexityEstimate), + reasoningFactors: this.generateReasoningFactors(candidate, analysis), + riskFactors: this.identifyRiskFactors(candidate, analysis, request) + } + } + + private estimateImpact(analysis: RequestAnalysis, candidate: any): number { + let impact = 0.6 // Base impact + + // Higher impact for urgent needs + impact += analysis.urgencyScore * 0.2 + + // Higher impact for critical categories + const criticalCategories = ['emergency-housing', 'food-assistance', 'medical-needs'] + if (analysis.primaryNeeds.some(need => criticalCategories.includes(need.category))) { + impact += 0.15 + } + + // Volunteer quality impact + if (candidate.volunteers?.length > 0) { + const avgRating = candidate.volunteers.reduce((sum: number, v: any) => sum + v.rating, 0) / candidate.volunteers.length + impact += (avgRating / 5) * 0.1 + } + + return Math.min(impact, 1.0) + } + + private estimateTimeline(urgencyScore: number, complexityScore: number): string { + const baseHours = 24 + (complexityScore * 48) - (urgencyScore * 12) + + if (baseHours <= 2) return 'Within 2 hours' + if (baseHours <= 24) return 'Same day' + if (baseHours <= 48) return '1-2 days' + if (baseHours <= 168) return '3-7 days' + return '1-2 weeks' + } + + private generateReasoningFactors(candidate: any, analysis: RequestAnalysis): string[] { + const factors: string[] = [] + + factors.push(`Resource specializes in ${candidate.type} assistance`) + + if (candidate.capacity > 50) factors.push('High capacity resource available') + if (candidate.avgCost <= analysis.estimatedBudget) factors.push('Cost within estimated budget') + + if (candidate.volunteers?.length > 0) { + const avgRating = candidate.volunteers.reduce((sum: number, v: any) => sum + v.rating, 0) / candidate.volunteers.length + factors.push(`${candidate.volunteers.length} volunteers available (avg rating: ${avgRating.toFixed(1)})`) + } + + if (analysis.urgencyScore > 0.7) factors.push('High urgency request prioritized') + + return factors + } + + private identifyRiskFactors(candidate: any, analysis: RequestAnalysis): string[] { + const risks: string[] = [] + + if (candidate.capacity < 5) risks.push('Limited resource capacity') + if (candidate.avgCost > analysis.estimatedBudget * 1.2) risks.push('Cost exceeds estimated budget') + if (candidate.volunteers?.length === 0) risks.push('No volunteers currently available') + if (analysis.complexityEstimate > 0.7) risks.push('High complexity request') + + return risks + } + + private async enrichWithPredictions(matches: MatchResult[]): Promise { + // For each match, predict impact using AI model if available + for (const match of matches) { + try { + if (this.impactModel && this.isInitialized) { + const impactFeatures = this.prepareImpactFeatures(match) + const impactTensor = tf.tensor2d([impactFeatures]) + + const impactPrediction = this.impactModel.predict(impactTensor) as tf.Tensor + const impactData = await impactPrediction.data() + + // Update match with AI predictions + match.estimatedImpact = Math.min(Math.max(impactData[1], 0), 1) // success probability + + // Clean up tensors + impactTensor.dispose() + impactPrediction.dispose() + } + } catch (error) { + console.error('Error in impact prediction:', error) + // Keep existing impact estimate + } + } + + return matches + } + + private prepareImpactFeatures(match: MatchResult): number[] { + // Create 20-feature vector for impact prediction + return [ + match.confidenceScore, + match.estimatedCost / 200, // Normalize + match.logisticalComplexity, + match.volunteerMatch ? match.volunteerMatch.rating / 5 : 0, + match.volunteerMatch ? match.volunteerMatch.completedAssignments / 50 : 0, + match.resourceType === 'emergency' ? 1 : 0, + match.resourceType === 'food' ? 1 : 0, + match.resourceType === 'clothing' ? 1 : 0, + match.resourceType === 'supplies' ? 1 : 0, + match.resourceType === 'transport' ? 1 : 0, + match.reasoningFactors.length / 5, + match.riskFactors.length / 5, + Math.random(), // Random noise for variation + Math.random(), + Math.random(), + Math.random(), + Math.random(), + Math.random(), + Math.random(), + Math.random() + ] + } + + private generateFallbackMatches(): MatchResult[] { + // Emergency fallback when AI fails + return [{ + resourceId: 'fallback-1', + resourceName: 'General Assistance Fund', + resourceType: 'other', + confidenceScore: 0.5, + estimatedImpact: 0.6, + logisticalComplexity: 0.5, + estimatedCost: 75, + fulfillmentTimeline: '1-2 days', + reasoningFactors: ['Fallback resource for emergency situations'], + riskFactors: ['System processing temporarily unavailable'] + }] + } + + async generateInsights(requests: StudentRequest[]): Promise { + const insights: AIInsight[] = [] + + // Pattern analysis + const categoryDistribution = this.analyzeCategoryDistribution(requests) + const urgencyTrends = this.analyzeUrgencyTrends(requests) + + // Generate insights based on patterns + if (categoryDistribution.clothing > 0.4) { + insights.push({ + id: `insight-${Date.now()}-1`, + type: 'trend', + title: 'High Clothing Demand Detected', + description: `${Math.round(categoryDistribution.clothing * 100)}% of recent requests are for clothing assistance. Consider expanding clothing inventory.`, + confidence: 0.85, + severity: 'medium', + timestamp: new Date(), + actionItems: ['Increase clothing fund allocation', 'Recruit clothing-focused volunteers', 'Partner with local clothing stores'] + }) + } + + if (urgencyTrends.emergencyRate > 0.3) { + insights.push({ + id: `insight-${Date.now()}-2`, + type: 'anomaly', + title: 'Increased Emergency Requests', + description: `${Math.round(urgencyTrends.emergencyRate * 100)}% of requests are marked as emergency. This is above normal baseline.`, + confidence: 0.9, + severity: 'high', + timestamp: new Date(), + actionItems: ['Review emergency response protocols', 'Ensure emergency fund availability', 'Alert senior coordinators'] + }) + } + + return insights + } + + private analyzeCategoryDistribution(requests: StudentRequest[]) { + const total = requests.length + if (total === 0) return {} + + const counts: Record = {} + requests.forEach(req => { + counts[req.category] = (counts[req.category] || 0) + 1 + }) + + const distribution: Record = {} + Object.entries(counts).forEach(([category, count]) => { + distribution[category] = count / total + }) + + return distribution + } + + private analyzeUrgencyTrends(requests: StudentRequest[]) { + const total = requests.length + if (total === 0) return { emergencyRate: 0 } + + const emergencyCount = requests.filter(req => req.urgency === 'emergency').length + return { + emergencyRate: emergencyCount / total + } + } + + async learnFromFeedback(feedback: LearningFeedback): Promise { + console.log(`📚 Learning from feedback for request ${feedback.requestId}`) + + // In production, this would update training data and trigger model retraining + // For now, we'll log the feedback for analysis + + const learningData = { + requestId: feedback.requestId, + outcome: feedback.outcome, + accuracyScore: feedback.outcome === 'successful' ? 1 : 0, + costAccuracy: Math.abs(feedback.actualCost - (feedback.actualCost)) / feedback.actualCost, + timeAccuracy: feedback.actualTimeToComplete, + satisfaction: feedback.satisfactionScore, + timestamp: new Date() + } + + // Store for batch processing and model improvement + console.log('Learning data recorded:', learningData) + } +} + +// Export the AI engine +export const aiEngine = new StudentAssistanceAI() \ No newline at end of file diff --git a/src/ai/types.ts b/src/ai/types.ts new file mode 100644 index 0000000..35aaaaa --- /dev/null +++ b/src/ai/types.ts @@ -0,0 +1,191 @@ +// Phase 3: AI Types and Interfaces + +export interface StudentRequest { + id: string + studentId: string + studentName: string + description: string + category: AssistanceCategory + urgency: UrgencyLevel + location: GeographicLocation + constraints: RequestConstraints + deadline?: Date + submittedAt: Date + estimatedCost?: number + requiredSkills?: string[] +} + +export interface MatchResult { + resourceId: string + resourceName: string + resourceType: 'clothing' | 'supplies' | 'food' | 'transport' | 'emergency' | 'other' + confidenceScore: number + estimatedImpact: number + logisticalComplexity: number + volunteerMatch?: VolunteerAssignment + estimatedCost: number + fulfillmentTimeline: string + reasoningFactors: string[] + riskFactors: string[] +} + +export interface RequestAnalysis { + primaryNeeds: NeedCategory[] + urgencyScore: number + complexityEstimate: number + resourceRequirements: ResourceRequirement[] + locationConstraints?: GeographicConstraint[] + timeConstraints?: TemporalConstraint[] + requiredSkills?: string[] + estimatedBudget: number +} + +export interface AIInsight { + id: string + type: 'anomaly' | 'optimization' | 'trend' | 'prediction' | 'recommendation' + title: string + description: string + confidence: number + severity?: 'low' | 'medium' | 'high' | 'critical' + timestamp: Date + actionItems?: string[] + estimatedImpact?: string + data?: Record +} + +export interface AIMetrics { + accuracyRate: number + accuracyTrend: number + avgProcessingTime: number + speedTrend: number + autoApprovalRate: number + automationTrend: number + impactPredictionAccuracy: number + impactTrend: number + totalRequestsProcessed: number + successfulMatches: number +} + +export interface AIUpdate { + type: 'request-processed' | 'new-insight' | 'auto-approval' | 'model-updated' | 'alert' + requestId?: string + studentName?: string + status?: string + recommendations?: MatchResult[] + insight?: AIInsight + message?: string + timestamp: Date +} + +export type AssistanceCategory = + | 'clothing' + | 'school-supplies' + | 'food-assistance' + | 'transportation' + | 'emergency-housing' + | 'medical-needs' + | 'technology' + | 'extracurricular' + | 'other' + +export type UrgencyLevel = 'low' | 'medium' | 'high' | 'emergency' + +export interface GeographicLocation { + latitude?: number + longitude?: number + address?: string + city: string + state: string + zipCode: string + schoolDistrict?: string +} + +export interface RequestConstraints { + maxBudget?: number + timeframe: 'immediate' | 'within-week' | 'within-month' | 'flexible' + deliveryMethod: 'pickup' | 'delivery' | 'mail' | 'school-delivery' | 'any' + privacyLevel: 'anonymous' | 'semi-anonymous' | 'open' + specialRequirements?: string[] +} + +export interface VolunteerAssignment { + id: string + volunteerId: string + volunteerName: string + skills: string[] + availability: Date[] + location: GeographicLocation + rating: number + completedAssignments: number +} + +export interface NeedCategory { + category: AssistanceCategory + subcategory?: string + priority: number + quantity?: number + specifications?: Record +} + +export interface ResourceRequirement { + type: string + quantity: number + specifications: Record + alternatives?: string[] + estimatedCost: number +} + +export interface GeographicConstraint { + maxDistance: number + preferredAreas?: string[] + excludedAreas?: string[] +} + +export interface TemporalConstraint { + earliestStart: Date + latestCompletion: Date + preferredTimes?: string[] + blackoutPeriods?: DateRange[] +} + +export interface DateRange { + start: Date + end: Date +} + +export interface ImpactPrediction { + estimatedBeneficiaries: number + successProbability: number + timeToImpact: number + sustainabilityScore: number + rippleEffects: RippleEffect[] + measurableOutcomes: string[] +} + +export interface RippleEffect { + type: 'family' | 'community' | 'academic' | 'social' | 'economic' + description: string + estimatedBeneficiaries: number + confidenceLevel: number +} + +export interface LearningFeedback { + requestId: string + matchId: string + outcome: 'successful' | 'partial' | 'failed' + actualCost: number + actualTimeToComplete: number + satisfactionScore: number + issues?: string[] + improvements?: string[] + measuredImpact?: Record +} + +export interface ProcessingPipelineConfig { + autoApprovalThreshold: number + urgencyWeights: Record + categoryWeights: Record + maxProcessingTime: number + retryAttempts: number + notificationEnabled: boolean +} \ No newline at end of file diff --git a/src/components/AIAssistancePortal.tsx b/src/components/AIAssistancePortal.tsx new file mode 100644 index 0000000..57a07da --- /dev/null +++ b/src/components/AIAssistancePortal.tsx @@ -0,0 +1,721 @@ +// Phase 3: AI Assistance Portal React Components +import React, { useState, useEffect } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { formatDistanceToNow } from 'date-fns' +import type { + StudentRequest, + MatchResult, + AIInsight, + AIMetrics, + AIUpdate, + UrgencyLevel, + AssistanceCategory +} from '../ai/types' +import { processingPipeline } from '../ai/ProcessingPipeline' + +// Icons (using the existing icon system) +const Brain = ({ className = "w-5 h-5" }: { className?: string }) => ( + + + +) + +const Cpu = ({ className = "w-4 h-4" }: { className?: string }) => ( + + + + + +) + +const Activity = ({ className = "w-4 h-4" }: { className?: string }) => ( + + + +) + +const TrendingUp = ({ className = "w-8 h-8" }: { className?: string }) => ( + + + + +) + +const Users = ({ className = "w-8 h-8" }: { className?: string }) => ( + + + + + +) + +// Mock data for demonstration +const mockRequests: StudentRequest[] = [ + { + id: 'req-1', + studentId: 'std-1', + studentName: 'Maria Rodriguez', + description: 'Need winter coat and boots for my daughter. Size 8 shoes and medium coat. Getting cold and she only has summer clothes.', + category: 'clothing', + urgency: 'high', + location: { city: 'Austin', state: 'TX', zipCode: '78701' }, + constraints: { timeframe: 'within-week', deliveryMethod: 'school-delivery', privacyLevel: 'semi-anonymous' }, + submittedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago + }, + { + id: 'req-2', + studentId: 'std-2', + studentName: 'James Thompson', + description: 'My son needs school supplies - notebooks, pencils, calculator for math class. Starting new semester next week.', + category: 'school-supplies', + urgency: 'medium', + location: { city: 'Round Rock', state: 'TX', zipCode: '78664' }, + constraints: { timeframe: 'within-week', deliveryMethod: 'pickup', privacyLevel: 'open' }, + submittedAt: new Date(Date.now() - 45 * 60 * 1000), // 45 minutes ago + }, + { + id: 'req-3', + studentId: 'std-3', + studentName: 'Sarah Kim', + description: 'Emergency - no food at home for kids this weekend. Need groceries or meal assistance ASAP.', + category: 'food-assistance', + urgency: 'emergency', + location: { city: 'Cedar Park', state: 'TX', zipCode: '78613' }, + constraints: { timeframe: 'immediate', deliveryMethod: 'delivery', privacyLevel: 'anonymous' }, + submittedAt: new Date(Date.now() - 20 * 60 * 1000), // 20 minutes ago + } +] + +interface AIAssistancePortalProps { + userRole: 'student' | 'coordinator' | 'admin' +} + +export function AIAssistancePortal({ userRole }: AIAssistancePortalProps) { + const [requests, setRequests] = useState(mockRequests) + const [aiInsights, setAIInsights] = useState([]) + const [processing, setProcessing] = useState(false) + const [selectedRequest, setSelectedRequest] = useState(null) + + useEffect(() => { + // Subscribe to real-time AI updates + const unsubscribe = processingPipeline.subscribe(handleRealTimeUpdate) + + // Generate initial insights + generateInsights() + + return unsubscribe + }, []) + + const handleRealTimeUpdate = (update: AIUpdate) => { + console.log('🔄 Real-time update received:', update) + + switch (update.type) { + case 'request-processed': + setRequests(prev => prev.map(r => + r.id === update.requestId + ? { ...r, status: update.status, aiRecommendations: update.recommendations } + : r + )) + break + + case 'new-insight': + if (update.insight) { + setAIInsights(prev => [update.insight!, ...prev.slice(0, 4)]) + } + break + + case 'auto-approval': + // Show success notification + console.log('✅ Auto-approval notification:', update.message) + break + } + } + + const generateInsights = async () => { + try { + const insights = await processingPipeline.generateInsights(requests) + setAIInsights(insights) + } catch (error) { + console.error('Error generating insights:', error) + } + } + + const handleApproval = async (requestId: string, recommendation: MatchResult) => { + setProcessing(true) + try { + console.log(`✅ Approving request ${requestId} with recommendation:`, recommendation) + // In production: make API call to approve + + setRequests(prev => prev.map(r => + r.id === requestId ? { ...r, status: 'approved' } : r + )) + + } catch (error) { + console.error('Error approving request:', error) + } finally { + setProcessing(false) + } + } + + const handleModification = async (requestId: string) => { + console.log(`✏️ Modifying request ${requestId}`) + setSelectedRequest(requestId) + } + + const submitNewRequest = async (request: Omit) => { + setProcessing(true) + try { + const newRequest: StudentRequest = { + ...request, + id: `req-${Date.now()}`, + submittedAt: new Date() + } + + // Submit to AI processing pipeline + await processingPipeline.submitRequest(newRequest) + + setRequests(prev => [newRequest, ...prev]) + + } catch (error) { + console.error('Error submitting request:', error) + } finally { + setProcessing(false) + } + } + + return ( +
+ {/* AI Insights Panel */} + +

+ + AI Insights +

+ + + {aiInsights.map((insight) => ( + +
+
+
+

{insight.title}

+

+ {insight.description} +

+ {insight.confidence && ( +
+
+
+
+ + {Math.round(insight.confidence * 100)}% + +
+ )} +
+
+ + ))} + + + {aiInsights.length === 0 && ( +
+ +

No insights available yet

+
+ )} + + + {/* Request Processing Interface */} +
+
+

+ Smart Request Processing +

+ + + {processing ? 'Processing...' : 'Refresh'} + +
+ +
+ {requests.map((request) => ( + + ))} +
+ + {userRole === 'student' && ( + + )} +
+ + {/* Performance Metrics */} +
+ +
+
+ ) +} + +interface RequestCardProps { + request: StudentRequest & { status?: string; aiRecommendations?: MatchResult[] } + onApprove: (requestId: string, recommendation: MatchResult) => void + onModify: (requestId: string) => void + showAIRecommendations: boolean + isSelected: boolean +} + +function RequestCard({ request, onApprove, onModify, showAIRecommendations, isSelected }: RequestCardProps) { + return ( + +
+
+

+ {request.studentName} +

+

+ {request.description} +

+

+ Submitted {formatDistanceToNow(request.submittedAt)} ago +

+
+
+ + + {request.status && } +
+
+ + {showAIRecommendations && request.aiRecommendations && ( + +
+ + + AI Recommendation + + +
+ +
+ {request.aiRecommendations.slice(0, 2).map((rec, index) => ( +
+
+

+ {rec.resourceName} +

+

+ {rec.reasoningFactors[0]} +

+
+
+ + ${rec.estimatedCost} + + + {rec.fulfillmentTimeline} + +
+
+ ))} +
+ +
+ onApprove(request.id, request.aiRecommendations![0])} + className="btn-primary text-xs px-4 py-2 flex items-center gap-2" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + ✓ Approve AI Recommendation + + +
+
+ )} + +
+
+ +
+ +
+ {!showAIRecommendations && ( + + Processing... + + )} +
+
+
+ ) +} + +function UrgencyBadge({ urgency }: { urgency: UrgencyLevel }) { + const colors = { + emergency: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300', + high: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300', + medium: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300', + low: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300' + } + + return ( + + {urgency.toUpperCase()} + + ) +} + +function CategoryBadge({ category }: { category: AssistanceCategory }) { + return ( + + {category.replace('-', ' ').toUpperCase()} + + ) +} + +function StatusBadge({ status }: { status: string }) { + const colors = { + 'approved': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300', + 'auto-approved': 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300', + 'under-review': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300', + 'pending': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' + } + + return ( + + {status.replace('-', ' ').toUpperCase()} + + ) +} + +function LocationBadge({ location }: { location: string }) { + return ( + + 📍 {location} + + ) +} + +function ConfidenceIndicator({ confidence }: { confidence: number }) { + const getColor = (conf: number) => { + if (conf >= 0.8) return 'text-green-500' + if (conf >= 0.6) return 'text-yellow-500' + return 'text-red-500' + } + + return ( +
+
+
+
+ + {Math.round(confidence * 100)}% + +
+ ) +} + +function AIPerformanceMetrics() { + const [metrics, setMetrics] = useState({ + accuracyRate: 0.87, + accuracyTrend: 2.3, + avgProcessingTime: 1.8, + speedTrend: -0.5, + autoApprovalRate: 0.68, + automationTrend: 5.2, + impactPredictionAccuracy: 0.82, + impactTrend: 1.8, + totalRequestsProcessed: 247, + successfulMatches: 213 + }) + + return ( +
+

+ + AI Performance Dashboard +

+ +
+ } + /> + } + /> + } + /> + } + /> +
+ +
+
+ Total Requests Processed + {metrics.totalRequestsProcessed} +
+
+ Successful Matches + {metrics.successfulMatches} +
+
+
+ ) +} + +interface MetricCardProps { + title: string + value: string + trend: number + color: 'green' | 'blue' | 'purple' | 'orange' + icon: React.ReactNode +} + +function MetricCard({ title, value, trend, color, icon }: MetricCardProps) { + const colorClasses = { + green: 'bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800', + blue: 'bg-blue-50 border-blue-200 dark:bg-blue-900/20 dark:border-blue-800', + purple: 'bg-purple-50 border-purple-200 dark:bg-purple-900/20 dark:border-purple-800', + orange: 'bg-orange-50 border-orange-200 dark:bg-orange-900/20 dark:border-orange-800' + } + + const iconColors = { + green: 'text-green-500', + blue: 'text-blue-500', + purple: 'text-purple-500', + orange: 'text-orange-500' + } + + return ( + +
+
{icon}
+
0 + ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300' + : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300' + }`}> + {trend > 0 ? '↗' : '↘'} {Math.abs(trend)}% +
+
+
+

{value}

+

{title}

+
+
+ ) +} + +function NewRequestForm({ onSubmit }: { onSubmit: (request: Omit) => void }) { + const [formData, setFormData] = useState({ + studentName: '', + description: '', + category: 'clothing' as AssistanceCategory, + urgency: 'medium' as UrgencyLevel, + city: '', + state: 'TX', + zipCode: '' + }) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + + onSubmit({ + studentId: `std-${Date.now()}`, + studentName: formData.studentName, + description: formData.description, + category: formData.category, + urgency: formData.urgency, + location: { + city: formData.city, + state: formData.state, + zipCode: formData.zipCode + }, + constraints: { + timeframe: 'within-week', + deliveryMethod: 'any', + privacyLevel: 'semi-anonymous' + } + }) + + // Reset form + setFormData({ + studentName: '', + description: '', + category: 'clothing', + urgency: 'medium', + city: '', + state: 'TX', + zipCode: '' + }) + } + + return ( + +

Submit New Request

+ +
+
+ setFormData(prev => ({ ...prev, studentName: e.target.value }))} + className="input-field" + required + /> + + +
+ +