feat(evidence): historiser l’usage humain d’identité
This commit is contained in:
parent
78f0e45450
commit
116b08f2f4
31 changed files with 792 additions and 2126 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
### Added
|
||||
|
||||
- Assistant de création de personne en sept étapes séparant la révision OCR,
|
||||
la projection facultative et les relations factuelles avant confirmation.
|
||||
- Projection contrôlée des champs OCR confirmés vers les attributs structurés
|
||||
d’une personne, avec provenance append-only, conflits explicites et rollback.
|
||||
- Saisie et consultation, depuis la fiche preuve, d’un historique immuable
|
||||
|
|
@ -40,6 +42,12 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- Schéma de création directe aligné sur la V19, avec champs structurés,
|
||||
provenance des projections et garde SQLite identique à la migration V19.
|
||||
- Migration V20 pour l’évaluation humaine append-only de l’usage d’identité,
|
||||
distincte de l’authenticité et sans automatisme OCR.
|
||||
- Consultation V20 sur la fiche preuve et test GTK des relations et champs
|
||||
structurés affichés sur la fiche Person.
|
||||
- Rafraîchissement immédiat de Workspace après import ou révision OCR et
|
||||
persistance des données sur l’UUID définitif de la preuve, y compris après
|
||||
fermeture et réouverture de SQLite.
|
||||
|
|
|
|||
29
Makefile
29
Makefile
|
|
@ -198,6 +198,8 @@ TEST_WORKSPACE_IDENTITY_OCR_GTK := tests/test_workspace_identity_ocr_gtk
|
|||
TEST_DIALOG_GEOMETRY_GTK := tests/test_dialog_geometry_gtk
|
||||
TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK := tests/test_person_factual_relation_editor_gtk
|
||||
TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK := tests/test_document_authenticity_editor_gtk
|
||||
TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK := tests/test_document_identity_misuse_editor_gtk
|
||||
TEST_PERSON_DETAILS_TRACEABILITY_GTK := tests/test_person_details_traceability_gtk
|
||||
TEST_PERSON_OCR_PROJECTION := tests/test_person_ocr_projection
|
||||
TEST_PERSON_OCR_PROJECTION_EDITOR_GTK := tests/test_person_ocr_projection_editor_gtk
|
||||
FAKE_DOCUMENT_TOOL := tests/fake_document_tool
|
||||
|
|
@ -238,6 +240,26 @@ $(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK): \
|
|||
src/database/error.c src/core/relation_type_normalizer.c
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK): \
|
||||
tests/test_document_identity_misuse_editor_gtk.c \
|
||||
src/views/document_identity_misuse_editor.c \
|
||||
src/core/document_identity_misuse_service.c src/models/identity_traceability.c \
|
||||
src/models/identity_ocr.c src/dao/identity_ocr_dao.c \
|
||||
src/dao/identity_traceability_dao.c src/database/database.c \
|
||||
src/database/schema.c src/database/statement.c src/database/transaction.c \
|
||||
src/database/error.c src/core/relation_type_normalizer.c
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
$(TEST_PERSON_DETAILS_TRACEABILITY_GTK): \
|
||||
tests/test_person_details_traceability_gtk.c \
|
||||
src/widgets/entity_details_panel.c src/models/entity_record.c \
|
||||
src/models/evidence_record.c src/models/identity_traceability.c \
|
||||
src/models/person_role_assignment.c src/views/person_vocabulary_adapter.c
|
||||
$(CC) $(CFLAGS) $^ src/dao/identity_traceability_dao.c \
|
||||
src/database/database.c src/database/schema.c src/database/statement.c \
|
||||
src/database/transaction.c src/database/error.c \
|
||||
src/core/relation_type_normalizer.c -o $@ $(LDFLAGS)
|
||||
|
||||
PERSON_OCR_PROJECTION_SOURCES := src/models/person_ocr_projection.c \
|
||||
src/core/person_ocr_projection_mapping.c src/core/person_ocr_projection_service.c \
|
||||
src/dao/person_ocr_projection_dao.c src/dao/identity_ocr_dao.c \
|
||||
|
|
@ -273,6 +295,7 @@ $(TEST_IDENTITY_OCR_PREPROCESSOR): tests/test_identity_ocr_preprocessor.c \
|
|||
$(TEST_IDENTITY_TRACEABILITY): tests/test_identity_traceability.c \
|
||||
src/models/identity_traceability.c src/dao/identity_traceability_dao.c \
|
||||
src/core/document_authenticity_service.c \
|
||||
src/core/document_identity_misuse_service.c \
|
||||
src/views/person_vocabulary_adapter.c \
|
||||
src/views/person_factual_relation_editor.c \
|
||||
src/models/person_role_assignment.c \
|
||||
|
|
@ -1238,6 +1261,8 @@ test: \
|
|||
$(TEST_DIALOG_GEOMETRY_GTK) \
|
||||
$(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK) \
|
||||
$(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK) \
|
||||
$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK) \
|
||||
$(TEST_PERSON_DETAILS_TRACEABILITY_GTK) \
|
||||
$(TEST_PERSON_OCR_PROJECTION) \
|
||||
$(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK)
|
||||
@echo "Exécution des tests..."
|
||||
|
|
@ -1337,6 +1362,8 @@ test: \
|
|||
@$(TEST_DIALOG_GEOMETRY_GTK)
|
||||
@$(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK)
|
||||
@$(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK)
|
||||
@$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK)
|
||||
@$(TEST_PERSON_DETAILS_TRACEABILITY_GTK)
|
||||
@$(TEST_PERSON_OCR_PROJECTION)
|
||||
@$(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
|
@ -1440,6 +1467,8 @@ clean:
|
|||
$(TEST_DIALOG_GEOMETRY_GTK) \
|
||||
$(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK) \
|
||||
$(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK) \
|
||||
$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK) \
|
||||
$(TEST_PERSON_DETAILS_TRACEABILITY_GTK) \
|
||||
$(TEST_PERSON_OCR_PROJECTION) \
|
||||
$(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK) \
|
||||
$(FAKE_DOCUMENT_TOOL)
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -390,6 +390,18 @@ make clean
|
|||
|
||||
Le projet est compilé en C17 avec les avertissements traités comme des erreurs.
|
||||
|
||||
L’assistant de création d’une personne suit sept étapes distinctes :
|
||||
`Personne`, `Rôles`, `Preuves`, `OCR identité`, `Projection OCR`,
|
||||
`Relations factuelles`, puis `Confirmation`. La révision OCR conserve les
|
||||
valeurs brute, normalisée, corrigée et confirmée avant toute projection. La
|
||||
projection et les relations sont facultatives, vides par défaut et résumées
|
||||
séparément avant l’unique écriture transactionnelle finale.
|
||||
|
||||
Le schéma SQLite courant est la V20. Une création neuve installe directement
|
||||
les extensions V19 et V20 ; une base V19 utilise `database/schema_v20.sql`.
|
||||
La fiche de preuve sépare l’authenticité de l’évaluation humaine de l’usage
|
||||
d’identité, dont l’historique est append-only et jamais produit par l’OCR.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
|
@ -405,7 +417,8 @@ Vérifications recommandées avant chaque commit :
|
|||
```bash
|
||||
make clean
|
||||
make -j8
|
||||
make -j8 test
|
||||
make check-source-size
|
||||
DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
|
|
@ -427,6 +440,9 @@ G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk
|
|||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_factual_relation_editor_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_document_authenticity_editor_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_ocr_projection_editor_gtk
|
||||
```
|
||||
|
||||
Les tests emploient uniquement des documents `SPECIMEN`, des fichiers
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/******************************************************************************
|
||||
* Labfy Investigation
|
||||
*
|
||||
* Extensions idempotentes du schéma SQLite courant V9
|
||||
* Extensions idempotentes du schéma SQLite courant V20
|
||||
******************************************************************************/
|
||||
|
||||
/*
|
||||
|
|
@ -436,3 +436,64 @@ BEFORE UPDATE ON identity_field_observations BEGIN
|
|||
WHEN NEW.confirmation_state='human_confirmed' AND NEW.confirmed_value IS NULL
|
||||
THEN RAISE(ABORT,'confirmed value required') END;
|
||||
END;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS person_profile_fields(
|
||||
person_id TEXT NOT NULL,field_code TEXT NOT NULL,value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL CHECK(length(updated_at)=20),
|
||||
PRIMARY KEY(person_id,field_code),
|
||||
CHECK(field_code IN('declared_name','surname','given_names','birth_date',
|
||||
'birth_place','nationality','sex_as_printed','address_as_printed')),
|
||||
CHECK(length(trim(value))>0),
|
||||
FOREIGN KEY(person_id) REFERENCES entites(id) ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS person_ocr_field_projections(
|
||||
id TEXT PRIMARY KEY,person_id TEXT NOT NULL,person_field_code TEXT NOT NULL,
|
||||
previous_value TEXT,new_value TEXT NOT NULL,evidence_id TEXT NOT NULL,
|
||||
ocr_run_id TEXT NOT NULL,ocr_field_id TEXT NOT NULL,ocr_field_code TEXT NOT NULL,
|
||||
value_quality TEXT NOT NULL CHECK(value_quality IN('complete','partial')),
|
||||
review_status TEXT NOT NULL CHECK(review_status IN('accepted','modified')),
|
||||
strategy TEXT NOT NULL CHECK(strategy IN('fill_empty','replace_existing')),
|
||||
projected_at TEXT NOT NULL CHECK(length(projected_at)=20),
|
||||
origin TEXT NOT NULL CHECK(origin='human'),
|
||||
FOREIGN KEY(person_id) REFERENCES entites(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(ocr_field_id) REFERENCES identity_field_observations(id)
|
||||
ON DELETE RESTRICT);
|
||||
CREATE INDEX IF NOT EXISTS idx_person_projection_person
|
||||
ON person_ocr_field_projections(person_id,projected_at,id);
|
||||
CREATE TRIGGER IF NOT EXISTS projection_v19_consistency
|
||||
BEFORE INSERT ON person_ocr_field_projections BEGIN
|
||||
SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM identity_field_observations f
|
||||
WHERE f.id=NEW.ocr_field_id AND f.ocr_run_id=NEW.ocr_run_id
|
||||
AND f.evidence_id=NEW.evidence_id AND f.field_code=NEW.ocr_field_code
|
||||
AND f.confirmation_state='human_confirmed'
|
||||
AND length(trim(f.confirmed_value))>0
|
||||
AND f.review_status IN('accepted','modified')
|
||||
AND f.value_quality IN('complete','partial')
|
||||
AND f.confirmed_value=NEW.new_value)
|
||||
THEN RAISE(ABORT,'OCR field is no longer projectable') END;
|
||||
END;
|
||||
|
||||
/* V20 — évaluations humaines distinctes de l’usage abusif d’identité. */
|
||||
CREATE TABLE IF NOT EXISTS document_identity_misuse_assessments(
|
||||
id TEXT PRIMARY KEY,evidence_id TEXT NOT NULL,ocr_run_id TEXT,
|
||||
status TEXT NOT NULL CHECK(status IN('indeterminate','presumed','confirmed')),
|
||||
justification TEXT,assessed_at TEXT NOT NULL CHECK(length(assessed_at)=20),
|
||||
previous_assessment_id TEXT,origin TEXT NOT NULL CHECK(origin='human'),
|
||||
CHECK(status='indeterminate' OR length(trim(justification))>0),
|
||||
FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(previous_assessment_id) REFERENCES document_identity_misuse_assessments(id) ON DELETE RESTRICT);
|
||||
CREATE INDEX IF NOT EXISTS idx_identity_misuse_evidence_history
|
||||
ON document_identity_misuse_assessments(evidence_id,assessed_at,id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_identity_misuse_previous
|
||||
ON document_identity_misuse_assessments(previous_assessment_id)
|
||||
WHERE previous_assessment_id IS NOT NULL;
|
||||
CREATE TRIGGER IF NOT EXISTS identity_misuse_consistency BEFORE INSERT ON document_identity_misuse_assessments BEGIN
|
||||
SELECT CASE WHEN NEW.ocr_run_id IS NOT NULL AND NOT EXISTS(SELECT 1 FROM identity_ocr_runs r WHERE r.id=NEW.ocr_run_id AND r.evidence_id=NEW.evidence_id) THEN RAISE(ABORT,'OCR run does not belong to evidence') END;
|
||||
SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND NOT EXISTS(SELECT 1 FROM document_identity_misuse_assessments p WHERE p.id=NEW.previous_assessment_id AND p.evidence_id=NEW.evidence_id) THEN RAISE(ABORT,'previous assessment does not belong to evidence') END;
|
||||
SELECT CASE WHEN EXISTS(SELECT 1 FROM document_identity_misuse_assessments p WHERE p.evidence_id=NEW.evidence_id) AND NEW.previous_assessment_id IS NULL THEN RAISE(ABORT,'previous assessment is required') END;
|
||||
SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND EXISTS(SELECT 1 FROM document_identity_misuse_assessments n WHERE n.previous_assessment_id=NEW.previous_assessment_id) THEN RAISE(ABORT,'previous assessment is not current') END;
|
||||
END;
|
||||
CREATE TRIGGER IF NOT EXISTS identity_misuse_append_only_update BEFORE UPDATE ON document_identity_misuse_assessments BEGIN SELECT RAISE(ABORT,'identity misuse history is append-only'); END;
|
||||
CREATE TRIGGER IF NOT EXISTS identity_misuse_append_only_delete BEFORE DELETE ON document_identity_misuse_assessments BEGIN SELECT RAISE(ABORT,'identity misuse history is append-only'); END;
|
||||
|
|
|
|||
44
database/schema_v20.sql
Normal file
44
database/schema_v20.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* V20 — évaluations humaines distinctes de l’usage abusif d’identité. */
|
||||
CREATE TABLE document_identity_misuse_assessments(
|
||||
id TEXT PRIMARY KEY,
|
||||
evidence_id TEXT NOT NULL,
|
||||
ocr_run_id TEXT,
|
||||
status TEXT NOT NULL CHECK(status IN('indeterminate','presumed','confirmed')),
|
||||
justification TEXT,
|
||||
assessed_at TEXT NOT NULL CHECK(length(assessed_at)=20),
|
||||
previous_assessment_id TEXT,
|
||||
origin TEXT NOT NULL CHECK(origin='human'),
|
||||
CHECK(status='indeterminate' OR length(trim(justification))>0),
|
||||
FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY(previous_assessment_id)
|
||||
REFERENCES document_identity_misuse_assessments(id) ON DELETE RESTRICT);
|
||||
CREATE INDEX idx_identity_misuse_evidence_history
|
||||
ON document_identity_misuse_assessments(evidence_id,assessed_at,id);
|
||||
CREATE UNIQUE INDEX idx_identity_misuse_previous
|
||||
ON document_identity_misuse_assessments(previous_assessment_id)
|
||||
WHERE previous_assessment_id IS NOT NULL;
|
||||
CREATE TRIGGER identity_misuse_consistency BEFORE INSERT
|
||||
ON document_identity_misuse_assessments BEGIN
|
||||
SELECT CASE WHEN NEW.ocr_run_id IS NOT NULL AND NOT EXISTS(
|
||||
SELECT 1 FROM identity_ocr_runs r
|
||||
WHERE r.id=NEW.ocr_run_id AND r.evidence_id=NEW.evidence_id)
|
||||
THEN RAISE(ABORT,'OCR run does not belong to evidence') END;
|
||||
SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND NOT EXISTS(
|
||||
SELECT 1 FROM document_identity_misuse_assessments p
|
||||
WHERE p.id=NEW.previous_assessment_id AND p.evidence_id=NEW.evidence_id)
|
||||
THEN RAISE(ABORT,'previous assessment does not belong to evidence') END;
|
||||
SELECT CASE WHEN EXISTS(SELECT 1 FROM document_identity_misuse_assessments p
|
||||
WHERE p.evidence_id=NEW.evidence_id) AND NEW.previous_assessment_id IS NULL
|
||||
THEN RAISE(ABORT,'previous assessment is required') END;
|
||||
SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND EXISTS(
|
||||
SELECT 1 FROM document_identity_misuse_assessments n
|
||||
WHERE n.previous_assessment_id=NEW.previous_assessment_id)
|
||||
THEN RAISE(ABORT,'previous assessment is not current') END;
|
||||
END;
|
||||
CREATE TRIGGER identity_misuse_append_only_update BEFORE UPDATE
|
||||
ON document_identity_misuse_assessments BEGIN
|
||||
SELECT RAISE(ABORT,'identity misuse history is append-only'); END;
|
||||
CREATE TRIGGER identity_misuse_append_only_delete BEFORE DELETE
|
||||
ON document_identity_misuse_assessments BEGIN
|
||||
SELECT RAISE(ABORT,'identity misuse history is append-only'); END;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
Makefile | 2 +
|
||||
include/core/person_creation_task.h | 3 +-
|
||||
include/dao/identity_traceability_dao.h | 2 +
|
||||
include/views/create_person_dialog.h | 2 +
|
||||
include/views/main_window.h | 7 ++++
|
||||
include/views/person_vocabulary_adapter.h | 3 ++
|
||||
include/widgets/entity_details_panel.h | 2 +
|
||||
include/widgets/workspace.h | 4 +-
|
||||
src/core/application.c | 20 ++++++++--
|
||||
src/core/person_creation_task.c | 45 +++++++++++++++++++---
|
||||
src/dao/identity_traceability_dao.c | 26 +++++++++++++
|
||||
src/views/create_person_dialog.c | 63 +++++++++++++++++++++++++++----
|
||||
src/views/evidence_metadata_dialog.c | 52 +++++++++++++++++++++++++
|
||||
src/views/main_window.c | 8 +++-
|
||||
src/views/person_vocabulary_adapter.c | 39 +++++++++++++++++++
|
||||
src/widgets/entity_details_panel.c | 56 +++++++++++++++++++++++++++
|
||||
src/widgets/workspace.c | 7 ++++
|
||||
tests/test_create_person_dialog_gtk.c | 3 ++
|
||||
tests/test_create_person_dialog_ocr_gtk.c | 2 +
|
||||
19 files changed, 327 insertions(+), 19 deletions(-)
|
||||
|
|
@ -30,7 +30,7 @@ pause, retour au début, détachement et libération via des actions injectées.
|
|||
|
||||
> **Version :** 3.2
|
||||
> **Dernière mise à jour :** 2026-07-30
|
||||
> **Schéma SQLite courant :** V18
|
||||
> **Schéma SQLite courant :** V20
|
||||
|
||||
## Personnes contextuelles — SQLite V14
|
||||
|
||||
|
|
@ -134,6 +134,21 @@ service relit le champ V18 avant COMMIT et conserve la valeur existante par
|
|||
défaut en cas de conflit. La fiche personne relit ces champs par
|
||||
`PersonDetailsProvider` et le DAO de projection.
|
||||
|
||||
`CreatePersonDialog` sépare sept pages : personne, rôles, preuves, révision
|
||||
OCR, projection OCR, relations factuelles et confirmation. La page de
|
||||
projection contient uniquement `PersonOcrProjectionEditor`; la page suivante
|
||||
contient uniquement `PersonFactualRelationEditor`. Le résumé distingue la
|
||||
correction OCR persistée, les projections choisies et les relations préparées.
|
||||
|
||||
La V20 ajoute `document_identity_misuse_assessments`, domaine distinct de
|
||||
l’authenticité documentaire. Ses états `indeterminate`, `presumed` et
|
||||
`confirmed` sont exclusivement humains, chaînés et append-only. La fiche de
|
||||
preuve affiche séparément leur origine et leur justification.
|
||||
|
||||
La collecte des justifications des rôles sensibles et l’alimentation plus
|
||||
complète de `person_identification_assessments` restent des renforcements de
|
||||
cohérence, non des exigences contractuelles explicites du ticket #109.
|
||||
|
||||
---
|
||||
|
||||
## 1. Objectif
|
||||
|
|
@ -430,7 +445,7 @@ validation du chemin
|
|||
↓
|
||||
création de l'arborescence
|
||||
↓
|
||||
initialisation transactionnelle de SQLite V18
|
||||
initialisation transactionnelle de SQLite V20
|
||||
↓
|
||||
création de l'identité de l'enquête
|
||||
↓
|
||||
|
|
@ -879,7 +894,8 @@ Validation :
|
|||
```sh
|
||||
make clean
|
||||
make -j8
|
||||
make -j8 test
|
||||
make check-source-size
|
||||
DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ La projection OCR se valide avec `test_person_ocr_projection`,
|
|||
`test_person_ocr_projection_editor_gtk`, ce dernier sous affichage réel et
|
||||
`G_DEBUG=fatal-criticals`.
|
||||
|
||||
L’assistant complet se valide également avec
|
||||
`tests/test_create_person_dialog_gtk` : il vérifie l’ordre des sept pages,
|
||||
leur séparation et les trois rubriques du résumé final.
|
||||
|
||||
La validation ciblée de l’aperçu partagé comprend
|
||||
`test_evidence_preview_widget_gtk`, `test_evidence_preview`,
|
||||
`test_evidence_video_preview_controller` et
|
||||
|
|
@ -306,6 +310,9 @@ make -j8 \
|
|||
tests/test_workspace_identity_ocr_gtk \
|
||||
tests/test_evidence_preview_widget_gtk \
|
||||
tests/test_dialog_geometry_gtk
|
||||
tests/test_person_factual_relation_editor_gtk \
|
||||
tests/test_document_authenticity_editor_gtk \
|
||||
tests/test_person_ocr_projection_editor_gtk
|
||||
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_create_person_dialog_ocr_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk
|
||||
|
|
@ -313,6 +320,9 @@ G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_metadata_dialog_gtk
|
|||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_factual_relation_editor_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_document_authenticity_editor_gtk
|
||||
G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_ocr_projection_editor_gtk
|
||||
```
|
||||
|
||||
Ils doivent cliquer sur les contrôles de production, passer par `MainWindow`
|
||||
|
|
@ -655,10 +665,19 @@ Les tests doivent utiliser :
|
|||
Avant chaque commit :
|
||||
|
||||
```sh
|
||||
make clean
|
||||
make -j8
|
||||
make check-source-size
|
||||
DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test
|
||||
git diff --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
Les validations ASan/UBSan ciblées produisent leurs binaires dans `/tmp`.
|
||||
Lorsque `ASAN_OPTIONS=detect_leaks=0` est nécessaire sous l’environnement GTK
|
||||
tracé, cela valide AddressSanitizer et UndefinedBehaviorSanitizer, pas
|
||||
LeakSanitizer.
|
||||
|
||||
Inspecter tout nouveau fichier inhabituel à la racine du dépôt.
|
||||
|
||||
---
|
||||
|
|
@ -729,5 +748,10 @@ Utiliser exclusivement une enquête, une base SQLite et des documents
|
|||
6. dialogues à 1200 × 800 et 760 × 560 : parent transitoire, formulaire
|
||||
défilable, répartition 2/3–1/3 et actions toujours visibles.
|
||||
|
||||
La validation manuelle réussie confirme ces parcours, mais ne clôt pas le
|
||||
ticket #109 et ne couvre pas les limitations encore listées dans la roadmap.
|
||||
Contrôler aussi les sept étapes de l’assistant, les trois rubriques du résumé,
|
||||
la projection vide par défaut et l’absence de relation automatique.
|
||||
|
||||
La validation manuelle doit également couvrir la section V20 distincte de
|
||||
l’authenticité. La justification des rôles sensibles et l’historisation
|
||||
complète de l’identification sont des renforcements de cohérence, pas des
|
||||
critères d’acceptation explicites du ticket #109.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ formulaire défilable, un aperçu redimensionnable et des actions fixes.
|
|||
La validation manuelle de ces parcours est réussie. Elle ne vaut pas
|
||||
achèvement du ticket #109.
|
||||
|
||||
> **Dernière mise à jour :** 2026-07-30
|
||||
> **Dernière mise à jour :** 2026-08-02
|
||||
> **État du projet :** développement actif
|
||||
> **Schéma SQLite courant :** V18
|
||||
> **Schéma SQLite courant :** V20
|
||||
> **Usage opérationnel :** non prêt pour la production
|
||||
|
||||
---
|
||||
|
|
@ -131,7 +131,7 @@ La branche `main` contient notamment :
|
|||
|
||||
- création, validation et ouverture d'enquêtes ;
|
||||
- session d'enquête remplaçable proprement ;
|
||||
- infrastructure SQLite et migrations jusqu'à V18 ;
|
||||
- infrastructure SQLite et migrations jusqu'à V20 ;
|
||||
- couche Database, DAO et services métier ;
|
||||
- import de preuves avec copie contrôlée et SHA-256 ;
|
||||
- vérification d'intégrité et reclassement des preuves ;
|
||||
|
|
@ -185,13 +185,15 @@ PARTIEL — VALIDATION MANUELLE DES TRANCHES LIVRÉES RÉUSSIE
|
|||
|
||||
Limitations restantes :
|
||||
|
||||
1. l’OCR groupé de plusieurs preuves dans une même opération n’est pas pris en
|
||||
charge ;
|
||||
2. les interfaces GTK complètes des états d’identification et des rôles V18
|
||||
restent à construire ;
|
||||
3. la provenance uniforme de tous les autres dérivés reste partielle ;
|
||||
1. la justification des rôles sensibles et l’alimentation complète de
|
||||
`person_identification_assessments` restent des renforcements de cohérence,
|
||||
sans être des exigences explicites du ticket #109 ;
|
||||
2. l’OCR groupé n’est volontairement pas pris en charge : après un import
|
||||
multiple, chaque preuve est analysée individuellement ;
|
||||
3. la provenance uniforme des dérivés hors parcours OCR reste partielle.
|
||||
|
||||
Ces limites interdisent de présenter le ticket #109 comme terminé.
|
||||
La V20 couvre distinctement les états contractuels d’usage d’identité présumé
|
||||
et confirmé. Une validation manuelle finale reste requise avant clôture.
|
||||
|
||||
### Ticket #107 — Pivot e-mail forensique
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,12 @@ dernière table conserve personne, cible, valeurs précédente et nouvelle,
|
|||
preuve, OcrRun, champ OCR, qualité, stratégie, date et origine humaine. Un
|
||||
trigger refuse une source devenue non projectable.
|
||||
|
||||
> **Statut :** architecture courante
|
||||
> **Version du schéma :** V19
|
||||
> **Dernière mise à jour :** 2026-07-24
|
||||
> **Statut :** architecture courante
|
||||
V20 ajoute l’historique humain append-only
|
||||
`document_identity_misuse_assessments`, distinct de l’authenticité.
|
||||
|
||||
> **Version du schéma :** V20
|
||||
> **Dernière mise à jour :** 2026-08-02
|
||||
> **Source de vérité détaillée :** `SCHEMA_AUDIT_CURRENT.md`
|
||||
|
||||
---
|
||||
|
|
@ -71,7 +74,7 @@ docs/database/SCHEMA_AUDIT_CURRENT.md
|
|||
L'état réel du schéma est déterminé par :
|
||||
|
||||
1. les constantes de version dans le code ;
|
||||
2. `database/schema_v1.sql` à `database/schema_v10.sql` ;
|
||||
2. `database/schema_v1.sql` à `database/schema_v19.sql` ;
|
||||
3. `database/schema_current.sql` ;
|
||||
4. les fonctions d'installation et de migration ;
|
||||
5. `tests/test_database.c` et les tests DAO ;
|
||||
|
|
@ -242,6 +245,7 @@ database/schema_v7.sql
|
|||
database/schema_v8.sql
|
||||
database/schema_v9.sql
|
||||
database/schema_v10.sql
|
||||
database/schema_v11.sql à database/schema_v19.sql
|
||||
```
|
||||
|
||||
`database/schema_current.sql` contient des extensions ou réparations
|
||||
|
|
@ -264,7 +268,7 @@ PRAGMA foreign_keys = ON
|
|||
↓
|
||||
BEGIN
|
||||
↓
|
||||
installation V1 à V10
|
||||
installation du socle historique puis du schéma courant V20
|
||||
↓
|
||||
application du schéma courant idempotent
|
||||
↓
|
||||
|
|
@ -278,7 +282,7 @@ Un échec provoque un rollback.
|
|||
Une base neuve doit aboutir directement à :
|
||||
|
||||
```text
|
||||
schema_version = 10
|
||||
schema_version = 19
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
16
include/core/document_identity_misuse_service.h
Normal file
16
include/core/document_identity_misuse_service.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef LABFY_DOCUMENT_IDENTITY_MISUSE_SERVICE_H
|
||||
#define LABFY_DOCUMENT_IDENTITY_MISUSE_SERVICE_H
|
||||
#include "database/database.h"
|
||||
#include "models/identity_traceability.h"
|
||||
#include <glib.h>
|
||||
G_BEGIN_DECLS
|
||||
typedef struct {const char*code,*label,*description;gboolean requires_justification;} DocumentIdentityMisuseStatus;
|
||||
const DocumentIdentityMisuseStatus *document_identity_misuse_service_statuses(gsize *count);
|
||||
const DocumentIdentityMisuseStatus *document_identity_misuse_service_status(const char *code);
|
||||
GPtrArray *document_identity_misuse_service_list_runs(Database *database,const char *evidence_identifier,GError **error);
|
||||
GPtrArray *document_identity_misuse_service_history(Database *database,const char *evidence_identifier,GError **error);
|
||||
gboolean document_identity_misuse_service_add(Database *database,const char *evidence_identifier,
|
||||
const char *status,const char *justification,const char *ocr_run_identifier,
|
||||
DocumentIdentityMisuseAssessment **created,GError **error);
|
||||
G_END_DECLS
|
||||
#endif
|
||||
|
|
@ -16,6 +16,13 @@ GPtrArray *identity_traceability_dao_list_authenticity(
|
|||
IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error);
|
||||
DocumentAuthenticityAssessment *identity_traceability_dao_current_authenticity(
|
||||
IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error);
|
||||
gboolean identity_traceability_dao_insert_identity_misuse(
|
||||
IdentityTraceabilityDao *dao,const DocumentIdentityMisuseAssessment *assessment,
|
||||
GError **error);
|
||||
GPtrArray *identity_traceability_dao_list_identity_misuse(
|
||||
IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error);
|
||||
DocumentIdentityMisuseAssessment *identity_traceability_dao_current_identity_misuse(
|
||||
IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error);
|
||||
gboolean identity_traceability_dao_insert_factual_relation(
|
||||
IdentityTraceabilityDao *dao,const PersonEvidenceFactualRelation *relation,
|
||||
GError **error);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ bool schema_install_v16(Database *database);
|
|||
bool schema_install_v17(Database *database);
|
||||
bool schema_install_v18(Database *database);
|
||||
bool schema_install_v19(Database *database);
|
||||
bool schema_install_v20(Database *database);
|
||||
|
||||
/**
|
||||
* @brief Garantit la présence des extensions du schéma courant V2.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ typedef struct {
|
|||
char *previous_identifier, *technical_note, *origin;
|
||||
} DocumentAuthenticityAssessment;
|
||||
|
||||
typedef struct {
|
||||
char *identifier, *evidence_identifier, *ocr_run_identifier;
|
||||
char *status, *justification, *assessed_at;
|
||||
char *previous_identifier, *origin;
|
||||
} DocumentIdentityMisuseAssessment;
|
||||
|
||||
typedef struct {
|
||||
char *identifier, *person_identifier, *evidence_identifier;
|
||||
char *ocr_run_identifier, *relation_type, *factual_note;
|
||||
|
|
@ -25,6 +31,7 @@ typedef struct {
|
|||
typedef PersonRoleVocabularyEntry IdentificationStatusVocabularyEntry;
|
||||
|
||||
gboolean identity_traceability_authenticity_status_valid(const char *status);
|
||||
gboolean identity_traceability_identity_misuse_status_valid(const char *status);
|
||||
gboolean identity_traceability_relation_type_valid(const char *type);
|
||||
gboolean identity_traceability_identification_status_valid(const char *status);
|
||||
gboolean identity_traceability_value_quality_valid(const char *quality);
|
||||
|
|
@ -51,6 +58,24 @@ const char *document_authenticity_assessment_get_previous_identifier(const Docum
|
|||
const char *document_authenticity_assessment_get_technical_note(const DocumentAuthenticityAssessment *assessment);
|
||||
const char *document_authenticity_assessment_get_origin(const DocumentAuthenticityAssessment *assessment);
|
||||
|
||||
DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_new(
|
||||
const char *identifier, const char *evidence_identifier,
|
||||
const char *ocr_run_identifier, const char *status,
|
||||
const char *justification, const char *assessed_at,
|
||||
const char *previous_identifier);
|
||||
DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_copy(
|
||||
const DocumentIdentityMisuseAssessment *assessment);
|
||||
void document_identity_misuse_assessment_free(
|
||||
DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_identifier(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_evidence_identifier(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_ocr_run_identifier(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_status(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_justification(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_assessed_at(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_previous_identifier(const DocumentIdentityMisuseAssessment *assessment);
|
||||
const char *document_identity_misuse_assessment_get_origin(const DocumentIdentityMisuseAssessment *assessment);
|
||||
|
||||
PersonEvidenceFactualRelation *person_evidence_factual_relation_new(
|
||||
const char *identifier, const char *person_identifier,
|
||||
const char *evidence_identifier, const char *ocr_run_identifier,
|
||||
|
|
|
|||
12
include/views/document_identity_misuse_editor.h
Normal file
12
include/views/document_identity_misuse_editor.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef LABFY_DOCUMENT_IDENTITY_MISUSE_EDITOR_H
|
||||
#define LABFY_DOCUMENT_IDENTITY_MISUSE_EDITOR_H
|
||||
#include "database/database.h"
|
||||
#include <gtk/gtk.h>
|
||||
G_BEGIN_DECLS
|
||||
typedef struct DocumentIdentityMisuseEditor DocumentIdentityMisuseEditor;
|
||||
DocumentIdentityMisuseEditor *document_identity_misuse_editor_new(Database *database,const char *evidence_identifier);
|
||||
GtkWidget *document_identity_misuse_editor_get_widget(DocumentIdentityMisuseEditor *editor);
|
||||
gboolean document_identity_misuse_editor_refresh(DocumentIdentityMisuseEditor *editor,GError **error);
|
||||
void document_identity_misuse_editor_free(DocumentIdentityMisuseEditor *editor);
|
||||
G_END_DECLS
|
||||
#endif
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
M Makefile
|
||||
M include/core/person_creation_task.h
|
||||
M include/dao/identity_traceability_dao.h
|
||||
M include/views/create_person_dialog.h
|
||||
M include/views/main_window.h
|
||||
M include/views/person_vocabulary_adapter.h
|
||||
M include/widgets/entity_details_panel.h
|
||||
M include/widgets/workspace.h
|
||||
M src/core/application.c
|
||||
M src/core/person_creation_task.c
|
||||
M src/dao/identity_traceability_dao.c
|
||||
M src/views/create_person_dialog.c
|
||||
M src/views/evidence_metadata_dialog.c
|
||||
M src/views/main_window.c
|
||||
M src/views/person_vocabulary_adapter.c
|
||||
M src/widgets/entity_details_panel.c
|
||||
M src/widgets/workspace.c
|
||||
M tests/test_create_person_dialog_gtk.c
|
||||
M tests/test_create_person_dialog_ocr_gtk.c
|
||||
40
src/core/document_identity_misuse_service.c
Normal file
40
src/core/document_identity_misuse_service.c
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "core/document_identity_misuse_service.h"
|
||||
#include "dao/identity_ocr_dao.h"
|
||||
#include "dao/identity_traceability_dao.h"
|
||||
#include "database/transaction.h"
|
||||
static GQuark domain(void){return g_quark_from_static_string("document-identity-misuse-service");}
|
||||
static const DocumentIdentityMisuseStatus statuses[]={
|
||||
{"indeterminate","Hypothèse d’usurpation indéterminée","Aucune conclusion humaine sur un usage abusif d’identité.",FALSE},
|
||||
{"presumed","Document présumé utilisé dans une usurpation d’identité","Hypothèse humaine prudente nécessitant une justification.",TRUE},
|
||||
{"confirmed","Utilisation dans une usurpation d’identité confirmée","Conclusion humaine documentée, jamais déduite automatiquement.",TRUE}};
|
||||
const DocumentIdentityMisuseStatus *document_identity_misuse_service_statuses(gsize*c)
|
||||
{if(c)*c=G_N_ELEMENTS(statuses);return statuses;}
|
||||
const DocumentIdentityMisuseStatus *document_identity_misuse_service_status(const char*code)
|
||||
{for(guint i=0;i<G_N_ELEMENTS(statuses);i++)if(g_strcmp0(code,statuses[i].code)==0)return &statuses[i];return NULL;}
|
||||
GPtrArray *document_identity_misuse_service_list_runs(Database*d,const char*e,GError**error)
|
||||
{IdentityOcrDao*dao=identity_ocr_dao_new(d);if(!dao)return NULL;GPtrArray*a=identity_ocr_dao_list_runs_by_evidence(dao,e,error);identity_ocr_dao_free(dao);return a;}
|
||||
GPtrArray *document_identity_misuse_service_history(Database*d,const char*e,GError**error)
|
||||
{IdentityTraceabilityDao*dao=identity_traceability_dao_new(d);if(!dao)return NULL;GPtrArray*a=identity_traceability_dao_list_identity_misuse(dao,e,error);identity_traceability_dao_free(dao);return a;}
|
||||
gboolean document_identity_misuse_service_add(Database*d,const char*evidence,
|
||||
const char*status,const char*justification,const char*run,
|
||||
DocumentIdentityMisuseAssessment**created,GError**error)
|
||||
{
|
||||
if(created)*created=NULL;
|
||||
const DocumentIdentityMisuseStatus*entry=
|
||||
document_identity_misuse_service_status(status);char*j=justification?g_strdup(justification):NULL;if(j)g_strstrip(j);
|
||||
if(!d||!g_uuid_string_is_valid(evidence)||!entry||(entry->requires_justification&&(!j||!*j))){
|
||||
g_free(j);g_set_error_literal(error,domain(),1,"Une justification est requise pour cette évaluation.");return FALSE;}
|
||||
IdentityTraceabilityDao*dao=identity_traceability_dao_new(d);GError*local=NULL;
|
||||
if(!database_transaction_begin(d)){identity_traceability_dao_free(dao);g_free(j);g_set_error_literal(error,domain(),2,"Impossible de démarrer la transaction d’usage d’identité.");return FALSE;}
|
||||
DocumentIdentityMisuseAssessment*previous=identity_traceability_dao_current_identity_misuse(dao,evidence,&local);
|
||||
if(local){database_transaction_rollback(d);g_propagate_error(error,local);identity_traceability_dao_free(dao);g_free(j);return FALSE;}
|
||||
char*id=g_uuid_string_random();GDateTime*now=g_date_time_new_now_utc();char*at=g_date_time_format(now,"%Y-%m-%dT%H:%M:%SZ");
|
||||
DocumentIdentityMisuseAssessment*a=document_identity_misuse_assessment_new(id,evidence,run,status,j&&*j?j:NULL,at,
|
||||
previous?document_identity_misuse_assessment_get_identifier(previous):NULL);
|
||||
gboolean ok=a&&identity_traceability_dao_insert_identity_misuse(dao,a,&local)&&database_transaction_commit(d);
|
||||
if(!ok&&database_transaction_is_active(d))database_transaction_rollback(d);
|
||||
if(!ok&&!local)g_set_error_literal(&local,domain(),2,"Impossible d’enregistrer l’évaluation d’usage d’identité.");
|
||||
if(ok&&created)*created=document_identity_misuse_assessment_copy(a);else if(local)g_propagate_error(error,local);
|
||||
document_identity_misuse_assessment_free(a);document_identity_misuse_assessment_free(previous);identity_traceability_dao_free(dao);
|
||||
g_date_time_unref(now);g_free(at);g_free(id);g_free(j);return ok;
|
||||
}
|
||||
|
|
@ -100,6 +100,83 @@ DocumentAuthenticityAssessment *identity_traceability_dao_current_authenticity(
|
|||
document_authenticity_assessment_copy(g_ptr_array_index(a,0)):NULL;
|
||||
g_ptr_array_unref(a);return r;
|
||||
}
|
||||
gboolean identity_traceability_dao_insert_identity_misuse(
|
||||
IdentityTraceabilityDao*d,const DocumentIdentityMisuseAssessment*a,GError**e)
|
||||
{
|
||||
DocumentIdentityMisuseAssessment*valid=a?document_identity_misuse_assessment_copy(a):NULL;
|
||||
if(!d||!valid){fail(e,"Évaluation d’usage d’identité invalide.");return FALSE;}
|
||||
const char*run=document_identity_misuse_assessment_get_ocr_run_identifier(valid);
|
||||
if(run){DatabaseStatement*check=database_statement_prepare(d->database,
|
||||
"SELECT 1 FROM identity_ocr_runs WHERE id=? AND evidence_id=?;");
|
||||
gboolean compatible=check&&bind(check,1,run)&&bind(check,2,
|
||||
document_identity_misuse_assessment_get_evidence_identifier(valid))&&
|
||||
database_statement_step(check)==DATABASE_STATEMENT_STEP_ROW;
|
||||
database_statement_finalize(check);if(!compatible){
|
||||
document_identity_misuse_assessment_free(valid);
|
||||
fail(e,"L’exécution OCR ne correspond pas à la preuve évaluée.");return FALSE;}}
|
||||
const char*previous=document_identity_misuse_assessment_get_previous_identifier(valid);
|
||||
if(previous){DatabaseStatement*check=database_statement_prepare(d->database,
|
||||
"SELECT 1 FROM document_identity_misuse_assessments WHERE id=? AND evidence_id=?;");
|
||||
gboolean compatible=check&&bind(check,1,previous)&&bind(check,2,
|
||||
document_identity_misuse_assessment_get_evidence_identifier(valid))&&
|
||||
database_statement_step(check)==DATABASE_STATEMENT_STEP_ROW;
|
||||
database_statement_finalize(check);if(!compatible){
|
||||
document_identity_misuse_assessment_free(valid);
|
||||
fail(e,"L’évaluation précédente ne correspond pas à la preuve.");return FALSE;}}
|
||||
DatabaseStatement*s=database_statement_prepare(d->database,
|
||||
"INSERT INTO document_identity_misuse_assessments VALUES(?,?,?,?,?,?,?,?);");
|
||||
gboolean ok=s&&bind(s,1,document_identity_misuse_assessment_get_identifier(valid))&&
|
||||
bind(s,2,document_identity_misuse_assessment_get_evidence_identifier(valid))&&
|
||||
bind(s,3,run)&&bind(s,4,document_identity_misuse_assessment_get_status(valid))&&
|
||||
bind(s,5,document_identity_misuse_assessment_get_justification(valid))&&
|
||||
bind(s,6,document_identity_misuse_assessment_get_assessed_at(valid))&&
|
||||
bind(s,7,previous)&&bind(s,8,"human")&&
|
||||
database_statement_step(s)==DATABASE_STATEMENT_STEP_DONE;
|
||||
database_statement_finalize(s);document_identity_misuse_assessment_free(valid);
|
||||
if(!ok)fail(e,"Impossible de conserver l’évaluation d’usage d’identité.");
|
||||
return ok;
|
||||
}
|
||||
static DocumentIdentityMisuseAssessment *read_misuse(DatabaseStatement*s)
|
||||
{
|
||||
char*v[8]={0};gboolean ok=TRUE;for(int i=0;i<8;i++)
|
||||
ok=ok&&database_statement_column_text(s,i,&v[i]);
|
||||
DocumentIdentityMisuseAssessment*a=ok?document_identity_misuse_assessment_new(
|
||||
v[0],v[1],v[2],v[3],v[4],v[5],v[6]):NULL;
|
||||
for(int i=0;i<8;i++)g_free(v[i]);
|
||||
return a;
|
||||
}
|
||||
GPtrArray *identity_traceability_dao_list_identity_misuse(
|
||||
IdentityTraceabilityDao*d,const char*evidence,GError**e)
|
||||
{
|
||||
if(!d||!evidence){fail(e,"Lecture de l’usage d’identité invalide.");return NULL;}
|
||||
DatabaseStatement*s=database_statement_prepare(d->database,
|
||||
"SELECT id,evidence_id,ocr_run_id,status,justification,assessed_at,"
|
||||
"previous_assessment_id,origin FROM document_identity_misuse_assessments "
|
||||
"WHERE evidence_id=? ORDER BY assessed_at,id;");
|
||||
GPtrArray*a=g_ptr_array_new_with_free_func((GDestroyNotify)
|
||||
document_identity_misuse_assessment_free);if(!s||!bind(s,1,evidence))goto bad_misuse;
|
||||
for(;;){DatabaseStatementStepResult step=database_statement_step(s);
|
||||
if(step==DATABASE_STATEMENT_STEP_DONE)break;
|
||||
if(step!=DATABASE_STATEMENT_STEP_ROW)goto bad_misuse;
|
||||
DocumentIdentityMisuseAssessment*r=read_misuse(s);if(!r)goto bad_misuse;g_ptr_array_add(a,r);}
|
||||
database_statement_finalize(s);return a;
|
||||
bad_misuse:database_statement_finalize(s);g_ptr_array_unref(a);
|
||||
fail(e,"Impossible de lire l’historique d’usage d’identité.");return NULL;
|
||||
}
|
||||
DocumentIdentityMisuseAssessment *identity_traceability_dao_current_identity_misuse(
|
||||
IdentityTraceabilityDao*d,const char*evidence,GError**e)
|
||||
{
|
||||
GPtrArray*a=identity_traceability_dao_list_identity_misuse(d,evidence,e);if(!a)return NULL;
|
||||
DocumentIdentityMisuseAssessment*current=NULL;
|
||||
for(guint i=0;i<a->len;i++){DocumentIdentityMisuseAssessment*c=g_ptr_array_index(a,i);
|
||||
gboolean has_next=FALSE;for(guint j=0;j<a->len;j++){
|
||||
DocumentIdentityMisuseAssessment*n=g_ptr_array_index(a,j);
|
||||
if(g_strcmp0(document_identity_misuse_assessment_get_previous_identifier(n),
|
||||
document_identity_misuse_assessment_get_identifier(c))==0){has_next=TRUE;break;}}
|
||||
if(!has_next)current=c;}
|
||||
DocumentIdentityMisuseAssessment*r=document_identity_misuse_assessment_copy(current);
|
||||
g_ptr_array_unref(a);return r;
|
||||
}
|
||||
gboolean identity_traceability_dao_insert_factual_relation(
|
||||
IdentityTraceabilityDao*d,const PersonEvidenceFactualRelation*r,GError**e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
/**
|
||||
* @brief Version actuelle du schéma SQLite.
|
||||
*/
|
||||
#define DATABASE_SCHEMA_VERSION_CURRENT 19
|
||||
#define DATABASE_SCHEMA_VERSION_CURRENT 20
|
||||
|
||||
/**
|
||||
* @brief Version actuelle sous forme textuelle pour metadata.
|
||||
*/
|
||||
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "19"
|
||||
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "20"
|
||||
|
||||
/**
|
||||
* @brief Nom de l'application enregistré dans les métadonnées.
|
||||
|
|
@ -947,6 +947,21 @@ rollback:
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool database_migrate_v19_to_v20(Database *database)
|
||||
{
|
||||
bool transaction_started = false;
|
||||
if (database == NULL || !database_transaction_begin(database)) return false;
|
||||
transaction_started = true;
|
||||
if (!schema_install_v20(database) ||
|
||||
!database_update_schema_version(database, "20") ||
|
||||
!database_transaction_commit(database)) goto rollback;
|
||||
return true;
|
||||
rollback:
|
||||
if (transaction_started && !database_transaction_rollback(database))
|
||||
g_warning("Impossible d’annuler la migration SQLite V19 vers V20.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Garantit atomiquement la présence des extensions du schéma courant.
|
||||
*/
|
||||
|
|
@ -1212,6 +1227,10 @@ bool database_migrate_to_latest(
|
|||
if (!database_migrate_v18_to_v19(database)) return false;
|
||||
schema_version = 19;
|
||||
break;
|
||||
case 19:
|
||||
if (!database_migrate_v19_to_v20(database)) return false;
|
||||
schema_version = 20;
|
||||
break;
|
||||
|
||||
default:
|
||||
database_set_error(
|
||||
|
|
@ -1354,8 +1373,7 @@ bool database_initialize(
|
|||
|
||||
if (!schema_ensure_current(
|
||||
database
|
||||
) ||
|
||||
!schema_install_v19(database))
|
||||
))
|
||||
{
|
||||
goto rollback;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,6 +439,12 @@ bool schema_install_v19(Database *database)
|
|||
"la migration SQLite V19");
|
||||
}
|
||||
|
||||
bool schema_install_v20(Database *database)
|
||||
{
|
||||
return schema_execute_file(database, "database/schema_v20.sql",
|
||||
"la migration SQLite V20");
|
||||
}
|
||||
|
||||
bool schema_ensure_current(
|
||||
Database *database
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ gboolean identity_traceability_authenticity_status_valid(const char *value)
|
|||
"suspicious","presumed_forged","confirmed_forged"};
|
||||
return in_values(value,values,G_N_ELEMENTS(values));
|
||||
}
|
||||
gboolean identity_traceability_identity_misuse_status_valid(const char *value)
|
||||
{
|
||||
static const char *const values[]={"indeterminate","presumed","confirmed"};
|
||||
return in_values(value,values,G_N_ELEMENTS(values));
|
||||
}
|
||||
gboolean identity_traceability_relation_type_valid(const char *value)
|
||||
{
|
||||
static const char *const values[]={"identity_observed_in",
|
||||
|
|
@ -90,6 +95,42 @@ AUTH_GETTER(previous_identifier,previous_identifier)
|
|||
AUTH_GETTER(technical_note,technical_note)
|
||||
AUTH_GETTER(origin,origin)
|
||||
|
||||
DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_new(
|
||||
const char *id,const char *evidence,const char *run,const char *status,
|
||||
const char *justification,const char *at,const char *previous)
|
||||
{
|
||||
if(!g_uuid_string_is_valid(id)||!g_uuid_string_is_valid(evidence)||
|
||||
!uuid_or_null(run)||!uuid_or_null(previous)||
|
||||
!identity_traceability_identity_misuse_status_valid(status)||
|
||||
!timestamp(at)||!optional_text(justification)||
|
||||
(g_strcmp0(status,"indeterminate")!=0&&!text(justification)))return NULL;
|
||||
DocumentIdentityMisuseAssessment*a=g_new0(DocumentIdentityMisuseAssessment,1);
|
||||
a->identifier=g_strdup(id);a->evidence_identifier=g_strdup(evidence);
|
||||
a->ocr_run_identifier=g_strdup(run);a->status=g_strdup(status);
|
||||
a->justification=g_strdup(justification);a->assessed_at=g_strdup(at);
|
||||
a->previous_identifier=g_strdup(previous);a->origin=g_strdup("human");return a;
|
||||
}
|
||||
DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_copy(
|
||||
const DocumentIdentityMisuseAssessment*a)
|
||||
{return a?document_identity_misuse_assessment_new(a->identifier,
|
||||
a->evidence_identifier,a->ocr_run_identifier,a->status,a->justification,
|
||||
a->assessed_at,a->previous_identifier):NULL;}
|
||||
void document_identity_misuse_assessment_free(DocumentIdentityMisuseAssessment*a)
|
||||
{if(!a)return;g_free(a->identifier);g_free(a->evidence_identifier);
|
||||
g_free(a->ocr_run_identifier);g_free(a->status);g_free(a->justification);
|
||||
g_free(a->assessed_at);g_free(a->previous_identifier);g_free(a->origin);g_free(a);}
|
||||
#define MISUSE_GETTER(name,field) \
|
||||
const char *document_identity_misuse_assessment_get_##name( \
|
||||
const DocumentIdentityMisuseAssessment*a){return a?a->field:NULL;}
|
||||
MISUSE_GETTER(identifier,identifier)
|
||||
MISUSE_GETTER(evidence_identifier,evidence_identifier)
|
||||
MISUSE_GETTER(ocr_run_identifier,ocr_run_identifier)
|
||||
MISUSE_GETTER(status,status)
|
||||
MISUSE_GETTER(justification,justification)
|
||||
MISUSE_GETTER(assessed_at,assessed_at)
|
||||
MISUSE_GETTER(previous_identifier,previous_identifier)
|
||||
MISUSE_GETTER(origin,origin)
|
||||
|
||||
PersonEvidenceFactualRelation *person_evidence_factual_relation_new(
|
||||
const char *id,const char *person,const char *evidence,const char *run,
|
||||
const char *type,const char *note,const char *at,gboolean active)
|
||||
|
|
|
|||
49
src/views/document_identity_misuse_editor.c
Normal file
49
src/views/document_identity_misuse_editor.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "views/document_identity_misuse_editor.h"
|
||||
#include "core/document_identity_misuse_service.h"
|
||||
#include "dao/identity_ocr_dao.h"
|
||||
struct DocumentIdentityMisuseEditor{Database*database;char*evidence;GtkWidget*root;
|
||||
GtkDropDown*status;GtkStringList*status_labels;GtkDropDown*run;GtkStringList*run_labels;
|
||||
GPtrArray*run_ids;GtkTextBuffer*justification;GtkLabel*error;GtkBox*history;};
|
||||
static void clear_box(GtkBox*b){GtkWidget*c=gtk_widget_get_first_child(GTK_WIDGET(b));while(c){GtkWidget*n=gtk_widget_get_next_sibling(c);gtk_box_remove(b,c);c=n;}}
|
||||
static char *buffer_text(GtkTextBuffer*b){GtkTextIter s,e;gtk_text_buffer_get_bounds(b,&s,&e);char*t=gtk_text_buffer_get_text(b,&s,&e,FALSE);g_strstrip(t);return t;}
|
||||
gboolean document_identity_misuse_editor_refresh(DocumentIdentityMisuseEditor*x,GError**error)
|
||||
{
|
||||
if(!x)return FALSE;
|
||||
clear_box(x->history);GPtrArray*h=document_identity_misuse_service_history(x->database,x->evidence,error);if(!h)return FALSE;
|
||||
if(h->len==0)gtk_box_append(x->history,gtk_label_new("Aucune évaluation humaine enregistrée."));
|
||||
for(gint i=(gint)h->len-1;i>=0;i--){DocumentIdentityMisuseAssessment*a=g_ptr_array_index(h,(guint)i);
|
||||
const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_status(document_identity_misuse_assessment_get_status(a));
|
||||
const char*j=document_identity_misuse_assessment_get_justification(a),*r=document_identity_misuse_assessment_get_ocr_run_identifier(a);
|
||||
char*t=g_strdup_printf("%s — %s\nOrigine : humaine%s%s\nJustification : %s",
|
||||
s?s->label:"Statut inconnu",document_identity_misuse_assessment_get_assessed_at(a),
|
||||
r?" — OcrRun : ":"",r?r:"",j&&*j?j:"Aucune (statut indéterminé)");
|
||||
GtkWidget*l=gtk_label_new(t);gtk_label_set_xalign(GTK_LABEL(l),0);gtk_label_set_wrap(GTK_LABEL(l),TRUE);gtk_box_append(x->history,l);g_free(t);}
|
||||
g_ptr_array_unref(h);return TRUE;
|
||||
}
|
||||
static void save(GtkButton*b,gpointer data)
|
||||
{
|
||||
(void)b;DocumentIdentityMisuseEditor*x=data;guint si=gtk_drop_down_get_selected(x->status),ri=gtk_drop_down_get_selected(x->run);gsize count=0;
|
||||
const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_statuses(&count);if(si>=count)return;
|
||||
char*j=buffer_text(x->justification);const char*run=ri>0&&ri-1<x->run_ids->len?g_ptr_array_index(x->run_ids,ri-1):NULL;GError*error=NULL;
|
||||
if(!document_identity_misuse_service_add(x->database,x->evidence,s[si].code,j,run,NULL,&error)){
|
||||
gtk_label_set_text(x->error,error?error->message:"Erreur d’enregistrement.");gtk_widget_set_visible(GTK_WIDGET(x->error),TRUE);g_clear_error(&error);
|
||||
}else{gtk_widget_set_visible(GTK_WIDGET(x->error),FALSE);gtk_text_buffer_set_text(x->justification,"",-1);document_identity_misuse_editor_refresh(x,NULL);}g_free(j);
|
||||
}
|
||||
DocumentIdentityMisuseEditor *document_identity_misuse_editor_new(Database*d,const char*evidence)
|
||||
{
|
||||
if(!d||!g_uuid_string_is_valid(evidence))return NULL;
|
||||
DocumentIdentityMisuseEditor*x=g_new0(DocumentIdentityMisuseEditor,1);x->database=d;x->evidence=g_strdup(evidence);
|
||||
x->root=gtk_box_new(GTK_ORIENTATION_VERTICAL,6);gtk_widget_set_name(x->root,"document-identity-misuse-editor");
|
||||
GtkWidget*title=gtk_label_new("Usage de l’identité du document — évaluation humaine distincte");gtk_label_set_xalign(GTK_LABEL(title),0);gtk_box_append(GTK_BOX(x->root),title);
|
||||
x->status_labels=gtk_string_list_new(NULL);gsize count=0;const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_statuses(&count);for(guint i=0;i<count;i++)gtk_string_list_append(x->status_labels,s[i].label);
|
||||
x->status=GTK_DROP_DOWN(gtk_drop_down_new(G_LIST_MODEL(g_object_ref(x->status_labels)),NULL));gtk_widget_set_name(GTK_WIDGET(x->status),"identity-misuse-status");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->status));
|
||||
x->run_labels=gtk_string_list_new(NULL);gtk_string_list_append(x->run_labels,"Aucune exécution OCR");x->run_ids=g_ptr_array_new_with_free_func(g_free);GPtrArray*runs=document_identity_misuse_service_list_runs(d,evidence,NULL);
|
||||
if(runs){for(guint i=0;i<runs->len;i++){IdentityOcrRunRecord*r=g_ptr_array_index(runs,i);char*l=g_strdup_printf("%s — %s",r->executed_at,r->engine);gtk_string_list_append(x->run_labels,l);g_ptr_array_add(x->run_ids,g_strdup(r->id));g_free(l);}g_ptr_array_unref(runs);}
|
||||
x->run=GTK_DROP_DOWN(gtk_drop_down_new(G_LIST_MODEL(g_object_ref(x->run_labels)),NULL));gtk_widget_set_name(GTK_WIDGET(x->run),"identity-misuse-ocr-run");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->run));
|
||||
gtk_box_append(GTK_BOX(x->root),gtk_label_new("Justification factuelle"));GtkWidget*jv=gtk_text_view_new();x->justification=gtk_text_view_get_buffer(GTK_TEXT_VIEW(jv));gtk_widget_set_name(jv,"identity-misuse-justification");gtk_widget_set_size_request(jv,-1,60);gtk_box_append(GTK_BOX(x->root),jv);
|
||||
x->error=GTK_LABEL(gtk_label_new(""));gtk_widget_set_name(GTK_WIDGET(x->error),"identity-misuse-error");gtk_widget_add_css_class(GTK_WIDGET(x->error),"error");gtk_widget_set_visible(GTK_WIDGET(x->error),FALSE);gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->error));
|
||||
GtkWidget*button=gtk_button_new_with_label("Enregistrer l’évaluation humaine");gtk_widget_set_name(button,"identity-misuse-save");g_signal_connect(button,"clicked",G_CALLBACK(save),x);gtk_box_append(GTK_BOX(x->root),button);
|
||||
x->history=GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL,4));gtk_widget_set_name(GTK_WIDGET(x->history),"identity-misuse-history");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->history));document_identity_misuse_editor_refresh(x,NULL);return x;
|
||||
}
|
||||
GtkWidget *document_identity_misuse_editor_get_widget(DocumentIdentityMisuseEditor*x){return x?x->root:NULL;}
|
||||
void document_identity_misuse_editor_free(DocumentIdentityMisuseEditor*x){if(!x)return;g_clear_object(&x->status_labels);g_clear_object(&x->run_labels);g_ptr_array_unref(x->run_ids);g_free(x->evidence);g_free(x);}
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include "views/evidence_identity_ocr_dialog.h"
|
||||
#include "views/person_vocabulary_adapter.h"
|
||||
#include "views/document_authenticity_editor.h"
|
||||
#include "views/document_identity_misuse_editor.h"
|
||||
#include "widgets/ocr_provenance_overlay.h"
|
||||
|
||||
struct EvidenceMetadataDialogResult
|
||||
|
|
@ -50,6 +51,7 @@ typedef struct
|
|||
gpointer user_data;
|
||||
gboolean completed;
|
||||
DocumentAuthenticityEditor *authenticity_editor;
|
||||
DocumentIdentityMisuseEditor *identity_misuse_editor;
|
||||
} EvidenceMetadataDialogContext;
|
||||
|
||||
static void evidence_metadata_dialog_complete(
|
||||
|
|
@ -92,6 +94,7 @@ static void evidence_metadata_dialog_context_free(gpointer data)
|
|||
g_clear_pointer(&context->ocr_run_identifiers, g_ptr_array_unref);
|
||||
g_clear_object(&context->ocr_run_labels);
|
||||
document_authenticity_editor_free(context->authenticity_editor);
|
||||
document_identity_misuse_editor_free(context->identity_misuse_editor);
|
||||
g_free(context->investigation_root);
|
||||
g_free(context->evidence_identifier);
|
||||
g_free(context);
|
||||
|
|
@ -554,6 +557,16 @@ gboolean evidence_metadata_dialog_present_with_ocr(
|
|||
document_authenticity_editor_get_widget(context->authenticity_editor));
|
||||
gtk_box_append(GTK_BOX(main_box), auth_scroll);
|
||||
}
|
||||
context->identity_misuse_editor = document_identity_misuse_editor_new(
|
||||
database, evidence_record_get_identifier(record));
|
||||
if (context->identity_misuse_editor != NULL) {
|
||||
GtkWidget *misuse_scroll = gtk_scrolled_window_new();
|
||||
gtk_widget_set_size_request(misuse_scroll, -1, 300);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(misuse_scroll),
|
||||
document_identity_misuse_editor_get_widget(
|
||||
context->identity_misuse_editor));
|
||||
gtk_box_append(GTK_BOX(main_box), misuse_scroll);
|
||||
}
|
||||
IdentityTraceabilityDao *trace_dao = identity_traceability_dao_new(database);
|
||||
if (trace_dao != NULL) {
|
||||
GPtrArray *relations = identity_traceability_dao_list_factual_relations_by_evidence(
|
||||
|
|
|
|||
|
|
@ -525,7 +525,7 @@ static void test_database_initialize_valid_database(void)
|
|||
"FROM investigation;"
|
||||
);
|
||||
|
||||
assert(strcmp(schema_version, "19") == 0);
|
||||
assert(strcmp(schema_version, "20") == 0);
|
||||
test_database_assert_table_exists(database, "person_role_assignments");
|
||||
test_database_assert_table_exists(database, "bank_account_entities");
|
||||
test_database_assert_table_exists(database, "relation_types");
|
||||
|
|
@ -543,6 +543,15 @@ static void test_database_initialize_valid_database(void)
|
|||
"person_role_vocabulary");
|
||||
test_database_assert_table_exists(database,
|
||||
"identification_status_vocabulary");
|
||||
test_database_assert_table_exists(database, "person_profile_fields");
|
||||
test_database_assert_table_exists(database,
|
||||
"document_identity_misuse_assessments");
|
||||
test_database_assert_table_exists(database,
|
||||
"person_ocr_field_projections");
|
||||
test_database_assert_column_exists(database,
|
||||
"person_ocr_field_projections", "previous_value");
|
||||
test_database_assert_column_exists(database,
|
||||
"person_ocr_field_projections", "origin");
|
||||
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
||||
|
||||
assert(created_at[0] != '\0');
|
||||
|
|
@ -1001,7 +1010,7 @@ static void test_database_migrate_v1_to_v2(void)
|
|||
assert(
|
||||
strcmp(
|
||||
schema_version,
|
||||
"19"
|
||||
"20"
|
||||
) == 0
|
||||
);
|
||||
|
||||
|
|
@ -1431,6 +1440,7 @@ static void test_database_migrate_v12_to_v13_preserves_legacy_link(void)
|
|||
"'20000000-0000-4000-8000-000000000014','alleged_scammer',"
|
||||
"'2026-07-28T08:00:00Z');"
|
||||
"DROP TABLE person_ocr_field_projections;"
|
||||
"DROP TABLE document_identity_misuse_assessments;"
|
||||
"DROP TABLE person_profile_fields;"
|
||||
"DROP TABLE person_identification_assessments;"
|
||||
"DROP TABLE person_evidence_factual_relations;"
|
||||
|
|
@ -1466,7 +1476,7 @@ static void test_database_migrate_v12_to_v13_preserves_legacy_link(void)
|
|||
legacy_sources = test_database_read_single_text(sqlite_database,
|
||||
"SELECT COUNT(*) FROM preuve_entite_sources "
|
||||
"WHERE source_kind='legacy_manual';");
|
||||
assert(strcmp(version, "19") == 0);
|
||||
assert(strcmp(version, "20") == 0);
|
||||
assert(strcmp(legacy_sources, "1") == 0);
|
||||
char *legacy_role = test_database_read_single_text(sqlite_database,
|
||||
"SELECT role_code || ':' || provenance_kind "
|
||||
|
|
|
|||
24
tests/test_document_identity_misuse_editor_gtk.c
Normal file
24
tests/test_document_identity_misuse_editor_gtk.c
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "views/document_identity_misuse_editor.h"
|
||||
#include "core/document_identity_misuse_service.h"
|
||||
#include "database/database.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include <sqlite3.h>
|
||||
#define EVIDENCE "11000000-0000-4000-8000-000000000020"
|
||||
#define RUN "31000000-0000-4000-8000-000000000020"
|
||||
#define AT "2026-08-02T10:00:00Z"
|
||||
static GtkWidget *named(GtkWidget*w,const char*n){if(g_strcmp0(gtk_widget_get_name(w),n)==0)return w;for(GtkWidget*c=gtk_widget_get_first_child(w);c;c=gtk_widget_get_next_sibling(c)){GtkWidget*r=named(c,n);if(r)return r;}return NULL;}
|
||||
static void test_editor(void)
|
||||
{
|
||||
GError*error=NULL;char*dir=g_dir_make_tmp("labfy-misuse-gtk-XXXXXX",&error);g_assert_no_error(error);char*path=g_build_filename(dir,"Enquete.sqlite",NULL);g_assert_true(database_initialize(path,"SPECIMEN",dir));
|
||||
sqlite3*s=NULL;g_assert_cmpint(sqlite3_open(path,&s),==,SQLITE_OK);char*message=NULL;
|
||||
const char*sql="INSERT INTO preuves(id,name,relative_path,type_id,size_bytes,sha256,imported_at,updated_at,status,locked,original_name) VALUES('" EVIDENCE "','specimen.png','specimen.png',2,8,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa','" AT "','" AT "','active',0,'specimen.png');INSERT INTO identity_ocr_runs(id,evidence_id,expected_sha256,page_number,document_type,document_side,engine,requested_languages,available_languages,parameters,preprocessing_profile,executed_at,status,text_relative_path,text_sha256,tsv_relative_path,tsv_sha256) VALUES('" RUN "','" EVIDENCE "','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',1,'identity_card','front','tesseract','fra','fra','SPECIMEN','none','" AT "','success','raw.txt','bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb','raw.tsv','cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc');";
|
||||
g_assert_cmpint(sqlite3_exec(s,sql,NULL,NULL,&message),==,SQLITE_OK);sqlite3_free(message);sqlite3_close(s);
|
||||
Database*d=database_open(path);g_assert_nonnull(d);DocumentIdentityMisuseEditor*x=document_identity_misuse_editor_new(d,EVIDENCE);g_assert_nonnull(x);GtkWidget*root=document_identity_misuse_editor_get_widget(x);g_object_ref_sink(root);
|
||||
GtkDropDown*status=GTK_DROP_DOWN(named(root,"identity-misuse-status"));GtkDropDown*run=GTK_DROP_DOWN(named(root,"identity-misuse-ocr-run"));GtkLabel*error_label=GTK_LABEL(named(root,"identity-misuse-error"));GtkWidget*save=named(root,"identity-misuse-save");
|
||||
g_assert_cmpuint(g_list_model_get_n_items(gtk_drop_down_get_model(status)),==,3);g_assert_cmpuint(g_list_model_get_n_items(gtk_drop_down_get_model(run)),==,2);
|
||||
gtk_drop_down_set_selected(status,1);g_signal_emit_by_name(save,"clicked");g_assert_true(gtk_widget_get_visible(GTK_WIDGET(error_label)));
|
||||
GtkTextBuffer*j=gtk_text_view_get_buffer(GTK_TEXT_VIEW(named(root,"identity-misuse-justification")));gtk_text_buffer_set_text(j,"Indices SPECIMEN",-1);gtk_drop_down_set_selected(run,1);g_signal_emit_by_name(save,"clicked");g_assert_false(gtk_widget_get_visible(GTK_WIDGET(error_label)));
|
||||
GPtrArray*h=document_identity_misuse_service_history(d,EVIDENCE,&error);g_assert_no_error(error);g_assert_cmpuint(h->len,==,1);DocumentIdentityMisuseAssessment*a=g_ptr_array_index(h,0);g_assert_cmpstr(document_identity_misuse_assessment_get_status(a),==,"presumed");g_assert_cmpstr(document_identity_misuse_assessment_get_origin(a),==,"human");g_ptr_array_unref(h);
|
||||
g_object_unref(root);document_identity_misuse_editor_free(x);database_close(d);g_remove(path);g_rmdir(dir);g_free(path);g_free(dir);
|
||||
}
|
||||
int main(int argc,char**argv){gtk_init();g_test_init(&argc,&argv,NULL);g_test_add_func("/identity-misuse/editor",test_editor);return g_test_run();}
|
||||
|
|
@ -329,6 +329,8 @@ static gboolean drive(gpointer data)
|
|||
g_assert_true(gtk_window_get_destroy_with_parent(dialog));
|
||||
g_assert_nonnull(find_named(GTK_WIDGET(dialog),
|
||||
"document-authenticity-editor"));
|
||||
g_assert_nonnull(find_named(GTK_WIDGET(dialog),
|
||||
"document-identity-misuse-editor"));
|
||||
}
|
||||
if (dialog == NULL) {
|
||||
if (context->phase == 1) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "database/database.h"
|
||||
#include "models/identity_traceability.h"
|
||||
#include "core/document_authenticity_service.h"
|
||||
#include "core/document_identity_misuse_service.h"
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <sqlite3.h>
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
#define ASSESSMENT1 "60000000-0000-4000-8000-000000000018"
|
||||
#define ASSESSMENT2 "60000000-0000-4000-8000-000000000019"
|
||||
#define FACT "70000000-0000-4000-8000-000000000018"
|
||||
#define MISUSE1 "90000000-0000-4000-8000-000000000018"
|
||||
#define MISUSE2 "90000000-0000-4000-8000-000000000019"
|
||||
#define AT "2026-07-30T10:00:00Z"
|
||||
|
||||
typedef struct{char*dir,*path;Database*database;}Fixture;
|
||||
|
|
@ -114,6 +117,73 @@ static void test_authenticity_service(void)
|
|||
NULL,&error));g_assert_nonnull(error);g_clear_error(&error);
|
||||
h=document_authenticity_service_history(f.database,EVIDENCE,&error);g_assert_cmpuint(h->len,==,2);g_ptr_array_unref(h);fixture_free(&f);
|
||||
}
|
||||
static void test_identity_misuse_service(void)
|
||||
{
|
||||
gsize count=0;const DocumentIdentityMisuseStatus*statuses=
|
||||
document_identity_misuse_service_statuses(&count);
|
||||
static const char*codes[]={"indeterminate","presumed","confirmed"};
|
||||
static const char*labels[]={"Hypothèse d’usurpation indéterminée",
|
||||
"Document présumé utilisé dans une usurpation d’identité",
|
||||
"Utilisation dans une usurpation d’identité confirmée"};
|
||||
g_assert_cmpuint(count,==,3);for(guint i=0;i<count;i++){
|
||||
g_assert_cmpstr(statuses[i].code,==,codes[i]);g_assert_cmpstr(statuses[i].label,==,labels[i]);}
|
||||
g_assert_null(document_identity_misuse_assessment_new(MISUSE1,EVIDENCE,NULL,
|
||||
"presumed",NULL,AT,NULL));
|
||||
Fixture f=fixture_new();GError*error=NULL;DocumentIdentityMisuseAssessment*created=NULL;
|
||||
g_assert_true(document_identity_misuse_service_add(f.database,EVIDENCE,
|
||||
"indeterminate",NULL,NULL,&created,&error));g_assert_no_error(error);
|
||||
g_assert_cmpstr(document_identity_misuse_assessment_get_origin(created),==,"human");
|
||||
document_identity_misuse_assessment_free(created);
|
||||
g_assert_true(document_identity_misuse_service_add(f.database,EVIDENCE,
|
||||
"presumed","Indices SPECIMEN",RUN,&created,&error));g_assert_no_error(error);
|
||||
g_assert_nonnull(document_identity_misuse_assessment_get_previous_identifier(created));
|
||||
document_identity_misuse_assessment_free(created);
|
||||
g_assert_false(document_identity_misuse_service_add(f.database,EVIDENCE,
|
||||
"confirmed","Conclusion SPECIMEN","30000000-0000-4000-8000-000000000099",
|
||||
NULL,&error));g_assert_nonnull(error);g_clear_error(&error);
|
||||
GPtrArray*h=document_identity_misuse_service_history(f.database,EVIDENCE,&error);
|
||||
g_assert_no_error(error);g_assert_cmpuint(h->len,==,2);g_ptr_array_unref(h);
|
||||
database_close(f.database);f.database=database_open(f.path);g_assert_nonnull(f.database);
|
||||
h=document_identity_misuse_service_history(f.database,EVIDENCE,&error);
|
||||
g_assert_cmpuint(h->len,==,2);g_ptr_array_unref(h);
|
||||
database_close(f.database);f.database=NULL;sqlite3*d=NULL;
|
||||
g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);
|
||||
char*automatic=scalar(d,"SELECT (SELECT COUNT(*) FROM person_role_assignments)"
|
||||
"||(SELECT COUNT(*) FROM person_evidence_factual_relations)"
|
||||
"||(SELECT COUNT(*) FROM person_identification_assessments)"
|
||||
"||(SELECT COUNT(*) FROM document_authenticity_assessments);");
|
||||
g_assert_cmpstr(automatic,==,"0000");g_free(automatic);char*message=NULL;
|
||||
g_assert_cmpint(sqlite3_exec(d,"UPDATE document_identity_misuse_assessments "
|
||||
"SET status='confirmed';",NULL,NULL,&message),!=,SQLITE_OK);
|
||||
sqlite3_free(message);message=NULL;g_assert_cmpint(sqlite3_exec(d,
|
||||
"DELETE FROM document_identity_misuse_assessments;",NULL,NULL,&message),!=,SQLITE_OK);
|
||||
sqlite3_free(message);sqlite3_close(d);f.database=database_open(f.path);
|
||||
g_assert_nonnull(f.database);fixture_free(&f);
|
||||
}
|
||||
static void test_migrate_v19_to_v20(void)
|
||||
{
|
||||
Fixture f=fixture_new();database_close(f.database);f.database=NULL;sqlite3*d=NULL;
|
||||
g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);
|
||||
exec_ok(d,"DROP TABLE document_identity_misuse_assessments;"
|
||||
"UPDATE metadata SET value='19' WHERE key='schema_version';");sqlite3_close(d);
|
||||
f.database=database_open(f.path);g_assert_nonnull(f.database);
|
||||
g_assert_true(database_migrate_to_latest(f.database));database_close(f.database);f.database=NULL;
|
||||
g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);char*version=scalar(d,
|
||||
"SELECT value FROM metadata WHERE key='schema_version';");char*table=scalar(d,
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='document_identity_misuse_assessments';");
|
||||
g_assert_cmpstr(version,==,"20");g_assert_cmpstr(table,==,"1");g_free(version);g_free(table);
|
||||
exec_ok(d,"DROP TABLE document_identity_misuse_assessments;"
|
||||
"UPDATE metadata SET value='19' WHERE key='schema_version';"
|
||||
"CREATE TABLE document_identity_misuse_assessments(dummy TEXT);");sqlite3_close(d);
|
||||
f.database=database_open(f.path);g_test_expect_message(NULL,G_LOG_LEVEL_WARNING,
|
||||
"*Impossible d’installer la migration SQLite V20*");
|
||||
g_assert_false(database_migrate_to_latest(f.database));g_test_assert_expected_messages();
|
||||
database_close(f.database);f.database=NULL;g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);
|
||||
version=scalar(d,"SELECT value FROM metadata WHERE key='schema_version';");
|
||||
g_assert_cmpstr(version,==,"19");g_free(version);exec_ok(d,
|
||||
"DROP TABLE document_identity_misuse_assessments;");sqlite3_close(d);
|
||||
f.database=database_open(f.path);g_assert_true(database_migrate_to_latest(f.database));fixture_free(&f);
|
||||
}
|
||||
static void test_dao_history_and_relations(void)
|
||||
{
|
||||
Fixture f=fixture_new();GError*e=NULL;
|
||||
|
|
@ -218,6 +288,7 @@ static void test_migrate_v17_preserves_ocr(void)
|
|||
Fixture f=fixture_new();database_close(f.database);f.database=NULL;
|
||||
sqlite3*d=NULL;g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);
|
||||
exec_ok(d,"PRAGMA foreign_keys=OFF;"
|
||||
"DROP TABLE document_identity_misuse_assessments;"
|
||||
"DROP TABLE person_ocr_field_projections;DROP TABLE person_profile_fields;"
|
||||
"DROP TABLE person_identification_assessments;"
|
||||
"DROP TABLE person_evidence_factual_relations;"
|
||||
|
|
@ -258,7 +329,7 @@ static void test_migrate_v17_preserves_ocr(void)
|
|||
char*version=scalar(d,"SELECT value FROM metadata WHERE key='schema_version';");
|
||||
char*values=scalar(d,"SELECT raw_value||':'||normalized_value||':'||corrected_value"
|
||||
"||':'||confirmation_state FROM identity_field_observations WHERE id='" FIELD "';");
|
||||
g_assert_cmpstr(version,==,"19");
|
||||
g_assert_cmpstr(version,==,"20");
|
||||
g_assert_cmpstr(values,==,"BRUT SPECIMEN:BRUT SPECIMEN:CORRIGÉ SPECIMEN:unconfirmed");
|
||||
sqlite3_close(d);g_free(version);g_free(values);
|
||||
f.database=database_open(f.path);g_assert_true(database_migrate_to_latest(f.database));
|
||||
|
|
@ -268,6 +339,8 @@ int main(int argc,char**argv)
|
|||
{g_test_init(&argc,&argv,NULL);g_test_add_func("/v18/models",test_models);
|
||||
g_test_add_func("/v18/dao-history-relations",test_dao_history_and_relations);
|
||||
g_test_add_func("/v18/authenticity-service",test_authenticity_service);
|
||||
g_test_add_func("/v20/identity-misuse-service",test_identity_misuse_service);
|
||||
g_test_add_func("/v20/migrate-v19",test_migrate_v19_to_v20);
|
||||
g_test_add_func("/v18/sqlite-negative-guards",test_sqlite_negative_guards);
|
||||
g_test_add_func("/v18/migrate-v17",test_migrate_v17_preserves_ocr);
|
||||
return g_test_run();}
|
||||
|
|
|
|||
21
tests/test_person_details_traceability_gtk.c
Normal file
21
tests/test_person_details_traceability_gtk.c
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include "widgets/entity_details_panel.h"
|
||||
#include "models/evidence_record.h"
|
||||
#include "models/identity_traceability.h"
|
||||
#include <gtk/gtk.h>
|
||||
#define PERSON "22000000-0000-4000-8000-000000000020"
|
||||
#define EVIDENCE "12000000-0000-4000-8000-000000000020"
|
||||
#define RELATION "72000000-0000-4000-8000-000000000020"
|
||||
#define AT "2026-08-02T10:00:00Z"
|
||||
static void collect_text(GtkWidget*w,GString*out){if(GTK_IS_LABEL(w))g_string_append(out,gtk_label_get_text(GTK_LABEL(w)));for(GtkWidget*c=gtk_widget_get_first_child(w);c;c=gtk_widget_get_next_sibling(c)){g_string_append_c(out,'\n');collect_text(c,out);}}
|
||||
static void test_person_details(void)
|
||||
{
|
||||
GError*error=NULL;EntityDetailsPanel*panel=entity_details_panel_new();g_assert_nonnull(panel);GtkWidget*root=entity_details_panel_get_widget(panel);g_object_ref_sink(root);
|
||||
EntityRecord*person=entity_record_new(PERSON,"person","Personne SPECIMEN","Personne SPECIMEN",NULL,0,AT,AT,ENTITY_STATUS_ACTIVE,&error);g_assert_no_error(error);g_assert_nonnull(person);entity_details_panel_set_entity(panel,person);
|
||||
EvidenceRecord*evidence=evidence_record_new(EVIDENCE,"preuve-specimen.png","preuve-specimen.png","preuve-specimen.png","2",8,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",AT,NULL,NULL,NULL,EVIDENCE_INTEGRITY_STATUS_UNKNOWN,&error);g_assert_no_error(error);g_assert_nonnull(evidence);
|
||||
GPtrArray*evidence_records=g_ptr_array_new();g_ptr_array_add(evidence_records,evidence);PersonEvidenceFactualRelation*relation=person_evidence_factual_relation_new(RELATION,PERSON,EVIDENCE,NULL,"identity_observed_in","Note factuelle SPECIMEN",AT,TRUE);g_assert_nonnull(relation);GPtrArray*relations=g_ptr_array_new();g_ptr_array_add(relations,relation);
|
||||
GHashTable*fields=g_hash_table_new(g_str_hash,g_str_equal);g_hash_table_insert(fields,"surname","NOM SPECIMEN");g_hash_table_insert(fields,"birth_date","2000-01-01");
|
||||
entity_details_panel_set_person_profile_fields(panel,fields);entity_details_panel_set_person_factual_relations(panel,relations,evidence_records);
|
||||
GString*text=g_string_new(NULL);collect_text(root,text);g_assert_nonnull(strstr(text->str,"Valeurs confirmées du profil"));g_assert_nonnull(strstr(text->str,"NOM SPECIMEN"));g_assert_nonnull(strstr(text->str,"Relations factuelles"));g_assert_nonnull(strstr(text->str,"Identité observée dans la preuve"));g_assert_nonnull(strstr(text->str,"Note factuelle SPECIMEN"));
|
||||
g_string_free(text,TRUE);g_hash_table_unref(fields);g_ptr_array_unref(relations);g_ptr_array_unref(evidence_records);person_evidence_factual_relation_free(relation);evidence_record_free(evidence);entity_record_free(person);entity_details_panel_free(panel);g_object_unref(root);
|
||||
}
|
||||
int main(int argc,char**argv){gtk_init();g_test_init(&argc,&argv,NULL);g_test_add_func("/person-details/traceability",test_person_details);return g_test_run();}
|
||||
473
ticket109.md
473
ticket109.md
|
|
@ -1,473 +0,0 @@
|
|||
## Contexte
|
||||
|
||||
Lors de la création d’une personne dans une enquête, l’utilisateur doit
|
||||
actuellement renseigner les données manuellement et ne peut pas choisir
|
||||
clairement la nature de la personne ni associer facilement une preuve.
|
||||
|
||||
Le parcours devient particulièrement long lorsque les noms de fichiers
|
||||
ne permettent pas de savoir ce qu’ils contiennent.
|
||||
|
||||
Le cas des documents d’identité nécessite également un traitement
|
||||
forensique strict :
|
||||
|
||||
- conservation de l’original ;
|
||||
- analyse sur une copie de travail ;
|
||||
- OCR contrôlé ;
|
||||
- validation humaine ;
|
||||
- distinction entre texte brut, valeur normalisée et correction manuelle ;
|
||||
- aucune affirmation automatique concernant l’identité réelle ou
|
||||
l’authenticité du document.
|
||||
|
||||
## Objectif
|
||||
|
||||
Créer un assistant fluide permettant, depuis la fenêtre de création d’une
|
||||
personne :
|
||||
|
||||
1. de sélectionner son rôle ou sa nature dans l’enquête ;
|
||||
2. de choisir une preuve déjà importée ;
|
||||
3. d’importer immédiatement une nouvelle preuve ;
|
||||
4. de visualiser la preuve avant de la sélectionner ;
|
||||
5. de qualifier le type de preuve ;
|
||||
6. de lancer une analyse adaptée au type choisi ;
|
||||
7. d’utiliser l’OCR pour préremplir les champs d’un document d’identité ;
|
||||
8. de corriger manuellement les données proposées ;
|
||||
9. de conserver la provenance complète de chaque valeur ;
|
||||
10. de garantir l’intégrité du fichier original.
|
||||
|
||||
## Terminologie
|
||||
|
||||
Ne pas confondre :
|
||||
|
||||
- le rôle de la personne dans l’enquête ;
|
||||
- son niveau d’identification ;
|
||||
- l’authenticité du document présenté ;
|
||||
- la confiance accordée aux différentes observations.
|
||||
|
||||
Une personne peut par exemple avoir :
|
||||
|
||||
- rôle : Identité présentée ;
|
||||
- identification : Non vérifiée ;
|
||||
- document : Authenticité indéterminée ;
|
||||
- hypothèse : Identité potentiellement usurpée.
|
||||
|
||||
L’application ne doit jamais transformer automatiquement cette personne
|
||||
en auteur identifié.
|
||||
|
||||
## Fenêtre de création d’une personne
|
||||
|
||||
Ajouter un champ contrôlé :
|
||||
|
||||
Rôle dans l’enquête
|
||||
|
||||
Valeurs initiales proposées :
|
||||
|
||||
- Auteur présumé ;
|
||||
- Identité présentée ;
|
||||
- Identité potentiellement usurpée ;
|
||||
- Victime ;
|
||||
- Témoin ;
|
||||
- Titulaire bancaire déclaré ;
|
||||
- Intermédiaire ;
|
||||
- Personne citée ;
|
||||
- Autre.
|
||||
|
||||
Ces valeurs doivent provenir du vocabulaire contrôlé du projet.
|
||||
|
||||
Le champ existant « Identification » reste distinct et conserve des états
|
||||
tels que :
|
||||
|
||||
- Inconnu ;
|
||||
- Non vérifié ;
|
||||
- Partiellement identifié ;
|
||||
- Identifié ;
|
||||
- Contesté.
|
||||
|
||||
## Association d’une preuve
|
||||
|
||||
Depuis la fenêtre de création d’une personne, proposer deux actions :
|
||||
|
||||
- Sélectionner une preuve existante ;
|
||||
- Importer une nouvelle preuve.
|
||||
|
||||
L’utilisateur ne doit pas être obligé de fermer la fenêtre, importer la
|
||||
preuve ailleurs, puis recommencer la création de la personne.
|
||||
|
||||
### Preuve existante
|
||||
|
||||
La sélection doit afficher au minimum :
|
||||
|
||||
- miniature ou aperçu ;
|
||||
- nom du fichier ;
|
||||
- type de preuve ;
|
||||
- taille ;
|
||||
- date d’import ;
|
||||
- empreinte SHA-256 abrégée ;
|
||||
- description éventuelle ;
|
||||
- état d’intégrité.
|
||||
|
||||
Prévoir une recherche et un filtrage par type.
|
||||
|
||||
### Nouvelle preuve
|
||||
|
||||
L’import déclenché depuis la fenêtre doit utiliser le mécanisme forensique
|
||||
central du projet :
|
||||
|
||||
- copie contrôlée dans l’enquête ;
|
||||
- calcul du SHA-256 ;
|
||||
- conservation du nom original ;
|
||||
- enregistrement de la provenance ;
|
||||
- aucune modification du fichier source ;
|
||||
- détection du type MIME réel ;
|
||||
- détection des collisions ;
|
||||
- traitement asynchrone ;
|
||||
- annulation sûre.
|
||||
|
||||
À la fin de l’import, la nouvelle preuve doit être automatiquement
|
||||
sélectionnée dans la fenêtre de création de la personne.
|
||||
|
||||
## Aperçu des preuves
|
||||
|
||||
La fenêtre de sélection et d’import doit fournir un aperçu suffisamment
|
||||
grand pour identifier le contenu sans occuper toute la fenêtre.
|
||||
|
||||
Prévoir une zone responsive avec :
|
||||
|
||||
- conservation du ratio ;
|
||||
- ajustement à la zone ;
|
||||
- zoom avant et arrière ;
|
||||
- retour à l’ajustement ;
|
||||
- défilement lorsque l’image est agrandie ;
|
||||
- message explicite lorsque l’aperçu est indisponible.
|
||||
|
||||
Prise en charge minimale :
|
||||
|
||||
- image : aperçu direct ;
|
||||
- PDF : première page avec navigation entre les pages ;
|
||||
- vidéo : miniature, durée et informations principales ;
|
||||
- EML : résumé des en-têtes et liste des pièces jointes ;
|
||||
- autre fichier : icône, type MIME, taille et métadonnées disponibles.
|
||||
|
||||
L’aperçu est une représentation dérivée.
|
||||
|
||||
Il ne doit jamais modifier le fichier original.
|
||||
|
||||
Toute conversion, génération de miniature, rotation, amélioration,
|
||||
redimensionnement ou rendu PDF doit être réalisée dans une zone de
|
||||
travail temporaire contrôlée.
|
||||
|
||||
## Qualification du type de preuve
|
||||
|
||||
Permettre de sélectionner ou confirmer un type contrôlé, par exemple :
|
||||
|
||||
- Document d’identité ;
|
||||
- Carte nationale d’identité ;
|
||||
- Passeport ;
|
||||
- Permis de conduire ;
|
||||
- Document bancaire ;
|
||||
- Capture d’écran ;
|
||||
- Conversation ;
|
||||
- Courrier électronique ;
|
||||
- PDF ;
|
||||
- Photo ;
|
||||
- Vidéo ;
|
||||
- Billet ou justificatif de commande ;
|
||||
- Autre.
|
||||
|
||||
La détection automatique peut proposer un type, mais l’utilisateur doit
|
||||
toujours pouvoir le corriger avant validation.
|
||||
|
||||
Le type choisi détermine les analyses proposées.
|
||||
|
||||
## Document d’identité
|
||||
|
||||
Lorsqu’une preuve est qualifiée comme document d’identité, proposer :
|
||||
|
||||
Analyser le document
|
||||
|
||||
Le traitement doit être asynchrone et annulable.
|
||||
|
||||
Il doit :
|
||||
|
||||
1. vérifier l’intégrité de la preuve ;
|
||||
2. créer une copie de travail ;
|
||||
3. conserver l’original strictement intact ;
|
||||
4. appliquer les conversions uniquement à la copie ;
|
||||
5. exécuter l’OCR ;
|
||||
6. conserver le texte OCR brut ;
|
||||
7. proposer des champs structurés ;
|
||||
8. attendre une validation humaine avant toute intégration.
|
||||
|
||||
## Champs OCR proposés
|
||||
|
||||
Selon les informations réellement visibles, proposer notamment :
|
||||
|
||||
- nom ;
|
||||
- prénoms ;
|
||||
- sexe déclaré ;
|
||||
- date de naissance ;
|
||||
- lieu de naissance ;
|
||||
- nationalité déclarée ;
|
||||
- taille ;
|
||||
- numéro du document ;
|
||||
- date de délivrance ;
|
||||
- date d’expiration ;
|
||||
- autorité de délivrance ;
|
||||
- zone lisible par machine ;
|
||||
- autres valeurs observées.
|
||||
|
||||
Ne jamais inventer une valeur absente ou illisible.
|
||||
|
||||
Chaque proposition doit posséder un état :
|
||||
|
||||
- détectée ;
|
||||
- partielle ;
|
||||
- incertaine ;
|
||||
- invalide ;
|
||||
- corrigée ;
|
||||
- rejetée ;
|
||||
- confirmée.
|
||||
|
||||
## Validation et correction humaine
|
||||
|
||||
Avant la création de la personne, afficher côte à côte autant que possible :
|
||||
|
||||
- l’aperçu du document ;
|
||||
- les valeurs extraites ;
|
||||
- les champs modifiables ;
|
||||
- le niveau de confiance OCR ;
|
||||
- la provenance précise.
|
||||
|
||||
Pour chaque champ, conserver séparément :
|
||||
|
||||
- valeur OCR brute ;
|
||||
- valeur normalisée ;
|
||||
- valeur corrigée manuellement ;
|
||||
- valeur finalement confirmée ;
|
||||
- méthode d’extraction ;
|
||||
- langue OCR ;
|
||||
- version de l’outil ;
|
||||
- date UTC ;
|
||||
- preuve source ;
|
||||
- page ou zone source lorsque disponible.
|
||||
|
||||
Une correction manuelle ne doit jamais écraser le résultat OCR brut.
|
||||
|
||||
L’utilisateur doit pouvoir :
|
||||
|
||||
- modifier une proposition ;
|
||||
- rejeter une proposition ;
|
||||
- laisser un champ vide ;
|
||||
- revenir à la valeur OCR ;
|
||||
- confirmer uniquement certains champs.
|
||||
|
||||
## Authenticité et identité usurpée
|
||||
|
||||
Ajouter un statut contrôlé pour le document :
|
||||
|
||||
- Authenticité indéterminée ;
|
||||
- Présumé authentique ;
|
||||
- Suspect ;
|
||||
- Présumé falsifié ;
|
||||
- Falsifié confirmé ;
|
||||
- Document usurpé présumé ;
|
||||
- Document usurpé confirmé.
|
||||
|
||||
Les statuts affirmatifs doivent nécessiter une validation explicite et
|
||||
une justification.
|
||||
|
||||
L’OCR ne doit jamais conclure :
|
||||
|
||||
- que le document est authentique ;
|
||||
- que la personne figurant sur le document est l’auteur ;
|
||||
- que l’identité est réellement usurpée ;
|
||||
- que le titulaire a participé aux faits.
|
||||
|
||||
Dans le cas courant, le document doit pouvoir être enregistré comme :
|
||||
|
||||
Identité présentée — authenticité indéterminée
|
||||
|
||||
avec une hypothèse séparée :
|
||||
|
||||
Identité potentiellement usurpée
|
||||
|
||||
## Création de la personne
|
||||
|
||||
Après validation, créer la personne avec uniquement les champs confirmés.
|
||||
|
||||
La personne doit être liée à la preuve par une relation factuelle, par
|
||||
exemple :
|
||||
|
||||
- Identité observée dans ;
|
||||
- Document présenté au nom de ;
|
||||
- Données extraites depuis ;
|
||||
- Identité déclarée dans.
|
||||
|
||||
Ne pas créer automatiquement une relation :
|
||||
|
||||
- Est l’auteur ;
|
||||
- Identité réelle de ;
|
||||
- A usurpé l’identité de.
|
||||
|
||||
Les résultats OCR rejetés ne doivent pas devenir des attributs de la
|
||||
personne.
|
||||
|
||||
## Intégrité et provenance
|
||||
|
||||
Le fichier original doit rester immuable.
|
||||
|
||||
Avant chaque analyse :
|
||||
|
||||
- recalculer ou vérifier le SHA-256 ;
|
||||
- bloquer l’analyse en cas de divergence ;
|
||||
- ne jamais réécrire l’empreinte enregistrée pour masquer une différence.
|
||||
|
||||
Tous les fichiers dérivés doivent être identifiables comme tels :
|
||||
|
||||
- miniature ;
|
||||
- rendu de page PDF ;
|
||||
- image préparée pour OCR ;
|
||||
- recadrage ;
|
||||
- correction d’orientation ;
|
||||
- texte OCR ;
|
||||
- JSON de métadonnées.
|
||||
|
||||
Chaque dérivé doit conserver :
|
||||
|
||||
- preuve parente ;
|
||||
- outil ;
|
||||
- version ;
|
||||
- arguments ;
|
||||
- date UTC ;
|
||||
- SHA-256 ;
|
||||
- statut ;
|
||||
- avertissements.
|
||||
|
||||
## Interface
|
||||
|
||||
Le parcours doit rester possible depuis une seule fenêtre ou un assistant
|
||||
cohérent :
|
||||
|
||||
Création de la personne
|
||||
↓
|
||||
choix du rôle
|
||||
↓
|
||||
sélection ou import de la preuve
|
||||
↓
|
||||
aperçu
|
||||
↓
|
||||
qualification du type
|
||||
↓
|
||||
analyse facultative
|
||||
↓
|
||||
révision des propositions
|
||||
↓
|
||||
création de la personne et des relations confirmées
|
||||
|
||||
Utiliser des boutons compacts avec icônes et infobulles.
|
||||
|
||||
Ne pas ajouter de gros boutons occupant inutilement l’interface.
|
||||
|
||||
La fermeture ou l’annulation ne doit créer ni personne partielle, ni
|
||||
attribut partiel, ni relation partielle.
|
||||
|
||||
## Transactions
|
||||
|
||||
La création finale doit être transactionnelle pour :
|
||||
|
||||
- la personne ;
|
||||
- ses attributs confirmés ;
|
||||
- le rattachement à la preuve ;
|
||||
- les observations OCR confirmées ;
|
||||
- les relations ;
|
||||
- les références aux fichiers dérivés conservés.
|
||||
|
||||
En cas d’échec :
|
||||
|
||||
- rollback complet ;
|
||||
- original inchangé ;
|
||||
- aucune personne incomplète ;
|
||||
- aucune relation orpheline ;
|
||||
- aucun fichier temporaire présenté comme preuve permanente.
|
||||
|
||||
## Tests obligatoires
|
||||
|
||||
Ajouter des fixtures exclusivement synthétiques.
|
||||
|
||||
Couvrir au minimum :
|
||||
|
||||
1. choix de chaque rôle contrôlé ;
|
||||
2. sélection d’une preuve existante ;
|
||||
3. import d’une nouvelle preuve depuis la fenêtre ;
|
||||
4. sélection automatique après import ;
|
||||
5. aperçu image ;
|
||||
6. aperçu PDF ;
|
||||
7. aperçu indisponible ;
|
||||
8. navigation entre pages ;
|
||||
9. qualification manuelle du type ;
|
||||
10. document d’identité déclenchant l’OCR ;
|
||||
11. autre type ne déclenchant pas automatiquement l’OCR ;
|
||||
12. original inchangé ;
|
||||
13. SHA-256 inchangé ;
|
||||
14. travail sur copie ;
|
||||
15. texte OCR brut conservé ;
|
||||
16. valeur normalisée séparée ;
|
||||
17. correction manuelle séparée ;
|
||||
18. rejet d’un champ ;
|
||||
19. champ illisible laissé vide ;
|
||||
20. création avec seulement les valeurs confirmées ;
|
||||
21. provenance par champ ;
|
||||
22. annulation ;
|
||||
23. rollback ;
|
||||
24. fermeture de fenêtre pendant l’analyse ;
|
||||
25. changement d’enquête pendant l’analyse ;
|
||||
26. aucune écriture dans la mauvaise base ;
|
||||
27. aucune donnée réelle dans les fixtures ;
|
||||
28. aucune conclusion automatique sur l’authenticité ;
|
||||
29. aucune fusion automatique avec l’auteur présumé ;
|
||||
30. réouverture de l’enquête avec données persistantes.
|
||||
|
||||
Séparer autant que possible :
|
||||
|
||||
- services d’import ;
|
||||
- génération d’aperçu ;
|
||||
- OCR ;
|
||||
- modèle de révision ;
|
||||
- DAO ;
|
||||
- tests GTK ciblés ;
|
||||
- validation visuelle manuelle.
|
||||
|
||||
## Critères d’acceptation
|
||||
|
||||
Le ticket est terminé seulement si :
|
||||
|
||||
- le rôle de la personne peut être sélectionné ;
|
||||
- une preuve existante peut être choisie ;
|
||||
- une nouvelle preuve peut être importée sans quitter le parcours ;
|
||||
- un aperçu exploitable est disponible ;
|
||||
- le type de preuve peut être confirmé ou corrigé ;
|
||||
- un document d’identité peut être analysé par OCR ;
|
||||
- les propositions OCR sont modifiables avant intégration ;
|
||||
- le texte OCR brut n’est jamais écrasé ;
|
||||
- l’original reste inchangé ;
|
||||
- la provenance est persistée ;
|
||||
- seules les valeurs confirmées alimentent la personne ;
|
||||
- aucune identité n’est attribuée automatiquement à l’auteur présumé ;
|
||||
- tous les tests passent.
|
||||
|
||||
## Sécurité de développement
|
||||
|
||||
Codex et tout agent de développement doivent travailler uniquement avec :
|
||||
|
||||
- le dépôt source ;
|
||||
- des fixtures synthétiques ;
|
||||
- des bases SQLite temporaires synthétiques.
|
||||
|
||||
Ils ne doivent jamais accéder à :
|
||||
|
||||
- Enquete.sqlite réelle ;
|
||||
- documents d’identité réels ;
|
||||
- captures réelles ;
|
||||
- e-mails réels ;
|
||||
- données bancaires réelles ;
|
||||
- vidéos réelles ;
|
||||
- autres preuves de l’enquête.
|
||||
10
wc_out.txt
10
wc_out.txt
|
|
@ -1,10 +0,0 @@
|
|||
9339 src/core/application.c
|
||||
142 src/core/person_creation_task.c
|
||||
2037 src/views/create_person_dialog.c
|
||||
660 src/views/evidence_metadata_dialog.c
|
||||
2245 src/views/main_window.c
|
||||
286 src/views/person_factual_relation_editor.c
|
||||
193 src/views/person_vocabulary_adapter.c
|
||||
1550 src/widgets/entity_details_panel.c
|
||||
4078 src/widgets/workspace.c
|
||||
20530 total
|
||||
Loading…
Reference in a new issue