feat: Implement AI Assistance Portal with types and components for student requests and insights
This commit is contained in:
683
PHASE3_AI_IMPLEMENTATION.md
Normal file
683
PHASE3_AI_IMPLEMENTATION.md
Normal file
@@ -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<MatchResult[]> {
|
||||
// 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<RequestAnalysis> {
|
||||
// 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<ResourceCandidate[]> {
|
||||
// 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<ScoredMatch[]> {
|
||||
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<ImpactPrediction> {
|
||||
// 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<StudentRequest>
|
||||
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<string> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
// 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<ImprovementInsight[]> {
|
||||
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<void> {
|
||||
// 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<StudentRequest[]>([])
|
||||
const [aiInsights, setAIInsights] = useState<AIInsight[]>([])
|
||||
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 (
|
||||
<div className="ai-assistance-portal">
|
||||
{/* AI Insights Panel */}
|
||||
<motion.div
|
||||
className="insights-panel"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Brain className="w-5 h-5 text-purple-500" />
|
||||
AI Insights
|
||||
</h3>
|
||||
|
||||
<AnimatePresence mode="popLayout">
|
||||
{aiInsights.map((insight) => (
|
||||
<motion.div
|
||||
key={insight.id}
|
||||
className="insight-card p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg mb-2"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<div className={`w-2 h-2 rounded-full mt-2 ${getInsightColor(insight.type)}`} />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm">{insight.title}</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
{insight.description}
|
||||
</p>
|
||||
{insight.confidence && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<div className="w-20 bg-gray-200 rounded-full h-1">
|
||||
<div
|
||||
className="bg-purple-500 h-1 rounded-full transition-all"
|
||||
style={{ width: `${insight.confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs font-medium">
|
||||
{Math.round(insight.confidence * 100)}% confidence
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
|
||||
{/* Request Processing Interface */}
|
||||
<div className="request-processing">
|
||||
<h3 className="text-lg font-semibold mb-4">Smart Request Processing</h3>
|
||||
|
||||
{requests.map((request) => (
|
||||
<RequestCard
|
||||
key={request.id}
|
||||
request={request}
|
||||
onApprove={handleApproval}
|
||||
onModify={handleModification}
|
||||
showAIRecommendations={userRole !== 'student'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Performance Metrics */}
|
||||
<AIPerformanceMetrics />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RequestCard({ request, onApprove, onModify, showAIRecommendations }: RequestCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className="request-card p-4 border rounded-lg mb-4"
|
||||
whileHover={{ y: -2, boxShadow: "0 4px 12px rgba(0,0,0,0.1)" }}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<h4 className="font-medium">{request.description}</h4>
|
||||
<p className="text-sm text-gray-500">
|
||||
Student: {request.studentName} • {formatDistanceToNow(request.submittedAt)} ago
|
||||
</p>
|
||||
</div>
|
||||
<UrgencyBadge urgency={request.urgency} />
|
||||
</div>
|
||||
|
||||
{showAIRecommendations && request.aiRecommendations && (
|
||||
<motion.div
|
||||
className="ai-recommendations bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg mb-3"
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Cpu className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
AI Recommendation
|
||||
</span>
|
||||
<ConfidenceIndicator confidence={request.aiRecommendations[0].confidenceScore} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{request.aiRecommendations.slice(0, 2).map((rec, index) => (
|
||||
<div key={index} className="flex justify-between items-center text-sm">
|
||||
<span>{rec.resourceName}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-green-600">${rec.estimatedCost}</span>
|
||||
<span className="text-blue-600">{rec.fulfillmentTimeline}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex gap-2">
|
||||
<motion.button
|
||||
onClick={() => 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
|
||||
</motion.button>
|
||||
<button
|
||||
onClick={() => onModify(request.id)}
|
||||
className="btn-secondary text-xs px-3 py-1"
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2">
|
||||
<CategoryBadge category={request.category} />
|
||||
<LocationBadge location={request.location} />
|
||||
</div>
|
||||
|
||||
<ActionButtons request={request} />
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function AIPerformanceMetrics() {
|
||||
const [metrics, setMetrics] = useState<AIMetrics>()
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch AI performance metrics
|
||||
fetchAIMetrics().then(setMetrics)
|
||||
}, [])
|
||||
|
||||
if (!metrics) return null
|
||||
|
||||
return (
|
||||
<div className="ai-performance-metrics mt-6">
|
||||
<h4 className="text-md font-semibold mb-3">AI Performance</h4>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<MetricCard
|
||||
title="Accuracy Rate"
|
||||
value={`${(metrics.accuracyRate * 100).toFixed(1)}%`}
|
||||
trend={metrics.accuracyTrend}
|
||||
color="green"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Avg Processing Time"
|
||||
value={`${metrics.avgProcessingTime}s`}
|
||||
trend={metrics.speedTrend}
|
||||
color="blue"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Auto-Approval Rate"
|
||||
value={`${(metrics.autoApprovalRate * 100).toFixed(1)}%`}
|
||||
trend={metrics.automationTrend}
|
||||
color="purple"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Impact Accuracy"
|
||||
value={`${(metrics.impactPredictionAccuracy * 100).toFixed(1)}%`}
|
||||
trend={metrics.impactTrend}
|
||||
color="orange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 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.
|
||||
668
PHASE3_ARCHITECTURE.md
Normal file
668
PHASE3_ARCHITECTURE.md
Normal file
@@ -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<MatchResult[]>
|
||||
predictNeeds(studentProfile: StudentProfile): Promise<PredictionResult>
|
||||
optimizeResources(availableResources: Resource[]): Promise<OptimizationPlan>
|
||||
}
|
||||
|
||||
class StudentAssistanceAI {
|
||||
private mlModel: TensorFlow.LayersModel
|
||||
private vectorizer: TextVectorizer
|
||||
|
||||
async matchStudent(request: StudentRequest): Promise<MatchResult[]> {
|
||||
// 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<ImpactPrediction> {
|
||||
// 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<OptimalTiming> {
|
||||
// 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<PersonalizedContent> {
|
||||
// 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<ProcessingResult> {
|
||||
// 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<DonationResult> {
|
||||
// 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<OptimizationPlan> {
|
||||
// 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<SalesforceContact> {
|
||||
// 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<Opportunity> {
|
||||
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<QBTransaction> {
|
||||
// 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<ReconciliationReport> {
|
||||
// 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<LiveMetrics> {
|
||||
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<AIInsight[]> {
|
||||
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<DonationForecast> {
|
||||
// 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<VolunteerForecast> {
|
||||
// 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<Organization> {
|
||||
// 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<ScalingPlan> {
|
||||
// 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<DataResponse> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
// 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<SyncResult> {
|
||||
// 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<void> {
|
||||
// Offline-capable assignment acceptance
|
||||
await this.queueAction('accept_assignment', { assignmentId })
|
||||
await this.updateLocalState(assignmentId, 'accepted')
|
||||
await this.notifyCoordinator(assignmentId)
|
||||
}
|
||||
|
||||
async scanDeliveryReceipt(imageUri: string): Promise<ProcessedReceipt> {
|
||||
// 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<void> {
|
||||
// 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.
|
||||
2174
package-lock.json
generated
2174
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1185
src/App.tsx
1185
src/App.tsx
File diff suppressed because it is too large
Load Diff
418
src/ai/ProcessingPipeline.ts
Normal file
418
src/ai/ProcessingPipeline.ts
Normal file
@@ -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<T> {
|
||||
private jobs: Array<{ id: string; data: T; priority: number }> = []
|
||||
private processors: Map<string, (job: { id: string; data: T }) => Promise<void>> = 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>): void {
|
||||
this.processors.set(jobType, processor)
|
||||
}
|
||||
|
||||
private async processNextJob(jobType: string): Promise<void> {
|
||||
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<void> {
|
||||
console.log(`📧 Notifying student ${studentId} about assignment`)
|
||||
// In production: send email, SMS, or push notification
|
||||
}
|
||||
|
||||
async notifyVolunteer(volunteerId: string, assignment: any): Promise<void> {
|
||||
console.log(`📧 Notifying volunteer ${volunteerId} about new assignment`)
|
||||
// In production: send volunteer notification
|
||||
}
|
||||
|
||||
async notifyCoordinators(assignment: any): Promise<void> {
|
||||
console.log(`📧 Notifying coordinators about new assignment`)
|
||||
// In production: alert coordination team
|
||||
}
|
||||
|
||||
async updateDonors(estimatedCost: number): Promise<void> {
|
||||
console.log(`💰 Updating donors about $${estimatedCost} impact opportunity`)
|
||||
// In production: trigger donor engagement campaign
|
||||
}
|
||||
|
||||
async notifyReviewer(reviewer: any, reviewTask: any, aiInsights: any): Promise<void> {
|
||||
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<string, any> = new Map()
|
||||
|
||||
static async createAssignment(assignmentData: any): Promise<any> {
|
||||
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<any | null> {
|
||||
return this.assignments.get(id) || null
|
||||
}
|
||||
}
|
||||
|
||||
class ReviewManager {
|
||||
private static reviewTasks: Map<string, any> = new Map()
|
||||
|
||||
static async createReviewTask(taskData: any): Promise<any> {
|
||||
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<StudentRequest>
|
||||
private notificationService: NotificationService
|
||||
private config: ProcessingPipelineConfig
|
||||
|
||||
constructor(config: Partial<ProcessingPipelineConfig> = {}) {
|
||||
this.queue = new MockQueue<StudentRequest>()
|
||||
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<string> {
|
||||
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<void> {
|
||||
console.log(`✉️ Sending acknowledgment to ${request.studentName}`)
|
||||
// In production: send immediate confirmation email/SMS
|
||||
}
|
||||
|
||||
private async autoApproveRequest(request: StudentRequest, match: MatchResult): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
console.log(`📊 Updating dashboard for request ${requestId}`)
|
||||
// In production: update real-time analytics dashboard
|
||||
}
|
||||
|
||||
private async handleProcessingError(request: StudentRequest, error: Error): Promise<void> {
|
||||
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<void> {
|
||||
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<AIInsight[]> {
|
||||
return await aiEngine.generateInsights(requests)
|
||||
}
|
||||
|
||||
getConfig(): ProcessingPipelineConfig {
|
||||
return { ...this.config }
|
||||
}
|
||||
|
||||
updateConfig(newConfig: Partial<ProcessingPipelineConfig>): 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 }
|
||||
803
src/ai/StudentAssistanceAI.ts
Normal file
803
src/ai/StudentAssistanceAI.ts
Normal file
@@ -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<string, number> = 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<number[]> {
|
||||
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<string, number>()
|
||||
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<MatchResult[]> {
|
||||
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<RequestAnalysis> {
|
||||
// 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<string, number> = {
|
||||
'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<string, string[]> = {
|
||||
'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<MatchResult[]> {
|
||||
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<number[]> {
|
||||
// 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<MatchResult[]> {
|
||||
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<MatchResult[]> {
|
||||
// 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<AIInsight[]> {
|
||||
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<string, number> = {}
|
||||
requests.forEach(req => {
|
||||
counts[req.category] = (counts[req.category] || 0) + 1
|
||||
})
|
||||
|
||||
const distribution: Record<string, number> = {}
|
||||
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<void> {
|
||||
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()
|
||||
191
src/ai/types.ts
Normal file
191
src/ai/types.ts
Normal file
@@ -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<string, any>
|
||||
}
|
||||
|
||||
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<string, any>
|
||||
}
|
||||
|
||||
export interface ResourceRequirement {
|
||||
type: string
|
||||
quantity: number
|
||||
specifications: Record<string, any>
|
||||
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<string, number>
|
||||
}
|
||||
|
||||
export interface ProcessingPipelineConfig {
|
||||
autoApprovalThreshold: number
|
||||
urgencyWeights: Record<UrgencyLevel, number>
|
||||
categoryWeights: Record<AssistanceCategory, number>
|
||||
maxProcessingTime: number
|
||||
retryAttempts: number
|
||||
notificationEnabled: boolean
|
||||
}
|
||||
721
src/components/AIAssistancePortal.tsx
Normal file
721
src/components/AIAssistancePortal.tsx
Normal file
@@ -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 }) => (
|
||||
<svg className={className} fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M17.28 9.28a.75.75 0 00-1.06-1.06l-7.5 7.5a.75.75 0 101.06 1.06l7.5-7.5z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const Cpu = ({ className = "w-4 h-4" }: { className?: string }) => (
|
||||
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||
<rect x="9" y="9" width="6" height="6" />
|
||||
<path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const Activity = ({ className = "w-4 h-4" }: { className?: string }) => (
|
||||
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<polyline points="22,12 18,12 15,21 9,3 6,12 2,12" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const TrendingUp = ({ className = "w-8 h-8" }: { className?: string }) => (
|
||||
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
|
||||
<polyline points="17 6 23 6 23 12" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const Users = ({ className = "w-8 h-8" }: { className?: string }) => (
|
||||
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
// 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<StudentRequest[]>(mockRequests)
|
||||
const [aiInsights, setAIInsights] = useState<AIInsight[]>([])
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const [selectedRequest, setSelectedRequest] = useState<string | null>(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<StudentRequest, 'id' | 'submittedAt'>) => {
|
||||
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 (
|
||||
<div className="ai-assistance-portal grid grid-cols-1 lg:grid-cols-3 gap-8 p-6">
|
||||
{/* AI Insights Panel */}
|
||||
<motion.div
|
||||
className="insights-panel bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 p-6"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-gray-900 dark:text-gray-100">
|
||||
<Brain className="w-5 h-5 text-purple-500" />
|
||||
AI Insights
|
||||
</h3>
|
||||
|
||||
<AnimatePresence mode="popLayout">
|
||||
{aiInsights.map((insight) => (
|
||||
<motion.div
|
||||
key={insight.id}
|
||||
className="insight-card p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg mb-3 border border-purple-100 dark:border-purple-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`w-2 h-2 rounded-full mt-2 ${getInsightColor(insight.type)}`} />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm text-gray-900 dark:text-gray-100">{insight.title}</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
|
||||
{insight.description}
|
||||
</p>
|
||||
{insight.confidence && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<div className="w-16 bg-gray-200 dark:bg-gray-600 rounded-full h-1">
|
||||
<div
|
||||
className="bg-purple-500 h-1 rounded-full transition-all duration-300"
|
||||
style={{ width: `${insight.confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{Math.round(insight.confidence * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
|
||||
{aiInsights.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<Brain className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p className="text-sm">No insights available yet</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Request Processing Interface */}
|
||||
<div className="request-processing lg:col-span-2">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
Smart Request Processing
|
||||
</h3>
|
||||
<motion.button
|
||||
onClick={generateInsights}
|
||||
className="btn-secondary flex items-center gap-2 text-sm"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
disabled={processing}
|
||||
>
|
||||
<Activity className="w-4 h-4" />
|
||||
{processing ? 'Processing...' : 'Refresh'}
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{requests.map((request) => (
|
||||
<RequestCard
|
||||
key={request.id}
|
||||
request={request}
|
||||
onApprove={handleApproval}
|
||||
onModify={handleModification}
|
||||
showAIRecommendations={userRole !== 'student'}
|
||||
isSelected={selectedRequest === request.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{userRole === 'student' && (
|
||||
<NewRequestForm onSubmit={submitNewRequest} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Performance Metrics */}
|
||||
<div className="lg:col-span-3">
|
||||
<AIPerformanceMetrics />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<motion.div
|
||||
className={`request-card p-6 border-2 rounded-xl mb-4 transition-all ${
|
||||
isSelected
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800'
|
||||
}`}
|
||||
whileHover={{ y: -2, boxShadow: "0 8px 25px rgba(0,0,0,0.1)" }}
|
||||
layout
|
||||
>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
{request.studentName}
|
||||
</h4>
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm mb-2 line-clamp-3">
|
||||
{request.description}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Submitted {formatDistanceToNow(request.submittedAt)} ago
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-2 ml-4">
|
||||
<UrgencyBadge urgency={request.urgency} />
|
||||
<CategoryBadge category={request.category} />
|
||||
{request.status && <StatusBadge status={request.status} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAIRecommendations && request.aiRecommendations && (
|
||||
<motion.div
|
||||
className="ai-recommendations bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 p-4 rounded-xl mb-4 border border-blue-200 dark:border-blue-800"
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Cpu className="w-5 h-5 text-blue-500" />
|
||||
<span className="text-sm font-semibold text-blue-700 dark:text-blue-300">
|
||||
AI Recommendation
|
||||
</span>
|
||||
<ConfidenceIndicator confidence={request.aiRecommendations[0].confidenceScore} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{request.aiRecommendations.slice(0, 2).map((rec, index) => (
|
||||
<div key={index} className="flex justify-between items-center">
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm text-gray-900 dark:text-gray-100">
|
||||
{rec.resourceName}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
{rec.reasoningFactors[0]}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="text-green-600 dark:text-green-400 font-medium">
|
||||
${rec.estimatedCost}
|
||||
</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
{rec.fulfillmentTimeline}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex gap-2">
|
||||
<motion.button
|
||||
onClick={() => 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
|
||||
</motion.button>
|
||||
<button
|
||||
onClick={() => onModify(request.id)}
|
||||
className="btn-secondary text-xs px-4 py-2"
|
||||
>
|
||||
✏️ Modify
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2">
|
||||
<LocationBadge location={request.location.city} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{!showAIRecommendations && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Processing...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${colors[urgency]}`}>
|
||||
{urgency.toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function CategoryBadge({ category }: { category: AssistanceCategory }) {
|
||||
return (
|
||||
<span className="px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
||||
{category.replace('-', ' ').toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${colors[status as keyof typeof colors] || colors.pending}`}>
|
||||
{status.replace('-', ' ').toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function LocationBadge({ location }: { location: string }) {
|
||||
return (
|
||||
<span className="px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
||||
📍 {location}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-20 bg-gray-200 dark:bg-gray-600 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className={`text-xs font-bold ${getColor(confidence)}`}>
|
||||
{Math.round(confidence * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AIPerformanceMetrics() {
|
||||
const [metrics, setMetrics] = useState<AIMetrics>({
|
||||
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 (
|
||||
<div className="ai-performance-metrics mt-8">
|
||||
<h4 className="text-lg font-semibold mb-6 text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||||
<Activity className="w-5 h-5 text-blue-500" />
|
||||
AI Performance Dashboard
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<MetricCard
|
||||
title="Accuracy Rate"
|
||||
value={`${(metrics.accuracyRate * 100).toFixed(1)}%`}
|
||||
trend={metrics.accuracyTrend}
|
||||
color="green"
|
||||
icon={<TrendingUp className="w-6 h-6" />}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Avg Processing Time"
|
||||
value={`${metrics.avgProcessingTime}s`}
|
||||
trend={metrics.speedTrend}
|
||||
color="blue"
|
||||
icon={<Activity className="w-6 h-6" />}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Auto-Approval Rate"
|
||||
value={`${(metrics.autoApprovalRate * 100).toFixed(1)}%`}
|
||||
trend={metrics.automationTrend}
|
||||
color="purple"
|
||||
icon={<Cpu className="w-6 h-6" />}
|
||||
/>
|
||||
<MetricCard
|
||||
title="Impact Accuracy"
|
||||
value={`${(metrics.impactPredictionAccuracy * 100).toFixed(1)}%`}
|
||||
trend={metrics.impactTrend}
|
||||
color="orange"
|
||||
icon={<Users className="w-6 h-6" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-xl">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Total Requests Processed</span>
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">{metrics.totalRequestsProcessed}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-sm mt-2">
|
||||
<span className="text-gray-600 dark:text-gray-400">Successful Matches</span>
|
||||
<span className="font-semibold text-green-600 dark:text-green-400">{metrics.successfulMatches}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<motion.div
|
||||
className={`metric-card p-4 rounded-xl border ${colorClasses[color]}`}
|
||||
whileHover={{ scale: 1.02, y: -2 }}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className={iconColors[color]}>{icon}</div>
|
||||
<div className={`text-xs px-2 py-1 rounded-full ${
|
||||
trend > 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)}%
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-1">{value}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{title}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewRequestForm({ onSubmit }: { onSubmit: (request: Omit<StudentRequest, 'id' | 'submittedAt'>) => 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 (
|
||||
<motion.div
|
||||
className="new-request-form mt-8 p-6 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<h4 className="text-md font-semibold mb-4 text-gray-900 dark:text-gray-100">Submit New Request</h4>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Student Name"
|
||||
value={formData.studentName}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, studentName: e.target.value }))}
|
||||
className="input-field"
|
||||
required
|
||||
/>
|
||||
|
||||
<select
|
||||
value={formData.category}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value as AssistanceCategory }))}
|
||||
className="input-field"
|
||||
>
|
||||
<option value="clothing">Clothing</option>
|
||||
<option value="school-supplies">School Supplies</option>
|
||||
<option value="food-assistance">Food Assistance</option>
|
||||
<option value="transportation">Transportation</option>
|
||||
<option value="emergency-housing">Emergency Housing</option>
|
||||
<option value="medical-needs">Medical Needs</option>
|
||||
<option value="technology">Technology</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder="Describe the assistance needed..."
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
||||
className="input-field h-24 resize-none"
|
||||
required
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<select
|
||||
value={formData.urgency}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, urgency: e.target.value as UrgencyLevel }))}
|
||||
className="input-field"
|
||||
>
|
||||
<option value="low">Low Priority</option>
|
||||
<option value="medium">Medium Priority</option>
|
||||
<option value="high">High Priority</option>
|
||||
<option value="emergency">Emergency</option>
|
||||
</select>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="City"
|
||||
value={formData.city}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, city: e.target.value }))}
|
||||
className="input-field"
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="ZIP Code"
|
||||
value={formData.zipCode}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, zipCode: e.target.value }))}
|
||||
className="input-field"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
type="submit"
|
||||
className="btn-primary w-full"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
Submit Request for AI Analysis
|
||||
</motion.button>
|
||||
</form>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function getInsightColor(type: string): string {
|
||||
switch (type) {
|
||||
case 'anomaly': return 'bg-red-400'
|
||||
case 'optimization': return 'bg-green-400'
|
||||
case 'trend': return 'bg-blue-400'
|
||||
case 'prediction': return 'bg-purple-400'
|
||||
case 'recommendation': return 'bg-yellow-400'
|
||||
default: return 'bg-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
export default AIAssistancePortal
|
||||
Reference in New Issue
Block a user