feat(server): Avoid face match with people born after file creation #4743 (#16918)

* feat(server): Avoid face matching with people born after file creation date (#4743)

* lint

* add medium tests for facial recognition

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Abhinav Valecha
2025-04-02 21:07:26 +05:30
committed by GitHub
parent 4336afd6bf
commit b621281351
10 changed files with 422 additions and 5 deletions
@@ -896,6 +896,66 @@ describe(PersonService.name, () => {
});
});
it('should match existing person if their birth date is unknown', async () => {
if (!faceStub.primaryFace1.person) {
throw new Error('faceStub.primaryFace1.person is null');
}
const faces = [
{ ...faceStub.noPerson1, distance: 0 },
{ ...faceStub.primaryFace1, distance: 0.2 },
{ ...faceStub.withBirthDate, distance: 0.3 },
] as FaceSearchResult[];
mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } });
mocks.search.searchFaces.mockResolvedValue(faces);
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1);
mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person);
await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id });
expect(mocks.person.create).not.toHaveBeenCalled();
expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1);
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
faceIds: expect.arrayContaining([faceStub.noPerson1.id]),
newPersonId: faceStub.primaryFace1.person.id,
});
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
faceIds: expect.not.arrayContaining([faceStub.face1.id]),
newPersonId: faceStub.primaryFace1.person.id,
});
});
it('should match existing person if their birth date is before file creation', async () => {
if (!faceStub.primaryFace1.person) {
throw new Error('faceStub.primaryFace1.person is null');
}
const faces = [
{ ...faceStub.noPerson1, distance: 0 },
{ ...faceStub.withBirthDate, distance: 0.2 },
{ ...faceStub.primaryFace1, distance: 0.3 },
] as FaceSearchResult[];
mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } });
mocks.search.searchFaces.mockResolvedValue(faces);
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1);
mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person);
await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id });
expect(mocks.person.create).not.toHaveBeenCalled();
expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1);
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
faceIds: expect.arrayContaining([faceStub.noPerson1.id]),
newPersonId: faceStub.withBirthDate.person?.id,
});
expect(mocks.person.reassignFaces).toHaveBeenCalledWith({
faceIds: expect.not.arrayContaining([faceStub.face1.id]),
newPersonId: faceStub.withBirthDate.person?.id,
});
});
it('should create a new person if the face is a core point with no person', async () => {
const faces = [
{ ...faceStub.noPerson1, distance: 0 },
+2
View File
@@ -483,6 +483,7 @@ export class PersonService extends BaseService {
embedding: face.faceSearch.embedding,
maxDistance: machineLearning.facialRecognition.maxDistance,
numResults: machineLearning.facialRecognition.minFaces,
minBirthDate: face.asset.fileCreatedAt,
});
// `matches` also includes the face itself
@@ -508,6 +509,7 @@ export class PersonService extends BaseService {
maxDistance: machineLearning.facialRecognition.maxDistance,
numResults: 1,
hasPerson: true,
minBirthDate: face.asset.fileCreatedAt,
});
if (matchWithPerson.length > 0) {