|
|
import { Component, OnInit } from '@angular/core'; |
|
|
import { Router } from '@angular/router'; |
|
|
import { CaseStoreService, PoliceCase } from '../shared/case-store.service'; |
|
|
|
|
|
|
|
|
|
|
|
@Component({ |
|
|
selector: 'app-validationpage', |
|
|
templateUrl: './validationpage.component.html', |
|
|
styleUrls: ['./validationpage.component.css'] |
|
|
}) |
|
|
export class ValidationpageComponent implements OnInit { |
|
|
truePercentage: number = 72; |
|
|
falsePercentage: number = 28; |
|
|
modalOpen: boolean = false; |
|
|
reportDate: string = new Date().toLocaleString('default', { month: 'long', year: 'numeric' }); |
|
|
|
|
|
|
|
|
|
|
|
sessionCount: number = 0; |
|
|
officerCount: number = 0; |
|
|
suspectCount: number = 0; |
|
|
avgSessionDuration: string = ''; |
|
|
analysisCount: number = 0; |
|
|
consistencyIndex: number = 0; |
|
|
audioVideoCount: number = 0; |
|
|
reportCount: number = 0; |
|
|
|
|
|
|
|
|
officer1Percent: number = 0; |
|
|
officer2Percent: number = 0; |
|
|
officer3Percent: number = 0; |
|
|
|
|
|
|
|
|
audioMetric1: number = 0; |
|
|
audioMetric2: number = 0; |
|
|
audioMetric3: number = 0; |
|
|
audioMetric4: number = 0; |
|
|
audioMetric5: number = 0; |
|
|
|
|
|
|
|
|
private audioMetric1Target = 0; |
|
|
private audioMetric2Target = 0; |
|
|
private audioMetric3Target = 0; |
|
|
private audioMetric4Target = 0; |
|
|
private audioMetric5Target = 0; |
|
|
|
|
|
|
|
|
outcome1: number = 0; |
|
|
outcome2: number = 0; |
|
|
outcome3: number = 0; |
|
|
|
|
|
|
|
|
selectedSection: string = 'Dashboard'; |
|
|
|
|
|
|
|
|
audioAnalysisScore: number = 0; |
|
|
videoAnalysisScore: number = 0; |
|
|
verifiedScore: number = 0; |
|
|
|
|
|
|
|
|
audioAnalysisTarget: number = 0; |
|
|
videoAnalysisTarget: number = 0; |
|
|
verifiedTarget: number = 0; |
|
|
|
|
|
|
|
|
audioTruthness: number = 74; |
|
|
videoTruthness: number = 72; |
|
|
verifiedConfidence: number = 73; |
|
|
|
|
|
|
|
|
r1 = 80; |
|
|
r2 = 64; |
|
|
r3 = 48; |
|
|
circumference1 = 2 * Math.PI * this.r1; |
|
|
circumference2 = 2 * Math.PI * this.r2; |
|
|
circumference3 = 2 * Math.PI * this.r3; |
|
|
|
|
|
offset1 = this.circumference1; |
|
|
offset2 = this.circumference2; |
|
|
offset3 = this.circumference3; |
|
|
|
|
|
constructor(private router: Router, private caseStore: CaseStoreService) { |
|
|
|
|
|
const cases = this.caseStore.getPoliceCases(); |
|
|
this.sessionCount = cases.length; |
|
|
this.officerCount = Array.from(new Set(cases.map(c => c.police?.name))).length; |
|
|
this.suspectCount = Array.from(new Set(cases.map(c => c.accused?.name))).length; |
|
|
this.analysisCount = cases.length; |
|
|
this.reportCount = cases.length; |
|
|
|
|
|
this.avgSessionDuration = '00:42:18'; |
|
|
|
|
|
this.audioVideoCount = cases.length; |
|
|
|
|
|
this.consistencyIndex = Math.round((cases.reduce((sum, c) => sum + 72, 0) / (cases.length || 1))); |
|
|
|
|
|
const officerSessions = cases.reduce((acc, c) => { |
|
|
const name = c.police?.name || 'Unknown'; |
|
|
acc[name] = (acc[name] || 0) + 1; |
|
|
return acc; |
|
|
}, {} as { [name: string]: number }); |
|
|
const officerNames = Object.keys(officerSessions); |
|
|
const totalSessions = cases.length || 1; |
|
|
this.officer1Percent = Math.round((officerSessions[officerNames[0]] || 0) * 100 / totalSessions); |
|
|
this.officer2Percent = Math.round((officerSessions[officerNames[1]] || 0) * 100 / totalSessions); |
|
|
this.officer3Percent = Math.round((officerSessions[officerNames[2]] || 0) * 100 / totalSessions); |
|
|
|
|
|
this.audioMetric1Target = 68; |
|
|
this.audioMetric2Target = 75; |
|
|
this.audioMetric3Target = 60; |
|
|
this.audioMetric4Target = 85; |
|
|
this.audioMetric5Target = 82; |
|
|
|
|
|
this.outcome1 = 60; |
|
|
this.outcome2 = 25; |
|
|
this.outcome3 = 15; |
|
|
|
|
|
const nav = this.router.getCurrentNavigation(); |
|
|
const state = nav?.extras?.state as { truePercentage?: number; falsePercentage?: number }; |
|
|
this.truePercentage = state?.truePercentage ?? 0; |
|
|
this.falsePercentage = state?.falsePercentage ?? 0; |
|
|
|
|
|
|
|
|
this.computeOverviewScores(); |
|
|
} |
|
|
|
|
|
ngOnInit(): void { |
|
|
|
|
|
this.animateLoad(); |
|
|
} |
|
|
|
|
|
private animateLoad() { |
|
|
const duration = 3000; |
|
|
const start = performance.now(); |
|
|
|
|
|
const animate = (now: number) => { |
|
|
const t = Math.min(1, (now - start) / duration); |
|
|
|
|
|
const ease = 1 - Math.pow(1 - t, 3); |
|
|
|
|
|
|
|
|
this.audioMetric1 = Math.round(this.audioMetric1Target * ease); |
|
|
this.audioMetric2 = Math.round(this.audioMetric2Target * ease); |
|
|
this.audioMetric3 = Math.round(this.audioMetric3Target * ease); |
|
|
this.audioMetric4 = Math.round(this.audioMetric4Target * ease); |
|
|
this.audioMetric5 = Math.round(this.audioMetric5Target * ease); |
|
|
|
|
|
|
|
|
this.audioAnalysisScore = Math.round(this.audioAnalysisTarget * ease); |
|
|
this.videoAnalysisScore = Math.round(this.videoAnalysisTarget * ease); |
|
|
this.verifiedScore = Math.round(this.verifiedTarget * ease); |
|
|
|
|
|
|
|
|
this.audioTruthness = this.audioAnalysisScore; |
|
|
this.videoTruthness = this.videoAnalysisScore; |
|
|
this.verifiedConfidence = this.verifiedScore; |
|
|
|
|
|
|
|
|
this.offset1 = Math.round(this.circumference1 * (1 - this.audioAnalysisScore / 100)); |
|
|
this.offset2 = Math.round(this.circumference2 * (1 - this.videoAnalysisScore / 100)); |
|
|
this.offset3 = Math.round(this.circumference3 * (1 - this.verifiedScore / 100)); |
|
|
|
|
|
if (t < 1) { |
|
|
requestAnimationFrame(animate); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
this.offset1 = this.circumference1; |
|
|
this.offset2 = this.circumference2; |
|
|
this.offset3 = this.circumference3; |
|
|
|
|
|
requestAnimationFrame(animate); |
|
|
} |
|
|
|
|
|
computeOverviewScores() { |
|
|
|
|
|
const audioVals = [this.audioMetric1Target, this.audioMetric2Target, this.audioMetric3Target, this.audioMetric4Target, this.audioMetric5Target].filter(v => typeof v === 'number'); |
|
|
this.audioAnalysisTarget = audioVals.length ? Math.round(audioVals.reduce((a, b) => a + b, 0) / audioVals.length) : 0; |
|
|
|
|
|
|
|
|
this.videoAnalysisTarget = this.consistencyIndex || Math.round((this.audioMetric2Target + this.audioMetric3Target + this.audioMetric4Target) / 3); |
|
|
|
|
|
|
|
|
const v = this.truePercentage || Math.round((this.audioAnalysisTarget + this.videoAnalysisTarget) / 2); |
|
|
this.verifiedTarget = Math.round(v); |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
percentToOffset(percent: number, circumference: number) { |
|
|
const pct = Math.max(0, Math.min(100, percent)); |
|
|
return Math.round(circumference * (1 - pct / 100)); |
|
|
} |
|
|
|
|
|
selectSection(section: string) { |
|
|
this.selectedSection = section; |
|
|
} |
|
|
|
|
|
navigateBackToCaseDetails() { |
|
|
this.router.navigate(['/case-details']); |
|
|
} |
|
|
|
|
|
openModal() { |
|
|
this.modalOpen = true; |
|
|
} |
|
|
|
|
|
closeModal() { |
|
|
this.modalOpen = false; |
|
|
} |
|
|
|
|
|
downloadPDF() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
alert('PDF download functionality to be implemented.'); |
|
|
} |
|
|
|
|
|
emailReport() { |
|
|
|
|
|
window.location.href = `mailto:?subject=Investigation Report&body=Pass Percentage: ${this.truePercentage}%\nFail Percentage: ${this.falsePercentage}%`; |
|
|
} |
|
|
|
|
|
navigateHome() { |
|
|
this.router.navigate(['/']); |
|
|
} |
|
|
|
|
|
navigateBackToPyDetect() { |
|
|
this.router.navigate(['/py-detect']); |
|
|
} |
|
|
|
|
|
reAnalyze() { |
|
|
|
|
|
alert('Re-Analyze Audio/Video functionality to be implemented.'); |
|
|
} |
|
|
|
|
|
|
|
|
goToQuestionSummary() { |
|
|
this.router.navigate(['/question-summary']); |
|
|
} |
|
|
|
|
|
getMetricGradient(metricValue: number): string { |
|
|
const p = Math.max(0, Math.min(100, Math.round(metricValue))); |
|
|
if (p >= 80) return 'linear-gradient(90deg, #10b981, #34d399)'; |
|
|
if (p >= 60) return 'linear-gradient(90deg, #3b82f6, #60a5fa)'; |
|
|
if (p >= 40) return 'linear-gradient(90deg, #f59e0b, #f97316)'; |
|
|
return 'linear-gradient(90deg, #ef4444, #fb7185)'; |
|
|
} |
|
|
|
|
|
getMetricTextColor(metricValue: number): string { |
|
|
const p = Math.max(0, Math.min(100, Math.round(metricValue))); |
|
|
return p >= 50 ? '#ffffff' : '#0f172a'; |
|
|
} |
|
|
} |
|
|
|