Py-detect / src /app /services /pydetect.service.ts
pykara's picture
fix
73566f6
export interface SessionData {
sessionId: string;
caseData?: CaseData;
accusedData?: AccusedData;
evidence?: EvidenceItem[];
questions?: any[];
responses?: any[];
notes?: any[];
report?: any;
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject, of, throwError } from 'rxjs';
import { delay, tap, catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
// Request investigation questions based on Brief Description
export interface CaseData {
caseId: string;
caseType: string;
crimeCategory: string;
crimeSubtype: string;
description: string;
location: string;
dateTime: string;
urgency: string;
officerName: string;
badgeNumber: string;
department: string;
contactInfo: string;
}
export interface AccusedData {
name: string;
age: string;
gender: string;
address: string;
occupation: string;
contactNumber: string;
relationship: string;
background: string;
previousRecords: string;
}
export interface EvidenceItem {
type: string;
description: string;
location: string;
collectedBy: string;
dateCollected: string;
chainOfCustody: string;
significance: string;
}
@Injectable({
providedIn: 'root'
})
export class PyDetectService {
private baseUrl = environment.pyDetectApiUrl;
private httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
// Session management
private currentSessionSubject = new BehaviorSubject<SessionData | null>(null);
public currentSession$ = this.currentSessionSubject.asObservable();
// Voice settings
private voiceEnabledSubject = new BehaviorSubject<boolean>(true);
public voiceEnabled$ = this.voiceEnabledSubject.asObservable();
constructor(private http: HttpClient) {
// Initialize voice settings from localStorage
const savedVoiceEnabled = localStorage.getItem('pydetect_voice_enabled');
if (savedVoiceEnabled !== null) {
this.voiceEnabledSubject.next(JSON.parse(savedVoiceEnabled));
}
}
// Request investigation questions based on Brief Description
// ============ SESSION MANAGEMENT ============
// Start a new session
startSession(briefDescription?: string): Observable<any> {
const payload: any = {};
if (briefDescription) payload.brief_description = briefDescription;
return this.http.post(`${this.baseUrl}/start_session`, payload, this.httpOptions)
.pipe(
tap(response => {
const responseData = response as any;
const sessionData: SessionData = {
sessionId: responseData.session_id || this.generateSessionId(),
caseData: undefined,
accusedData: undefined,
evidence: [],
questions: [],
responses: [],
notes: [],
report: undefined
};
this.currentSessionSubject.next(sessionData);
this.saveSessionToStorage(sessionData);
}),
catchError(this.handleError)
);
}
// Get current session
getCurrentSession(): SessionData | null {
return this.currentSessionSubject.value;
}
// Update session data
updateSession(updates: Partial<SessionData>): void {
const current = this.currentSessionSubject.value;
if (current) {
const updated = { ...current, ...updates };
this.currentSessionSubject.next(updated);
this.saveSessionToStorage(updated);
}
}
// ============ CASE MANAGEMENT ============
// Submit complete case details
submitCaseDetails(sessionId: string, caseData: CaseData, briefDescription?: string): Observable<any> {
const payload: any = {
session_id: sessionId,
case_data: caseData,
timestamp: new Date().toISOString()
};
// ...existing code...
return this.http.post(`${this.baseUrl}/submit_case`, payload, this.httpOptions)
.pipe(
tap(response => {
this.updateSession({ caseData: caseData });
}),
catchError(this.handleError)
);
}
// ============ BODY LANGUAGE EXPLANATION ============
/**
* Fetches body language explanation for a given cue from backend.
* @param cue The body language cue to explain
* @returns Observable<{ explanation: string }>
*/
bodyLanguageExplain(cue: string): Observable<{ meaning?: string; explanation?: string }> {
const payload = { cue };
return this.http.post<{ meaning?: string; explanation?: string }>(`${this.baseUrl}/body_language_explain`, payload, this.httpOptions)
.pipe(catchError((error): Observable<{ meaning?: string; explanation?: string }> => {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
errorMessage = `Client Error: ${error.error.message}`;
} else {
errorMessage = `Server Error: ${error.status} - ${error.message}`;
}
// Return an object with explanation only for error case
return of({ explanation: errorMessage });
}));
}
// Submit accused details
submitAccused(sessionId: string, accused: AccusedData, additionalData?: any): Observable<any> {
const currentSession = this.getCurrentSession();
const payload = {
session_id: sessionId,
accused: accused,
crime: additionalData?.crime || currentSession?.caseData?.crimeCategory || '',
profile: currentSession?.caseData || additionalData?.profile || {},
evidence: currentSession?.evidence || additionalData?.evidence || [],
timestamp: new Date().toISOString(),
...additionalData
};
return this.http.post(`${this.baseUrl}/submit_accused`, payload, this.httpOptions)
.pipe(
tap(response => {
this.updateSession({ accusedData: accused });
}),
catchError(this.handleError)
);
}
// ============ EVIDENCE MANAGEMENT ============
// Submit evidence
submitEvidence(sessionId: string, evidence: EvidenceItem[]): Observable<any> {
const payload = {
session_id: sessionId,
evidence: evidence.map(item => ({
...item,
timestamp: new Date().toISOString(),
evidence_id: this.generateEvidenceId()
}))
};
return this.http.post(`${this.baseUrl}/submit_evidence`, payload, this.httpOptions)
.pipe(
tap(response => {
const currentSession = this.getCurrentSession();
const updatedEvidence = [...(currentSession?.evidence || []), ...evidence];
this.updateSession({ evidence: updatedEvidence });
}),
catchError(this.handleError)
);
}
// Add single evidence item
addEvidenceItem(sessionId: string, evidenceItem: EvidenceItem): Observable<any> {
return this.submitEvidence(sessionId, [evidenceItem]);
}
// ============ INVESTIGATION NOTES ============
// Add investigation note
addNote(sessionId: string, note: string, category: string = 'general'): Observable<any> {
const noteData = {
session_id: sessionId,
note: note,
timestamp: new Date().toISOString(),
category: category,
note_id: this.generateNoteId(),
officer: this.getCurrentSession()?.caseData?.officerName || 'Unknown Officer'
};
return this.http.post(`${this.baseUrl}/add_note`, noteData, this.httpOptions)
.pipe(
tap(response => {
const currentSession = this.getCurrentSession();
const updatedNotes = [...(currentSession?.notes || []), noteData];
this.updateSession({ notes: updatedNotes });
}),
catchError(this.handleError)
);
}
// ============ AI QUESTIONING SYSTEM ============
// Get context-aware AI questions
askQuestion(sessionId: string, crimeType?: string, briefDescription?: string): Observable<any> {
// ...existing code...
const queryParams = [`session_id=${encodeURIComponent(sessionId)}`];
// Always send crimeType, even if empty
queryParams.push(`crime_type=${encodeURIComponent(crimeType ?? '')}`);
// Always send briefDescription, even if empty
queryParams.push(`brief_description=${encodeURIComponent(briefDescription ?? '')}`);
const queryString = queryParams.join('&');
return this.http.get(`${this.baseUrl}/ask_question?${queryString}`, this.httpOptions)
.pipe(catchError(this.handleError));
}
// Submit response to AI question
submitResponse(sessionId: string, text: string, questionId?: string, timing?: {
answer_start_at?: number;
answer_end_at?: number;
duration_ms?: number;
mode?: string;
}): Observable<any> {
const responseData: any = {
session_id: sessionId,
text: text,
question_id: questionId,
timestamp: new Date().toISOString(),
response_id: this.generateResponseId()
};
if (timing) {
if (timing.answer_start_at) responseData.answer_start_at = timing.answer_start_at;
if (timing.answer_end_at) responseData.answer_end_at = timing.answer_end_at;
if (typeof timing.duration_ms === 'number') responseData.duration_ms = timing.duration_ms;
if (timing.mode) responseData.mode = timing.mode;
}
return this.http.post(`${this.baseUrl}/submit_response`, responseData, this.httpOptions)
.pipe(
tap(response => {
const currentSession = this.getCurrentSession();
const updatedResponses = [...(currentSession?.responses || []), responseData];
this.updateSession({ responses: updatedResponses });
}),
catchError(this.handleError)
);
}
// Stream a single face frame for nonverbal analysis
faceFrame(sessionId: string, frameDataUrl: string): Observable<any> {
const payload = { session_id: sessionId, frame: frameDataUrl };
return this.http.post(`${this.baseUrl}/face_frame`, payload, this.httpOptions)
.pipe(catchError(this.handleError));
}
// ============ REPORTING SYSTEM ============
// Get comprehensive report
getReport(sessionId: string, reportType: string = 'complete'): Observable<any> {
return this.http.get(`${this.baseUrl}/get_report/${sessionId}?type=${reportType}`, this.httpOptions)
.pipe(
tap(response => {
this.updateSession({ report: response });
}),
catchError(this.handleError)
);
}
// Generate summary report
generateSummary(sessionId: string): Observable<any> {
const currentSession = this.getCurrentSession();
const summaryData = {
session_id: sessionId,
case_data: currentSession?.caseData,
accused_data: currentSession?.accusedData,
evidence_count: currentSession?.evidence?.length || 0,
questions_answered: currentSession?.responses?.length || 0,
notes_count: currentSession?.notes?.length || 0,
timestamp: new Date().toISOString()
};
return this.http.post(`${this.baseUrl}/generate_summary`, summaryData, this.httpOptions)
.pipe(catchError(this.handleError));
}
// ============ VOICE FUNCTIONALITY ============
// Toggle voice functionality
toggleVoice(): void {
const current = this.voiceEnabledSubject.value;
this.voiceEnabledSubject.next(!current);
localStorage.setItem('pydetect_voice_enabled', JSON.stringify(!current));
}
// Check if voice is enabled
isVoiceEnabled(): boolean {
return this.voiceEnabledSubject.value;
}
// ============ AUTHENTICATION ============
// Sign in
signIn(email: string, password: string): Observable<any> {
return this.http.post(`${this.baseUrl}/sign-in`, {
email: email,
password: password
}, this.httpOptions).pipe(catchError(this.handleError));
}
// Sign up
signUp(name: string, email: string, password: string, role: string = 'investigator'): Observable<any> {
return this.http.post(`${this.baseUrl}/sign-up`, {
name: name,
email: email,
password: password,
role: role
}, this.httpOptions).pipe(catchError(this.handleError));
}
// Health check
healthCheck(): Observable<any> {
return this.http.get(`${this.baseUrl}/health`, this.httpOptions)
.pipe(catchError(this.handleError));
}
// ============ UTILITY METHODS ============
// Generate unique session ID
private generateSessionId(): string {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Generate unique evidence ID
private generateEvidenceId(): string {
return 'evidence_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Generate unique note ID
private generateNoteId(): string {
return 'note_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Generate unique response ID
private generateResponseId(): string {
return 'response_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Save session to localStorage
private saveSessionToStorage(session: SessionData): void {
try {
localStorage.setItem('pydetect_current_session', JSON.stringify(session));
} catch (error) {
}
}
// Load session from localStorage
loadSessionFromStorage(): SessionData | null {
try {
const stored = localStorage.getItem('pydetect_current_session');
if (stored) {
const session = JSON.parse(stored);
this.currentSessionSubject.next(session);
return session;
}
} catch (error) {
}
return null;
}
// Clear current session
clearSession(): void {
this.currentSessionSubject.next(null);
localStorage.removeItem('pydetect_current_session');
}
// Error handling
private handleError(error: any): Observable<never> {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
errorMessage = `Client Error: ${error.error.message}`;
} else {
errorMessage = `Server Error: ${error.status} - ${error.message}`;
}
return throwError(() => new Error(errorMessage));
}
// ============ DATA VALIDATION ============
// Validate case data
validateCaseData(caseData: Partial<CaseData>): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!caseData.caseId) errors.push('Case ID is required');
if (!caseData.crimeCategory) errors.push('Crime category is required');
if (!caseData.description) errors.push('Description is required');
if (!caseData.officerName) errors.push('Officer name is required');
if (!caseData.badgeNumber) errors.push('Badge number is required');
return {
isValid: errors.length === 0,
errors: errors
};
}
// Validate accused data
validateAccusedData(accusedData: Partial<AccusedData>): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!accusedData.name) errors.push('Accused name is required');
if (!accusedData.age) errors.push('Age is required');
if (!accusedData.gender) errors.push('Gender is required');
return {
isValid: errors.length === 0,
errors: errors
};
}
// ============ STATISTICS & ANALYTICS ============
// Get session statistics
getSessionStats(): any {
const session = this.getCurrentSession();
if (!session) return null;
return {
sessionId: session.sessionId,
questionsAnswered: session.responses?.length || 0,
evidenceItems: session.evidence?.length || 0,
notesAdded: session.notes?.length || 0,
hasAccusedData: !!session.accusedData,
hasCaseData: !!session.caseData,
hasReport: !!session.report
};
}
// Request investigation questions from backend
getInvestigationQuestions(sessionId: string, crimeType: string, briefDescription: string): Observable<any> {
return this.http.get(
`${this.baseUrl}/ask_question?session_id=${sessionId}&crime_type=${encodeURIComponent(crimeType)}&brief_description=${encodeURIComponent(briefDescription)}`,
this.httpOptions
);
}
}