refactor: Improve person creation and identification logic with optional fields handling
This commit refactors the person creation and identification logic to handle optional fields more effectively. The `date_of_birth` field in the `PersonCreateRequest` schema is now optional, and the frontend has been updated to trim whitespace from name fields before submission. Additionally, the identification logic has been enhanced to ensure that only non-empty names are considered valid. Documentation has been updated to reflect these changes.
This commit is contained in:
parent
709be7555a
commit
999e79f859
@ -28,7 +28,7 @@ export interface PersonCreateRequest {
|
||||
last_name: string
|
||||
middle_name?: string
|
||||
maiden_name?: string
|
||||
date_of_birth: string
|
||||
date_of_birth?: string | null
|
||||
}
|
||||
|
||||
export interface PersonUpdateRequest {
|
||||
|
||||
@ -82,8 +82,10 @@ export default function Identify() {
|
||||
const restorationCompleteRef = useRef(false)
|
||||
|
||||
const canIdentify = useMemo(() => {
|
||||
return Boolean((personId && currentFace) || (firstName && lastName && dob && currentFace))
|
||||
}, [personId, firstName, lastName, dob, currentFace])
|
||||
if (!currentFace) return false
|
||||
if (personId) return true
|
||||
return Boolean(firstName.trim() && lastName.trim())
|
||||
}, [personId, firstName, lastName, currentFace])
|
||||
|
||||
const loadFaces = async (clearState: boolean = false, ignorePhotoIds: boolean = false) => {
|
||||
setLoadingFaces(true)
|
||||
@ -589,6 +591,11 @@ export default function Identify() {
|
||||
const handleIdentify = async () => {
|
||||
if (!currentFace) return
|
||||
setBusy(true)
|
||||
const trimmedFirstName = firstName.trim()
|
||||
const trimmedLastName = lastName.trim()
|
||||
const trimmedMiddleName = middleName.trim()
|
||||
const trimmedMaidenName = maidenName.trim()
|
||||
const trimmedDob = dob.trim()
|
||||
const additional = Object.entries(selectedSimilar)
|
||||
.filter(([, v]) => v)
|
||||
.map(([k]) => Number(k))
|
||||
@ -597,11 +604,17 @@ export default function Identify() {
|
||||
if (personId) {
|
||||
payload.person_id = personId
|
||||
} else {
|
||||
payload.first_name = firstName
|
||||
payload.last_name = lastName
|
||||
payload.middle_name = middleName || undefined
|
||||
payload.maiden_name = maidenName || undefined
|
||||
payload.date_of_birth = dob
|
||||
payload.first_name = trimmedFirstName
|
||||
payload.last_name = trimmedLastName
|
||||
if (trimmedMiddleName) {
|
||||
payload.middle_name = trimmedMiddleName
|
||||
}
|
||||
if (trimmedMaidenName) {
|
||||
payload.maiden_name = trimmedMaidenName
|
||||
}
|
||||
if (trimmedDob) {
|
||||
payload.date_of_birth = trimmedDob
|
||||
}
|
||||
}
|
||||
await facesApi.identify(currentFace.id, payload)
|
||||
// Optimistic: remove identified faces from list
|
||||
@ -948,7 +961,7 @@ export default function Identify() {
|
||||
disabled={!!personId} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700">Date of Birth *</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Date of Birth (optional)</label>
|
||||
<input type="date" value={dob} onChange={(e) => {
|
||||
setDob(e.target.value)
|
||||
setPersonId(undefined) // Clear person selection when typing
|
||||
|
||||
@ -11,17 +11,39 @@ from sqlalchemy import inspect
|
||||
from src.web.db.session import engine, get_database_url
|
||||
from src.web.db.models import Base
|
||||
|
||||
# Ordered list ensures foreign-key dependents drop first
|
||||
TARGET_TABLES = [
|
||||
"photo_favorites",
|
||||
"phototaglinkage",
|
||||
"person_encodings",
|
||||
"faces",
|
||||
"tags",
|
||||
"photos",
|
||||
"people",
|
||||
]
|
||||
|
||||
|
||||
def drop_all_tables():
|
||||
"""Drop all tables from the database."""
|
||||
db_url = get_database_url()
|
||||
print(f"Connecting to database: {db_url}")
|
||||
|
||||
# Drop all tables
|
||||
print("\nDropping all tables...")
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
inspector = inspect(engine)
|
||||
existing_tables = set(inspector.get_table_names())
|
||||
|
||||
print("✅ All tables dropped successfully!")
|
||||
print("\nDropping selected tables...")
|
||||
for table_name in TARGET_TABLES:
|
||||
if table_name not in Base.metadata.tables:
|
||||
print(f" ⚠️ Table '{table_name}' not found in metadata, skipping.")
|
||||
continue
|
||||
if table_name not in existing_tables:
|
||||
print(f" ℹ️ Table '{table_name}' does not exist in database, skipping.")
|
||||
continue
|
||||
table = Base.metadata.tables[table_name]
|
||||
print(f" 🗑️ Dropping '{table_name}'...")
|
||||
table.drop(bind=engine, checkfirst=True)
|
||||
|
||||
print("✅ Selected tables dropped successfully!")
|
||||
print("\nYou can now recreate tables using:")
|
||||
print(" python scripts/recreate_tables_web.py")
|
||||
|
||||
|
||||
@ -290,16 +290,20 @@ def identify_face(
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="person_id not found")
|
||||
else:
|
||||
# Validate required fields for creation
|
||||
if not (request.first_name and request.last_name and request.date_of_birth):
|
||||
first_name = (request.first_name or "").strip()
|
||||
last_name = (request.last_name or "").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
|
||||
if not (first_name and last_name):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="first_name, last_name and date_of_birth are required to create a person",
|
||||
detail="first_name and last_name are required to create a person",
|
||||
)
|
||||
person = Person(
|
||||
first_name=request.first_name,
|
||||
last_name=request.last_name,
|
||||
middle_name=request.middle_name,
|
||||
maiden_name=request.maiden_name,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
middle_name=middle_name,
|
||||
maiden_name=maiden_name,
|
||||
date_of_birth=request.date_of_birth,
|
||||
)
|
||||
db.add(person)
|
||||
|
||||
@ -304,41 +304,32 @@ def approve_deny_pending_identifications(
|
||||
|
||||
# Check if person already exists (by name and DOB)
|
||||
# Match the unique constraint: first_name, last_name, middle_name, maiden_name, date_of_birth
|
||||
person = None
|
||||
# Build query with proper None handling
|
||||
query = main_db.query(Person).filter(
|
||||
Person.first_name == row.first_name,
|
||||
Person.last_name == row.last_name,
|
||||
)
|
||||
# Handle optional fields - use IS NULL for None values
|
||||
if row.middle_name:
|
||||
query = query.filter(Person.middle_name == row.middle_name)
|
||||
else:
|
||||
query = query.filter(Person.middle_name.is_(None))
|
||||
|
||||
if row.maiden_name:
|
||||
query = query.filter(Person.maiden_name == row.maiden_name)
|
||||
else:
|
||||
query = query.filter(Person.maiden_name.is_(None))
|
||||
|
||||
if row.date_of_birth:
|
||||
# Build query with proper None handling
|
||||
query = main_db.query(Person).filter(
|
||||
Person.first_name == row.first_name,
|
||||
Person.last_name == row.last_name,
|
||||
Person.date_of_birth == row.date_of_birth
|
||||
)
|
||||
# Handle optional fields - use IS NULL for None values
|
||||
if row.middle_name:
|
||||
query = query.filter(Person.middle_name == row.middle_name)
|
||||
else:
|
||||
query = query.filter(Person.middle_name.is_(None))
|
||||
|
||||
if row.maiden_name:
|
||||
query = query.filter(Person.maiden_name == row.maiden_name)
|
||||
else:
|
||||
query = query.filter(Person.maiden_name.is_(None))
|
||||
|
||||
person = query.first()
|
||||
query = query.filter(Person.date_of_birth == row.date_of_birth)
|
||||
else:
|
||||
query = query.filter(Person.date_of_birth.is_(None))
|
||||
|
||||
person = query.first()
|
||||
|
||||
# Create person if doesn't exist
|
||||
created_person = False
|
||||
if not person:
|
||||
if not row.date_of_birth:
|
||||
errors.append(f"Pending identification {decision.id} missing date_of_birth (required for person creation)")
|
||||
auth_db.execute(text("""
|
||||
UPDATE pending_identifications
|
||||
SET status = 'denied', updated_at = :updated_at
|
||||
WHERE id = :id
|
||||
"""), {"id": decision.id, "updated_at": datetime.utcnow()})
|
||||
auth_db.commit()
|
||||
denied_count += 1
|
||||
continue
|
||||
|
||||
person = Person(
|
||||
first_name=row.first_name,
|
||||
last_name=row.last_name,
|
||||
|
||||
@ -95,11 +95,15 @@ def list_people_with_faces(
|
||||
@router.post("", response_model=PersonResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_person(request: PersonCreateRequest, db: Session = Depends(get_db)) -> PersonResponse:
|
||||
"""Create a new person."""
|
||||
first_name = request.first_name.strip()
|
||||
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
|
||||
person = Person(
|
||||
first_name=request.first_name,
|
||||
last_name=request.last_name,
|
||||
middle_name=request.middle_name,
|
||||
maiden_name=request.maiden_name,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
middle_name=middle_name,
|
||||
maiden_name=maiden_name,
|
||||
date_of_birth=request.date_of_birth,
|
||||
)
|
||||
db.add(person)
|
||||
|
||||
@ -30,7 +30,7 @@ class PersonCreateRequest(BaseModel):
|
||||
last_name: str = Field(..., min_length=1)
|
||||
middle_name: Optional[str] = None
|
||||
maiden_name: Optional[str] = None
|
||||
date_of_birth: date
|
||||
date_of_birth: Optional[date] = None
|
||||
|
||||
|
||||
class PeopleListResponse(BaseModel):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user