diff --git a/admin-frontend/src/pages/Identify.tsx b/admin-frontend/src/pages/Identify.tsx index 699f93c..0625566 100644 --- a/admin-frontend/src/pages/Identify.tsx +++ b/admin-frontend/src/pages/Identify.tsx @@ -692,7 +692,19 @@ export default function Identify() { const handleIdentify = async () => { - if (!currentFace) return + console.log('handleIdentify called', { currentFace, personId, firstName, lastName, canIdentify }) + + if (!currentFace) { + console.warn('handleIdentify: No current face') + return + } + + // Validate that we have either a person ID or both first and last name + if (!personId && (!firstName.trim() || !lastName.trim())) { + alert('Please select an existing person or enter first name and last name.') + return + } + setBusy(true) const trimmedFirstName = firstName.trim() const trimmedLastName = lastName.trim() @@ -719,7 +731,10 @@ export default function Identify() { payload.date_of_birth = trimmedDob } } + + console.log('Identifying face:', currentFace.id, 'with payload:', payload) await facesApi.identify(currentFace.id, payload) + // Optimistic: remove identified faces from list const identifiedSet = new Set([currentFace.id, ...additional]) const remaining = faces.filter((f) => !identifiedSet.has(f.id)) @@ -741,6 +756,10 @@ export default function Identify() { } // Don't clear form - let the useEffect handle restoring/clearing when face changes + } catch (error: any) { + console.error('Error identifying face:', error) + const errorMessage = error.response?.data?.detail || error.message || 'Failed to identify face. Please try again.' + alert(errorMessage) } finally { setBusy(false) } @@ -1378,13 +1397,15 @@ export default function Identify() { disabled={!!personId} />
- - - + +
diff --git a/backend/api/faces.py b/backend/api/faces.py index d9dfff3..64f9f4f 100644 --- a/backend/api/faces.py +++ b/backend/api/faces.py @@ -2,6 +2,8 @@ from __future__ import annotations +from datetime import datetime + from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi.responses import FileResponse, Response from rq import Queue @@ -306,12 +308,14 @@ def identify_face( status_code=status.HTTP_400_BAD_REQUEST, detail="first_name and last_name are required to create a person", ) + # Explicitly set created_date to ensure it's a valid datetime object person = Person( first_name=first_name, last_name=last_name, middle_name=middle_name, maiden_name=maiden_name, date_of_birth=request.date_of_birth, + created_date=datetime.utcnow(), ) db.add(person) db.flush() # get person.id diff --git a/backend/api/pending_identifications.py b/backend/api/pending_identifications.py index 110ab59..889b627 100644 --- a/backend/api/pending_identifications.py +++ b/backend/api/pending_identifications.py @@ -330,12 +330,14 @@ def approve_deny_pending_identifications( # Create person if doesn't exist created_person = False if not person: + # Explicitly set created_date to ensure it's a valid datetime object person = Person( first_name=row.first_name, last_name=row.last_name, middle_name=row.middle_name, maiden_name=row.maiden_name, date_of_birth=row.date_of_birth, + created_date=datetime.utcnow(), ) main_db.add(person) main_db.flush() # get person.id diff --git a/backend/api/people.py b/backend/api/people.py index 27aadc4..ad6d774 100644 --- a/backend/api/people.py +++ b/backend/api/people.py @@ -2,6 +2,7 @@ from __future__ import annotations +from datetime import datetime from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query, Response, status @@ -118,12 +119,14 @@ def create_person(request: PersonCreateRequest, db: Session = Depends(get_db)) - last_name = request.last_name.strip() middle_name = request.middle_name.strip() if request.middle_name else None maiden_name = request.maiden_name.strip() if request.maiden_name else None + # Explicitly set created_date to ensure it's a valid datetime object person = Person( first_name=first_name, last_name=last_name, middle_name=middle_name, maiden_name=maiden_name, date_of_birth=request.date_of_birth, + created_date=datetime.utcnow(), ) db.add(person) try: diff --git a/backend/db/models.py b/backend/db/models.py index 45fe127..6b85d01 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -10,6 +10,7 @@ from sqlalchemy import ( Column, Date, DateTime, + String, ForeignKey, Index, Integer, @@ -37,19 +38,37 @@ class PrismaCompatibleDateTime(TypeDecorator): Prisma's SQLite driver has issues with microseconds in datetime strings. This type ensures datetimes are stored in ISO format without microseconds: 'YYYY-MM-DD HH:MM:SS' instead of 'YYYY-MM-DD HH:MM:SS.ffffff' + + Uses String as the underlying type for SQLite to have full control over the format. """ - impl = DateTime + impl = String cache_ok = True def process_bind_param(self, value, dialect): - """Convert Python datetime to SQL string format.""" + """Convert Python datetime to SQL string format without microseconds.""" if value is None: return None if isinstance(value, datetime): # Strip microseconds and format as ISO string without microseconds # This ensures Prisma can read it correctly - value = value.replace(microsecond=0) - return value.strftime('%Y-%m-%d %H:%M:%S') + return value.replace(microsecond=0).strftime('%Y-%m-%d %H:%M:%S') + # If it's already a string, ensure it doesn't have microseconds + if isinstance(value, str): + try: + # Parse and reformat to remove microseconds + if '.' in value: + # Has microseconds or timezone info - strip them + dt = datetime.strptime(value.split('.')[0], '%Y-%m-%d %H:%M:%S') + elif 'T' in value: + # ISO format with T + dt = datetime.fromisoformat(value.replace('Z', '+00:00').split('.')[0]) + else: + # Already in correct format + return value + return dt.strftime('%Y-%m-%d %H:%M:%S') + except (ValueError, TypeError): + # If parsing fails, return as-is + return value return value def process_result_value(self, value, dialect): diff --git a/backend/services/video_service.py b/backend/services/video_service.py index b73423b..0009626 100644 --- a/backend/services/video_service.py +++ b/backend/services/video_service.py @@ -224,12 +224,14 @@ def identify_person_in_video( if existing_person: person = existing_person else: + # Explicitly set created_date to ensure it's a valid datetime object person = Person( first_name=first_name, last_name=last_name, middle_name=middle_name, maiden_name=maiden_name, date_of_birth=date_of_birth, + created_date=datetime.utcnow(), ) db.add(person) db.flush() # Get person.id