T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/lib/smart-matching.ts
import { prisma } from './prisma';

interface CaseProfile {
  id: string;
  complexity: number;
  keywords: string[];
  facility: string;
  caseType: string;
  urgency: 'low' | 'medium' | 'high' | 'critical';
  documentCount: number;
  language: string;
}

interface SimilarCase {
  id: string;
  similarity: number;
  outcome: string;
  duration: number;
  assignedTeam: string[];
  successFactors: string[];
}

interface LawyerRecommendation {
  userId: string;
  name: string;
  matchScore: number;
  expertise: string[];
  pastPerformance: {
    similarCases: number;
    successRate: number;
    averageDuration: number;
  };
  availability: {
    currentLoad: number;
    nextAvailable: Date;
  };
  reasoning: string[];
}

export class SmartMatching {
  
  /**
   * Find similar cases for learning and strategy
   */
  static async findSimilarCases(registrationId: string): Promise<SimilarCase[]> {
    const targetCase = await prisma.registration.findUnique({
      where: { id: registrationId },
      include: { 
        documents: true, 
        detaineeInfo: true,
        caseAssignments: {
          include: { user: true }
        }
      }
    });

    if (!targetCase) return [];

    const targetProfile = this.createCaseProfile(targetCase);
    
    // Get all completed cases for comparison
    const completedCases = await prisma.registration.findMany({
      where: {
        status: { in: ['APPROVED', 'REJECTED'] },
        id: { not: registrationId }
      },
      include: { 
        documents: true, 
        detaineeInfo: true,
        caseAssignments: {
          include: { user: true }
        }
      }
    });

    const similarities: SimilarCase[] = [];

    for (const case_ of completedCases) {
      const caseProfile = this.createCaseProfile(case_);
      const similarity = this.calculateSimilarity(targetProfile, caseProfile);
      
      if (similarity > 0.3) { // Only include cases with >30% similarity
        similarities.push({
          id: case_.id,
          similarity,
          outcome: case_.status,
          duration: this.calculateCaseDuration(case_),
          assignedTeam: case_.caseAssignments.map(a => a.user.name).filter((name): name is string => name !== null),
          successFactors: this.extractSuccessFactors(case_)
        });
      }
    }

    return similarities
      .sort((a, b) => b.similarity - a.similarity)
      .slice(0, 10); // Top 10 most similar cases
  }

  /**
   * Get smart lawyer recommendations for a case
   */
  static async getSmartRecommendations(registrationId: string): Promise<LawyerRecommendation[]> {
    const targetCase = await prisma.registration.findUnique({
      where: { id: registrationId },
      include: { documents: true, detaineeInfo: true }
    });

    if (!targetCase) return [];

    const targetProfile = this.createCaseProfile(targetCase);
    const similarCases = await this.findSimilarCases(registrationId);
    
    // Get all available lawyers
    const lawyers = await prisma.user.findMany({
      where: { role: { in: ['LAWYER', 'ADMIN', 'SUPERADMIN', 'SUPERADMIN'] } },
      include: {
        caseAssignments: {
          include: {
            registration: true
          }
        }
      }
    });

    const recommendations: LawyerRecommendation[] = [];

    for (const lawyer of lawyers) {
      const recommendation = await this.evaluateLawyerMatch(
        lawyer, 
        targetProfile, 
        similarCases
      );
      recommendations.push(recommendation);
    }

    return recommendations
      .sort((a, b) => b.matchScore - a.matchScore)
      .slice(0, 5); // Top 5 recommendations
  }

  /**
   * Create a profile for case matching
   */
  private static createCaseProfile(case_: any): CaseProfile {
    const keywords = this.extractKeywords(case_.message + ' ' + (case_.reasonForJoining || ''));
    const complexity = this.calculateComplexity(case_);
    
    return {
      id: case_.id,
      complexity,
      keywords,
      facility: case_.detaineeInfo?.facility || 'unknown',
      caseType: this.determineCaseType(case_),
      urgency: this.determineUrgency(case_),
      documentCount: case_.documents?.length || 0,
      language: case_.preferredLanguage || 'French'
    };
  }

