|
|
import { Component } from '@angular/core'; |
|
|
import { Router } from '@angular/router'; |
|
|
import * as XLSX from 'xlsx'; |
|
|
import { saveAs } from 'file-saver'; |
|
|
|
|
|
import jsPDF from 'jspdf'; |
|
|
|
|
|
import autoTable from 'jspdf-autotable'; |
|
|
import { QuestionDataService } from '../question-data.service'; |
|
|
|
|
|
@Component({ |
|
|
selector: 'app-question-summary-page', |
|
|
templateUrl: './question-summary-page.component.html', |
|
|
styleUrls: ['./question-summary-page.component.css'] |
|
|
}) |
|
|
export class QuestionSummaryPageComponent { |
|
|
|
|
|
pageSize = 5; |
|
|
currentPage = 1; |
|
|
|
|
|
|
|
|
caseDetails = { |
|
|
caseId: 'CASE-007', |
|
|
officer: 'Ganesh', |
|
|
suspect: 'Jeeva', |
|
|
date: '2025-10-15', |
|
|
verdict: 'Consistent', |
|
|
summary: 'The suspect displayed calm emotions overall but showed minor inconsistency in hand gestures. Recommendation: Conduct a short follow-up session.', |
|
|
observations: 'During questioning, the suspect showed hesitation when discussing the time of the incident. Eye movement frequency decreased by25% during key questions. Speech tone remained steady, indicating partial honesty. Recommendation: Further questioning advised for financial motive discussion.', |
|
|
location: 'Chennai', |
|
|
sessionTime: '00:42:18', |
|
|
progress: 92, |
|
|
status: 'Closed' |
|
|
}; |
|
|
|
|
|
questions = [ |
|
|
{ text: 'Did you visit the location on 12th?', answer: 'Yes, I was there for about 20 minutes.', duration: '00:18', truthProbability: 78, dominantEmotion: 'Nervous 😟', body: 'ajslkdfjsa ldfjlska', bodyScore: 33, voice: 'aksjdfkls jadflk', voiceScore: 23, overallScore: '66%', emotion: 'Calm', videoUrl: '', audioUrl: '', eyeContact: '78%', blinkRate: '12/min', posture: 'Neutral', handMovement: 'Low', legMovement: 'Moderate', microExpressions: '2 detected', stressLevel: 68, confidence: 'Moderate', sentiment: 'Negative (-0.45)', responseDelay: '3.1 sec' }, |
|
|
{ text: 'Were you alone at the scene?', answer: 'No, my friend was with me.', duration: '00:22', truthProbability: 62, dominantEmotion: 'Nervous 😟', body: '', bodyScore: '', voice: '', voiceScore: '', overallScore: '', emotion: 'Nervous', videoUrl: '', audioUrl: '', eyeContact: '65%', blinkRate: '15/min', posture: 'Defensive', handMovement: 'Medium', legMovement: 'Low', microExpressions: '3 detected', stressLevel: 72, confidence: 'Low', sentiment: 'Negative (-0.32)', responseDelay: '2.7 sec' }, |
|
|
{ text: 'Did you know the victim?', answer: 'Yes, we worked together.', duration: '00:15', truthProbability: 85, dominantEmotion: 'Calm 😌', body: '', bodyScore: '', voice: '', voiceScore: '', overallScore: '', emotion: 'Calm', videoUrl: '', audioUrl: '', eyeContact: '82%', blinkRate: '10/min', posture: 'Relaxed', handMovement: 'Low', legMovement: 'Low', microExpressions: '1 detected', stressLevel: 38, confidence: 'High', sentiment: 'Positive (+0.22)', responseDelay: '1.2 sec' }, |
|
|
{ text: 'Did you handle any objects?', answer: 'I picked up a bag to check for ID.', duration: '00:19', truthProbability: 44, dominantEmotion: 'Defensive 🛡️', body: '', bodyScore: '', voice: '', voiceScore: '', overallScore: '', emotion: 'Defensive', videoUrl: '', audioUrl: '', eyeContact: '55%', blinkRate: '18/min', posture: 'Tense', handMovement: 'High', legMovement: 'High', microExpressions: '4 detected', stressLevel: 81, confidence: 'Low', sentiment: 'Negative (-0.61)', responseDelay: '4.0 sec' }, |
|
|
|
|
|
{ text: 'What time did the incident occur?', answer: 'Around 9 PM.', duration: '00:12', truthProbability: 71, dominantEmotion: 'Calm 😌', body: '', bodyScore: '', voice: '', voiceScore: '', overallScore: '', emotion: 'Calm', videoUrl: '', audioUrl: '', eyeContact: '70%', blinkRate: '11/min', posture: 'Relaxed', handMovement: 'Low', legMovement: 'Low', microExpressions: '1 detected', stressLevel: 40, confidence: 'High', sentiment: 'Neutral (0.00)', responseDelay: '1.0 sec' }, |
|
|
{ text: 'Did anyone else accompany you?', answer: 'No one else was present.', duration: '00:10', truthProbability: 79, dominantEmotion: 'Calm 😌', body: '', bodyScore: '', voice: '', voiceScore: '', overallScore: '', emotion: 'Calm', videoUrl: '', audioUrl: '', eyeContact: '80%', blinkRate: '9/min', posture: 'Neutral', handMovement: 'Low', legMovement: 'Low', microExpressions: '0 detected', stressLevel: 34, confidence: 'High', sentiment: 'Positive (+0.15)', responseDelay: '0.8 sec' } |
|
|
]; |
|
|
|
|
|
constructor(private router: Router, public questionDataService: QuestionDataService) { } |
|
|
|
|
|
|
|
|
get totalPages(): number { |
|
|
return Math.max(1, Math.ceil(this.questions.length / this.pageSize)); |
|
|
} |
|
|
|
|
|
get pagedQuestions() { |
|
|
const start = (this.currentPage - 1) * this.pageSize; |
|
|
return this.questions.slice(start, start + this.pageSize); |
|
|
} |
|
|
|
|
|
goToPage(page: number) { |
|
|
if (page < 1) page = 1; |
|
|
if (page > this.totalPages) page = this.totalPages; |
|
|
this.currentPage = page; |
|
|
window.scrollTo({ top: 0, behavior: 'smooth' }); |
|
|
} |
|
|
|
|
|
prevPage() { this.goToPage(this.currentPage - 1); } |
|
|
nextPage() { this.goToPage(this.currentPage + 1); } |
|
|
|
|
|
|
|
|
get totalResults(): number { |
|
|
return this.questions.length; |
|
|
} |
|
|
|
|
|
get resultsStart(): number { |
|
|
if (this.totalResults === 0) return 0; |
|
|
return (this.currentPage - 1) * this.pageSize + 1; |
|
|
} |
|
|
|
|
|
get resultsEnd(): number { |
|
|
return Math.min(this.currentPage * this.pageSize, this.totalResults); |
|
|
} |
|
|
|
|
|
changePageSize(eventOrSize: any) { |
|
|
let newSize: any = eventOrSize; |
|
|
|
|
|
if (eventOrSize && typeof eventOrSize === 'object' && 'target' in eventOrSize) { |
|
|
const target = eventOrSize.target as HTMLSelectElement | null; |
|
|
newSize = target?.value; |
|
|
} |
|
|
const size = Number(newSize) || 5; |
|
|
this.pageSize = size; |
|
|
this.currentPage = 1; |
|
|
} |
|
|
|
|
|
visiblePages(): (number | string)[] { |
|
|
const total = this.totalPages; |
|
|
const current = this.currentPage; |
|
|
const pages: (number | string)[] = []; |
|
|
if (total <= 7) { |
|
|
for (let i = 1; i <= total; i++) pages.push(i); |
|
|
return pages; |
|
|
} |
|
|
pages.push(1); |
|
|
if (current > 4) pages.push('...'); |
|
|
const start = Math.max(2, Math.min(current - 1, total - 4)); |
|
|
const end = Math.min(total - 1, start + 2); |
|
|
for (let i = start; i <= end; i++) pages.push(i); |
|
|
if (end < total - 1) pages.push('...'); |
|
|
pages.push(total); |
|
|
return pages; |
|
|
} |
|
|
|
|
|
goBack() { |
|
|
this.router.navigate(['/validationpage']); |
|
|
} |
|
|
|
|
|
navigateHome() { |
|
|
|
|
|
} |
|
|
|
|
|
navigateBackToPyDetect() { |
|
|
|
|
|
} |
|
|
|
|
|
downloadExcel() { |
|
|
|
|
|
const header1 = [ |
|
|
'S. No', 'Case ID', 'Officer', 'Date', |
|
|
'Question', 'Answer', 'Duration', 'Truth Probability', 'Dominant Emotion', 'Emotion', |
|
|
'Audio Analysis', '', '', '', '', |
|
|
'Video Analysis', '', '', '', '', '', |
|
|
'Physical Expression', 'Physical Score', 'Voice Expression', 'Voice Score', 'Overall Score' |
|
|
]; |
|
|
|
|
|
const header2 = [ |
|
|
'', '', '', '', |
|
|
'', '', '', '', '', '', |
|
|
'Stress Level', 'Confidence', 'Sentiment', 'Response Delay', |
|
|
'Eye Contact', 'Blink Rate', 'Posture', 'Hand Movement', 'Leg Movement', 'Micro Expressions', |
|
|
'', '', '', '', '', '' |
|
|
]; |
|
|
|
|
|
const dataRows = this.questions.map((q, i) => [ |
|
|
i + 1, |
|
|
this.caseDetails.caseId || '', |
|
|
this.caseDetails.officer || '', |
|
|
this.caseDetails.date || '', |
|
|
q.text, |
|
|
q.answer, |
|
|
q.duration, |
|
|
(q.truthProbability !== undefined ? q.truthProbability + '%' : ''), |
|
|
q.dominantEmotion || '', |
|
|
q.emotion || '', |
|
|
q.stressLevel || '', |
|
|
q.confidence || '', |
|
|
q.sentiment || '', |
|
|
q.responseDelay || '', |
|
|
q.eyeContact || '', |
|
|
q.blinkRate || '', |
|
|
q.posture || '', |
|
|
q.handMovement || '', |
|
|
q.legMovement || '', |
|
|
q.microExpressions || '', |
|
|
this.getPhysicalExpressionSummary(q), |
|
|
this.getPhysicalScore(q), |
|
|
this.getVoiceExpressionSummary(q), |
|
|
this.getVoiceScore(q), |
|
|
this.getOverallScore(q) |
|
|
]); |
|
|
|
|
|
|
|
|
const wsData = [header1, header2, ...dataRows]; |
|
|
const ws = XLSX.utils.aoa_to_sheet(wsData); |
|
|
|
|
|
|
|
|
ws['!merges'] = [ |
|
|
{ s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, |
|
|
{ s: { r: 0, c: 1 }, e: { r: 1, c: 1 } }, |
|
|
{ s: { r: 0, c: 2 }, e: { r: 1, c: 2 } }, |
|
|
{ s: { r: 0, c: 3 }, e: { r: 1, c: 3 } }, |
|
|
{ s: { r: 0, c: 4 }, e: { r: 1, c: 4 } }, |
|
|
{ s: { r: 0, c: 5 }, e: { r: 1, c: 5 } }, |
|
|
{ s: { r: 0, c: 6 }, e: { r: 1, c: 6 } }, |
|
|
{ s: { r: 0, c: 7 }, e: { r: 1, c: 7 } }, |
|
|
{ s: { r: 0, c: 8 }, e: { r: 1, c: 8 } }, |
|
|
{ s: { r: 0, c: 9 }, e: { r: 1, c: 9 } }, |
|
|
{ s: { r: 0, c: 10 }, e: { r: 0, c: 14 } }, |
|
|
{ s: { r: 0, c: 15 }, e: { r: 0, c: 20 } }, |
|
|
{ s: { r: 0, c: 21 }, e: { r: 1, c: 21 } }, |
|
|
{ s: { r: 0, c: 22 }, e: { r: 1, c: 22 } }, |
|
|
{ s: { r: 0, c: 23 }, e: { r: 1, c: 23 } }, |
|
|
{ s: { r: 0, c: 24 }, e: { r: 1, c: 24 } }, |
|
|
{ s: { r: 0, c: 25 }, e: { r: 1, c: 25 } } |
|
|
]; |
|
|
|
|
|
const wb = XLSX.utils.book_new(); |
|
|
XLSX.utils.book_append_sheet(wb, ws, 'Questions'); |
|
|
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); |
|
|
saveAs(new Blob([wbout], { type: 'application/octet-stream' }), 'questions-and-answers.xlsx'); |
|
|
} |
|
|
|
|
|
getPhysicalExpressionSummary(q: any): string { |
|
|
let parts = []; |
|
|
if (q.posture) parts.push(q.posture); |
|
|
if (q.handMovement) parts.push(q.handMovement + ' hand'); |
|
|
if (q.legMovement) parts.push(q.legMovement + ' leg'); |
|
|
if (q.microExpressions) parts.push(q.microExpressions); |
|
|
return parts.length ? parts.join(', ') : '—'; |
|
|
} |
|
|
getPhysicalScore(q: any): string { |
|
|
let scores = []; |
|
|
if (typeof q.handMovement === 'number') scores.push(q.handMovement); |
|
|
if (typeof q.legMovement === 'number') scores.push(q.legMovement); |
|
|
const match = (q.microExpressions || '').match(/(\d+)/); |
|
|
if (match) scores.push(Number(match[1])); |
|
|
if (scores.length === 0) return '—'; |
|
|
return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) + '%'; |
|
|
} |
|
|
getVoiceExpressionSummary(q: any): string { |
|
|
let parts = []; |
|
|
if (q.stressLevel !== undefined) parts.push('Stress ' + q.stressLevel); |
|
|
if (q.confidence) parts.push('Conf ' + q.confidence); |
|
|
if (q.sentiment) parts.push('Sent ' + this.getSentimentPercent(q.sentiment)); |
|
|
if (q.responseDelay) parts.push('Delay ' + q.responseDelay); |
|
|
return parts.length ? parts.join(', ') : '—'; |
|
|
} |
|
|
getVoiceScore(q: any): string { |
|
|
let scores = []; |
|
|
if (typeof q.stressLevel === 'number') scores.push(q.stressLevel); |
|
|
if (typeof q.confidence === 'number') scores.push(q.confidence); |
|
|
else if (q.confidence === 'High') scores.push(90); |
|
|
else if (q.confidence === 'Moderate') scores.push(60); |
|
|
else if (q.confidence === 'Low') scores.push(30); |
|
|
if (scores.length === 0) return '—'; |
|
|
return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) + '%'; |
|
|
} |
|
|
getOverallScore(q: any): string { |
|
|
|
|
|
const phys = this.getPhysicalScore(q); |
|
|
const voice = this.getVoiceScore(q); |
|
|
const physNum = parseInt(phys); |
|
|
const voiceNum = parseInt(voice); |
|
|
if (isNaN(physNum) && isNaN(voiceNum)) return '—'; |
|
|
if (isNaN(physNum)) return voice; |
|
|
if (isNaN(voiceNum)) return phys; |
|
|
return Math.round((physNum + voiceNum) / 2) + '%'; |
|
|
} |
|
|
getSentimentPercent(sentiment: string): string { |
|
|
if (!sentiment) return ''; |
|
|
const match = sentiment.match(/([+-]?\d*\.?\d+)/); |
|
|
if (match) { |
|
|
const value = parseFloat(match[1]); |
|
|
const percent = Math.round(value * 100); |
|
|
return (percent > 0 ? '+' : '') + percent + '%'; |
|
|
} |
|
|
return sentiment; |
|
|
} |
|
|
|
|
|
viewDetails(index: number) { |
|
|
|
|
|
const normalized = this.questions.map((q: any) => ({ ...q, question: q.question ?? q.text ?? '' })); |
|
|
this.questionDataService.setQuestions(normalized); |
|
|
this.questionDataService.setCaseDetails(this.caseDetails); |
|
|
const absoluteIndex = (this.currentPage - 1) * this.pageSize + index; |
|
|
this.router.navigate(['/view-details', absoluteIndex]); |
|
|
} |
|
|
} |
|
|
|