feat: Improve face identification process with validation and error handling
This commit enhances the face identification process by adding validation checks for person ID and names, ensuring that users provide necessary information before proceeding. It also introduces detailed logging for better debugging and user feedback during the identification process. Additionally, error handling is improved to provide user-friendly messages in case of failures, enhancing the overall user experience.
This commit is contained in:
parent
0b95cd2492
commit
c69604573d
@ -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} />
|
||||
</div>
|
||||
<div className="col-span-2 flex gap-2 mt-2">
|
||||
<button disabled={!canIdentify || busy}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canIdentify || busy}
|
||||
onClick={handleIdentify}
|
||||
className={`px-3 py-2 rounded text-white ${canIdentify && !busy ? 'bg-indigo-600 hover:bg-indigo-700' : 'bg-gray-400 cursor-not-allowed'}`}>
|
||||
{busy ? 'Identifying...' : 'Identify'}
|
||||
</button>
|
||||
<button onClick={() => setCurrentIdx((i) => Math.max(0, i - 1))} className="px-3 py-2 rounded border">Back</button>
|
||||
<button onClick={() => setCurrentIdx((i) => Math.min(faces.length - 1, i + 1))} className="px-3 py-2 rounded border">Next</button>
|
||||
<button type="button" onClick={() => setCurrentIdx((i) => Math.max(0, i - 1))} className="px-3 py-2 rounded border">Back</button>
|
||||
<button type="button" onClick={() => setCurrentIdx((i) => Math.min(faces.length - 1, i + 1))} className="px-3 py-2 rounded border">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user