![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/lib/ |
import { prisma } from './prisma';
interface CaseComplexity {
score: number;
factors: string[];
urgency: 'low' | 'medium' | 'high' | 'critical';
estimatedHours: number;
}
interface LawyerCapacity {
userId: string;
currentCaseload: number;
specializations: string[];
successRate: number;
availableHours: number;
preferredCaseTypes: string[];
}
interface AssignmentRecommendation {
userId: string;
confidence: number;
reasoning: string[];
role: 'primary_lawyer' | 'assistant_lawyer' | 'secretary';
estimatedStartDate: Date;
}
export class AIAssignmentEngine {
/**
* Analyze case complexity using AI factors
*/
static analyzeCaseComplexity(registration: any): CaseComplexity {
let score = 0;
const factors: string[] = [];
// Urgency indicators
const urgentKeywords = ['urgent', 'court date', 'deadline', 'appeal', 'bail', 'hearing'];
const hasUrgency = urgentKeywords.some(keyword =>
registration.urgentNeeds?.toLowerCase().includes(keyword) ||
registration.message?.toLowerCase().includes(keyword)
);
if (hasUrgency) {
score += 30;
factors.push('Time-sensitive case requiring immediate attention');
}
// Case type complexity
const complexCaseTypes = ['wrongful conviction', 'appeal', 'class action', 'federal'];
const isComplex = complexCaseTypes.some(type =>
registration.message?.toLowerCase().includes(type) ||
registration.reasonForJoining?.toLowerCase().includes(type)
);
if (isComplex) {
score += 40;
factors.push('Complex legal matter requiring specialized expertise');
}
// Document volume
const documentCount = registration.documents?.length || 0;
if (documentCount > 5) {
score += 20;
factors.push('High document volume requiring thorough review');
}
// Facility type (some are more challenging)
const challengingFacilities = ['bordeaux', 'leclerc']; // Federal facilities
if (challengingFacilities.includes(registration.detaineeInfo?.facility)) {
score += 15;
factors.push('High-security facility with additional procedural requirements');
}
// Language requirements
if (registration.preferredLanguage !== 'French') {
score += 10;
factors.push('Bilingual communication requirements');
}
// Determine urgency and hours
let urgency: 'low' | 'medium' | 'high' | 'critical' = 'low';
let estimatedHours = 20; // Base hours
if (score >= 70) {
urgency = 'critical';
estimatedHours = 80;
} else if (score >= 50) {
urgency = 'high';
estimatedHours = 60;
} else if (score >= 30) {
urgency = 'medium';
estimatedHours = 40;
}
return {
score,
factors,
urgency,
estimatedHours
};
}
/**
* Calculate lawyer capacity and expertise
*/
static async calculateLawyerCapacity(userId: string): Promise<LawyerCapacity> {
const user = await prisma.user.findUnique({
where: { id: userId },
include: {
caseAssignments: {
where: { isActive: true },
include: {
registration: true
}
}
}
});
if (!user) throw new Error('User not found');
const currentCaseload = user.caseAssignments.length;
// Calculate success rate based on completed cases
const completedCases = await prisma.caseAssignment.count({
where: {
userId,
isActive: false,
registration: {
status: 'APPROVED'
}
}
});
const totalCases = await prisma.caseAssignment.count({
where: { userId }
});
const successRate = totalCases > 0 ? (completedCases / totalCases) * 100 : 50;
// Estimate available hours (max 40 hours per week, 20 hours per complex case)
const maxWeeklyHours = 40;
const estimatedCurrentHours = currentCaseload * 20;
const availableHours = Math.max(0, maxWeeklyHours - estimatedCurrentHours);
return {
userId,
currentCaseload,
specializations: user.specialization ? [user.specialization] : [],
successRate,
availableHours,
preferredCaseTypes: [] // Could be enhanced with user preferences
};
}
/**
* Generate AI-powered assignment recommendations
*/
static async generateRecommendations(
registrationId: string
): Promise<AssignmentRecommendation[]> {
const registration = await prisma.registration.findUnique({
where: { id: registrationId },
include: {
documents: true,
detaineeInfo: true
}
});
if (!registration) throw new Error('Registration not found');
// Analyze case complexity
const complexity = this.analyzeCaseComplexity(registration);
// Get all eligible lawyers
const lawyers = await prisma.user.findMany({
where: {
role: { in: ['LAWYER', 'ADMIN', 'SUPERADMIN', 'SUPERADMIN'] }
}
});
const recommendations: AssignmentRecommendation[] = [];
for (const lawyer of lawyers) {
const capacity = await this.calculateLawyerCapacity(lawyer.id);
// Calculate confidence score
let confidence = 50; // Base confidence
const reasoning: string[] = [];
// Availability factor
if (capacity.availableHours >= complexity.estimatedHours) {
confidence += 30;
reasoning.push(`Has ${capacity.availableHours} available hours for ${complexity.estimatedHours}h case`);
} else if (capacity.availableHours > 0) {
confidence += 10;
reasoning.push(`Limited availability: ${capacity.availableHours}h available`);
} else {
confidence -= 20;
reasoning.push('Currently at capacity with existing caseload');
}
// Experience factor
if (capacity.successRate > 80) {
confidence += 20;
reasoning.push(`High success rate: ${capacity.successRate.toFixed(1)}%`);
} else if (capacity.successRate > 60) {
confidence += 10;
reasoning.push(`Good success rate: ${capacity.successRate.toFixed(1)}%`);
}
// Specialization match
const hasRelevantSpecialization = capacity.specializations.some(spec =>
registration.message?.toLowerCase().includes(spec.toLowerCase()) ||
registration.reasonForJoining?.toLowerCase().includes(spec.toLowerCase())
);
if (hasRelevantSpecialization) {
confidence += 25;
reasoning.push('Specialization matches case requirements');
}
// Workload balance
if (capacity.currentCaseload <= 3) {
confidence += 15;
reasoning.push('Optimal caseload for quality attention');
} else if (capacity.currentCaseload <= 5) {
confidence += 5;
reasoning.push('Manageable caseload');
} else {
confidence -= 10;
reasoning.push('Heavy caseload may impact attention');
}
// Language compatibility
if (lawyer.name && registration.preferredLanguage === 'French') {
// Assume French names indicate French speakers (could be enhanced)
const frenchNames = ['marie', 'jean', 'pierre', 'sophie', 'isabelle'];
const isFrenchSpeaker = frenchNames.some(name =>
lawyer.name!.toLowerCase().includes(name)
);
if (isFrenchSpeaker) {
confidence += 10;
reasoning.push('Language compatibility with client preference');
}
}
// Urgency handling
if (complexity.urgency === 'critical' && capacity.availableHours > 20) {
confidence += 15;
reasoning.push('Available for urgent case handling');
}
// Determine role recommendation
let role: 'primary_lawyer' | 'assistant_lawyer' | 'secretary' = 'primary_lawyer';
if (capacity.currentCaseload > 5 || complexity.score < 30) {
role = 'assistant_lawyer';
reasoning.push('Recommended as supporting attorney');
}
// Estimate start date
const estimatedStartDate = new Date();
if (capacity.availableHours < 10) {
estimatedStartDate.setDate(estimatedStartDate.getDate() + 7); // Next week
}
recommendations.push({
userId: lawyer.id,
confidence: Math.min(100, Math.max(0, confidence)),
reasoning,
role,
estimatedStartDate
});
}
// Sort by confidence score
return recommendations.sort((a, b) => b.confidence - a.confidence);
}
/**
* Auto-assign optimal team based on AI recommendations
*/
static async autoAssignOptimalTeam(
registrationId: string,
assignedBy: string
): Promise<any[]> {
const recommendations = await this.generateRecommendations(registrationId);
const complexity = await this.analyzeCaseComplexity(
await prisma.registration.findUnique({
where: { id: registrationId },
include: { documents: true, detaineeInfo: true }
})
);
const assignments = [];
// Assign primary lawyer (highest confidence)
if (recommendations.length > 0) {
const primaryLawyer = recommendations[0];
assignments.push(
await prisma.caseAssignment.create({
data: {
registrationId,
userId: primaryLawyer.userId,
role: 'primary_lawyer',
assignedBy
}
})
);
}
// For complex cases, assign assistant
if (complexity.score >= 50 && recommendations.length > 1) {
const assistantLawyer = recommendations[1];
assignments.push(
await prisma.caseAssignment.create({
data: {
registrationId,
userId: assistantLawyer.userId,
role: 'assistant_lawyer',
assignedBy
}
})
);
}
// For very complex cases, assign secretary
if (complexity.score >= 70) {
const secretaries = await prisma.user.findMany({
where: { role: { in: ['SECRETARY', 'ASSISTANT'] } }
});
if (secretaries.length > 0) {
assignments.push(
await prisma.caseAssignment.create({
data: {
registrationId,
userId: secretaries[0].id,
role: 'secretary',
assignedBy
}
})
);
}
}
return assignments;
}
}