  /**
   * Extract keywords from case text
   */
  private static extractKeywords(text: string): string[] {
    const stopWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
    const words = text.toLowerCase()
      .replace(/[^\w\s]/g, '')
      .split(/\s+/)
      .filter(word => word.length > 3 && !stopWords.includes(word));
    
    // Count word frequency
    const wordCount: { [key: string]: number } = {};
    words.forEach(word => {
      wordCount[word] = (wordCount[word] || 0) + 1;
    });
    
    // Return top keywords
    return Object.entries(wordCount)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 10)
      .map(([word]) => word);
  }

  /**
   * Calculate case complexity score
   */
  private static calculateComplexity(case_: any): number {
    let score = 0;
    
    // Document count factor
    score += Math.min((case_.documents?.length || 0) * 5, 25);
    
    // Urgency factor
    const urgentKeywords = ['urgent', 'court', 'hearing', 'deadline', 'appeal'];
    const hasUrgency = urgentKeywords.some(keyword => 
      case_.message?.toLowerCase().includes(keyword) ||
      case_.urgentNeeds?.toLowerCase().includes(keyword)
    );
    if (hasUrgency) score += 20;
    
    // Case type factor
    const complexTypes = ['appeal', 'wrongful conviction', 'class action', 'federal'];
    const isComplex = complexTypes.some(type => 
      case_.message?.toLowerCase().includes(type)
    );
    if (isComplex) score += 30;
    
    // Facility factor
    const highSecurityFacilities = ['bordeaux', 'leclerc'];
    if (highSecurityFacilities.includes(case_.detaineeInfo?.facility?.toLowerCase())) {
      score += 15;
    }
    
    return Math.min(score, 100);
  }

  /**
   * Determine case type from content
   */
  private static determineCaseType(case_: any): string {
    const text = (case_.message + ' ' + (case_.reasonForJoining || '')).toLowerCase();
    
    if (text.includes('appeal')) return 'appeal';
    if (text.includes('wrongful conviction')) return 'wrongful_conviction';
    if (text.includes('bail')) return 'bail';
    if (text.includes('sentence')) return 'sentencing';
    if (text.includes('parole')) return 'parole';
    if (text.includes('conditions')) return 'conditions';
    
    return 'general';
  }

  /**
   * Determine case urgency
   */
  private static determineUrgency(case_: any): 'low' | 'medium' | 'high' | 'critical' {
    const text = (case_.message + ' ' + (case_.urgentNeeds || '')).toLowerCase();
    
    if (text.includes('urgent') || text.includes('immediate')) return 'critical';
    if (text.includes('court date') || text.includes('hearing')) return 'high';
    if (text.includes('deadline')) return 'medium';
    
    return 'low';
  }

  /**
   * Calculate similarity between two case profiles
   */
  private static calculateSimilarity(profile1: CaseProfile, profile2: CaseProfile): number {
    let similarity = 0;
    let factors = 0;
    
    // Keyword similarity (30% weight)
    const keywordSimilarity = this.calculateKeywordSimilarity(profile1.keywords, profile2.keywords);
    similarity += keywordSimilarity * 0.3;
    factors += 0.3;
    
    // Facility match (15% weight)
    if (profile1.facility === profile2.facility) {
      similarity += 0.15;
    }
    factors += 0.15;
    
    // Case type match (20% weight)
    if (profile1.caseType === profile2.caseType) {
      similarity += 0.2;
    }
    factors += 0.2;
    
    // Complexity similarity (20% weight)
    const complexityDiff = Math.abs(profile1.complexity - profile2.complexity);
    const complexitySimilarity = Math.max(0, 1 - (complexityDiff / 100));
    similarity += complexitySimilarity * 0.2;
    factors += 0.2;
    
    // Urgency match (10% weight)
    const urgencyLevels = ['low', 'medium', 'high', 'critical'];
    const urgency1Index = urgencyLevels.indexOf(profile1.urgency);
    const urgency2Index = urgencyLevels.indexOf(profile2.urgency);
    const urgencyDiff = Math.abs(urgency1Index - urgency2Index);
    const urgencySimilarity = Math.max(0, 1 - (urgencyDiff / 3));
    similarity += urgencySimilarity * 0.1;
    factors += 0.1;
    
    // Language match (5% weight)
    if (profile1.language === profile2.language) {
      similarity += 0.05;
    }
    factors += 0.05;
    
    return similarity / factors;
  }

  /**
   * Calculate keyword similarity using Jaccard index
   */
  private static calculateKeywordSimilarity(keywords1: string[], keywords2: string[]): number {
    const set1 = new Set(keywords1);
    const set2 = new Set(keywords2);
    
    const intersection = new Set(Array.from(set1).filter(x => set2.has(x)));
    const union = new Set([...keywords1, ...keywords2]);
    
    return union.size === 0 ? 0 : intersection.size / union.size;
  }

  /**
   * Calculate case duration in days
   */
  private static calculateCaseDuration(case_: any): number {
    if (!case_.createdAt || !case_.updatedAt) return 0;
    
    const start = new Date(case_.createdAt);
    const end = new Date(case_.updatedAt);
    
    return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
  }

  /**
   * Extract success factors from completed case
   */
  private static extractSuccessFactors(case_: any): string[] {
    const factors: string[] = [];
    
    if (case_.status === 'APPROVED') {
      // Team composition
      const teamRoles = case_.caseAssignments.map((a: any) => a.role);
      if (teamRoles.includes('primary_lawyer') && teamRoles.includes('assistant_lawyer')) {
        factors.push('Full legal team assigned');
      }
      
      // Quick response
      const duration = this.calculateCaseDuration(case_);
      if (duration < 30) {
        factors.push('Resolved quickly (< 30 days)');
      }
      
      // Document preparation
      if (case_.documents?.length > 3) {
        factors.push('Comprehensive documentation');
      }
      
      // Specialization match
      const hasSpecialistLawyer = case_.caseAssignments.some((a: any) => 
        a.user.specialization && 
        case_.message?.toLowerCase().includes(a.user.specialization.toLowerCase())
      );
      if (hasSpecialistLawyer) {
        factors.push('Specialist lawyer assigned');
      }
    }
    
    return factors;
  }

  /**
   * Evaluate how well a lawyer matches a case
   */
  private static async evaluateLawyerMatch(
    lawyer: any, 
    targetProfile: CaseProfile, 
    similarCases: SimilarCase[]
  ): Promise<LawyerRecommendation> {
    let matchScore = 50; // Base score
    const reasoning: string[] = [];
    
    // Experience with similar cases
    const similarCaseExperience = lawyer.caseAssignments.filter((assignment: any) => {
      const caseProfile = this.createCaseProfile(assignment.registration);
      return this.calculateSimilarity(targetProfile, caseProfile) > 0.5;
    }).length;
    
    if (similarCaseExperience > 0) {
      matchScore += Math.min(similarCaseExperience * 10, 30);
      reasoning.push(`Has handled ${similarCaseExperience} similar cases`);
    }
    
    // Specialization match
    if (lawyer.specialization) {
      const specializationMatch = targetProfile.keywords.some(keyword => 
        lawyer.specialization.toLowerCase().includes(keyword) ||
        keyword.includes(lawyer.specialization.toLowerCase())
      );
      
      if (specializationMatch) {
        matchScore += 20;
        reasoning.push(`Specialization matches case requirements`);
      }
    }
    
    // Success rate calculation
    const completedCases = lawyer.caseAssignments.filter((a: any) => 
      ['APPROVED', 'REJECTED'].includes(a.registration.status)
    );
    const successfulCases = completedCases.filter((a: any) => 
      a.registration.status === 'APPROVED'
    );
    const successRate = completedCases.length > 0 ? 
      (successfulCases.length / completedCases.length) * 100 : 50;
    
    if (successRate > 80) {
      matchScore += 15;
      reasoning.push(`High success rate: ${successRate.toFixed(1)}%`);
    } else if (successRate > 60) {
      matchScore += 8;
      reasoning.push(`Good success rate: ${successRate.toFixed(1)}%`);
    }
    
    // Current workload
    const activeCases = lawyer.caseAssignments.filter((a: any) => a.isActive).length;
    if (activeCases <= 3) {
      matchScore += 10;
      reasoning.push(`Optimal workload for attention to detail`);
    } else if (activeCases > 6) {
      matchScore -= 15;
      reasoning.push(`Heavy workload may impact case attention`);
    }
    
    // Language compatibility
    if (targetProfile.language !== 'French' && lawyer.name) {
      // Simple heuristic - could be enhanced with actual language skills data
      const englishNames = ['john', 'mary', 'david', 'sarah', 'michael'];
      const isEnglishSpeaker = englishNames.some(name => 
        lawyer.name.toLowerCase().includes(name)
      );
      if (isEnglishSpeaker) {
        matchScore += 5;
        reasoning.push(`Language compatibility`);
      }
    }
    
    // Performance in similar cases
    const avgDuration = this.calculateAverageDuration(lawyer.caseAssignments);
    
    return {
      userId: lawyer.id,
      name: lawyer.name,
      matchScore: Math.min(matchScore, 100),
      expertise: lawyer.specialization ? [lawyer.specialization] : [],
      pastPerformance: {
        similarCases: similarCaseExperience,
        successRate,
        averageDuration: avgDuration
      },
      availability: {
        currentLoad: activeCases,
        nextAvailable: new Date() // Could be calculated based on current caseload
      },
      reasoning
    };
  }

  /**
   * Calculate average case duration for a lawyer
   */
  private static calculateAverageDuration(assignments: any[]): number {
    const completedCases = assignments.filter(a => 
      ['APPROVED', 'REJECTED'].includes(a.registration.status)
    );
    
    if (completedCases.length === 0) return 45; // Default estimate
    
    const totalDuration = completedCases.reduce((sum, assignment) => {
      return sum + this.calculateCaseDuration(assignment.registration);
    }, 0);
    
    return Math.round(totalDuration / completedCases.length);
  }

  /**
   * Get case insights and recommendations
   */
  static async getCaseInsights(registrationId: string): Promise<any> {
    const similarCases = await this.findSimilarCases(registrationId);
    const recommendations = await this.getSmartRecommendations(registrationId);
    
    // Generate insights
    const insights = {
      similarCases: similarCases.slice(0, 5),
      recommendedLawyers: recommendations.slice(0, 3),
      predictedOutcome: this.predictOutcome(similarCases),
      estimatedDuration: this.estimateDuration(similarCases),
      riskFactors: this.identifyRiskFactors(similarCases),
      successStrategies: this.extractSuccessStrategies(similarCases)
    };
    
    return insights;
  }

  /**
   * Predict case outcome based on similar cases
   */
  private static predictOutcome(similarCases: SimilarCase[]): any {
    if (similarCases.length === 0) {
      return { prediction: 'Unknown', confidence: 0 };
    }
    
    const outcomes = similarCases.map(c => c.outcome);
    const approvedCount = outcomes.filter(o => o === 'APPROVED').length;
    const rejectedCount = outcomes.filter(o => o === 'REJECTED').length;
    
    const approvalRate = approvedCount / outcomes.length;
    
    return {
      prediction: approvalRate > 0.5 ? 'APPROVED' : 'REJECTED',
      confidence: Math.max(approvalRate, 1 - approvalRate),
      approvalRate: approvalRate * 100
    };
  }

  /**
   * Estimate case duration
   */
  private static estimateDuration(similarCases: SimilarCase[]): number {
    if (similarCases.length === 0) return 45; // Default
    
    const durations = similarCases.map(c => c.duration);
    return Math.round(durations.reduce((sum, d) => sum + d, 0) / durations.length);
  }

  /**
   * Identify risk factors
   */
  private static identifyRiskFactors(similarCases: SimilarCase[]): string[] {
    const rejectedCases = similarCases.filter(c => c.outcome === 'REJECTED');
    const riskFactors: string[] = [];
    
    if (rejectedCases.length > similarCases.length * 0.3) {
      riskFactors.push('High rejection rate for similar cases');
    }
    
    const longCases = similarCases.filter(c => c.duration > 90);
    if (longCases.length > similarCases.length * 0.4) {
      riskFactors.push('Similar cases tend to take longer than average');
    }
    
    return riskFactors;
  }

  /**
   * Extract success strategies
   */
  private static extractSuccessStrategies(similarCases: SimilarCase[]): string[] {
    const successfulCases = similarCases.filter(c => c.outcome === 'APPROVED');
    const strategies: string[] = [];
    
    // Analyze common success factors
    const allFactors = successfulCases.flatMap(c => c.successFactors);
    const factorCounts: { [key: string]: number } = {};
    
    allFactors.forEach(factor => {
      factorCounts[factor] = (factorCounts[factor] || 0) + 1;
    });
    
    // Get most common success factors
    Object.entries(factorCounts)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 5)
      .forEach(([factor, count]) => {
        if (count > successfulCases.length * 0.3) {
          strategies.push(factor);
        }
      });
    
    return strategies;
  }
} 

CasperSecurity Mini