Skip to content

OP-886 Anonymize a patient's personal data (GDPR right to erasure) - #1641

Open
giuseppesorge wants to merge 8 commits into
informatici:developfrom
giuseppesorge:OP-886-gdpr-oblivion
Open

OP-886 Anonymize a patient's personal data (GDPR right to erasure)#1641
giuseppesorge wants to merge 8 commits into
informatici:developfrom
giuseppesorge:OP-886-gdpr-oblivion

Conversation

@giuseppesorge

@giuseppesorge giuseppesorge commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Implements the CORE part of OP-886 – GDPR 1/4 Oblivion (Art. 17, right to erasure): irreversibly anonymize a patient's personal identification data while keeping the clinical records for statistical use.

Changes

  • Patient: new PAT_ANONYMIZED flag (+ getter/setter).
  • PatientIoOperations.anonymizePatient(Patient): overwrites the identifying fields with sentinels (first/second/mother/father name → ANONYMIZED, address/city/telephone → empty, tax code → DELETED), always detaches the profile photo (and deletes the filesystem copy when applicable), sets the flag and saves.
  • PatientBrowserManager.anonymizePatient(int): not-found / already-anonymized guards, best-effort removal of every linked DICOM series, then delegates the erasure.
  • Migration sql/step_aXXX_patient_anonymized.sql: adds PAT_ANONYMIZED, registers the patient.anonymize permission (REST API) + the btnpatientanonymize menu grant (GUI), both granted to admin.
  • Tests: testMgrAnonymizePatient, testMgrAnonymizePatientAlreadyAnonymizedThrows (full patient suite green).

Notes

  • Who/when is covered by the existing Auditable columns, no extra audit table.
  • SQL step name is a step_aXXX placeholder — to be renumbered at merge.

Related PRs (merge order: core first)

Verification

Migration applied and the manager exercised on a MariaDB 10.6 demo DB; anonymization overwrites the PII, keeps birth date/sex and all clinical records, and re-anonymizing is rejected.

Add a PAT_ANONYMIZED flag to OH_PATIENT and PatientBrowserManager.anonymizePatient(),
which irreversibly overwrites the personal identification data (name, surname, mother/
father name, address, city, telephone, tax code) with sentinel values, removes the
profile photo and every linked DICOM series, and marks the record as anonymized. The
clinical records are preserved for statistical use. Re-anonymizing a patient is rejected.
DICOM removal is best-effort so a missing DICOM configuration does not block the erasure.
Register the patient.anonymize permission (used by the REST API and the GUI to gate the
anonymization) and grant it to the admin group. P_ID_A/GP_ID are auto-increment, so the
inserts assign no explicit id and link the grant with a sub-select — avoiding id clashes
with other migrations. Verified on a demo DB.
The GUI gates the Anonymize button via MainMenu.checkUserGrants (which reads the user's menu
grants, not OH_PERMISSIONS), so register btnpatientanonymize as a menu item under the
Admission/Patient menu and grant it to the admin group. Verified on a demo DB.
The photo was only nulled when PATIENTPHOTOSTORAGE is DB; with filesystem storage the DB
relation was left in place, which left a dangling profile-photo reference on the record and
broke later mapping (LazyInitializationException when the anonymized patient's admissions were
loaded via the API). Now always null the OneToOne relation (GDPR erasure) and additionally
delete the file when photos are on the filesystem. Also broaden the best-effort DICOM cleanup
to swallow any failure (the DICOM module is unavailable outside the GUI context).
Keep the birth year (needed for age statistics) but drop the exact day and month, which would
otherwise remain a re-identifying detail.
…vion

# Conflicts:
#	sql/step_04_all_following_steps.sql
Add PAT_ANONYMIZED (default 0) to the oh_patient demo table and add the anonymize permission and menu
entry (oh_permissions, oh_grouppermission, oh_menuitem, oh_groupmenu), regenerated via a MariaDB
round-trip of the migration, so a clean demo DB matches the migrated schema.
@giuseppesorge

Copy link
Copy Markdown
Contributor Author

@dbmalkovsky — good call, thanks. Synced load_demo_data.sql for this change (02184139): added PAT_ANONYMIZED (default 0) to oh_patient plus the anonymize permission and menu rows. I regenerated it via a MariaDB round-trip of the migration (load the current demo dump → run the migration → mysqldump the changed tables) and verified it loads into a clean DB. I'll keep the demo dump in sync going forward.

1 similar comment
@giuseppesorge

Copy link
Copy Markdown
Contributor Author

@dbmalkovsky — good call, thanks. Synced load_demo_data.sql for this change (02184139): added PAT_ANONYMIZED (default 0) to oh_patient plus the anonymize permission and menu rows. I regenerated it via a MariaDB round-trip of the migration (load the current demo dump → run the migration → mysqldump the changed tables) and verified it loads into a clean DB. I'll keep the demo dump in sync going forward.

…vion

# Conflicts:
#	sql/load_demo_data.sql
#	sql/step_04_all_following_steps.sql
@giuseppesorge

Copy link
Copy Markdown
Contributor Author

Merged develop (26edc30). Besides step_04, sql/load_demo_data.sql conflicted as well: the dump is generated, and both sides had touched the same rows. Here there were four hunks: the AUTO_INCREMENT of oh_groupmenu (351 kept, since the merged table holds both new rows), the two oh_groupmenu rows themselves (349 and 350, both kept), the new oh_patient columns and the patient rows, which end up ordered PAT_DELETED_BY, PAT_DELETED_DATE, PAT_ANONYMIZED.

It is resolved with a three-way merge on each row's list of values (merge base / this branch / develop), so that both sets of columns survive, with develop's columns placed before this branch's — its migrations are already sourced from step_04 while ours is appended after it, which is the order a real database ends up with.

Two checks afterwards: every INSERT in the file carries exactly as many values as its own CREATE TABLE declares (across all 64 tables), and this branch's delta against develop is the same as it was before the merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant