Cards from scheduled scans of editor-saved profiles showed the account's
GUID as its name and no Elev/Ansat badge: the profile editor stored bare
id strings, the scheduler passes them straight to run_scan, and run_scan
normalised them to displayName=<id>, userRole="other". Before 1.7.10 the
scan-start profile overwrite kept repairing profiles to named objects,
which is why this surfaced now.
- run_scan: _enrich_bare_users() looks bare accounts up in the directory
(list_users, falling back to lookup_user) for name and role.
- M365Connector.build_role_sku_map(): the SKU map /api/users built inline,
now shared so cards and the account list classify roles identically.
lookup_user() also returns assignedLicenses.
- Profile editor saves {id, displayName, userRole} (manual accounts keep
email/manual), like the 💾 save.
- _accountPill: an account_name equal to account_id counts as missing;
name and role are filled from the loaded user list, so existing cards
display correctly without a rescan.
- Excel export: the scan's account-name map no longer breaks on bare id
strings (.get() on a str raised and dropped the whole map).
Tests: tests/test_account_names.py (10); the run_scan test fails on the
old engine with "Collecting emails — guid-henrik…".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_applyPendingProfileUsers() set u.selected = matches(u), where matches()
was `wanted.has(u.id) || (u.googleEmail && ...)`. For users without a
googleEmail (every M365-only and Google-only user) that evaluates to
undefined, not false — and both renderAccountList() and getSelectedUsers()
treat anything other than false as selected. So applying a profile ticked,
and would have scanned, all of those users in addition to the profile's own.
Coerce the googleEmail branch to a boolean.
Reproduced against a copy of real data: a 1-account profile ticked 56
(37 M365-only + 18 GWS-only extras); after the fix it ticks 1, and every
profile ticks only its own stored accounts.
Regression from bbe9c62 (released in 1.7.10).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Manual accounts were never scanned:
- Sidebar "add user" entries had no platform, and getSelectedUsers()
required platform === 'm365', so they were dropped from every scan.
- Profile-editor manual accounts were stored as "manual:<email>", which
is not a Graph user id, and applying the profile never put them in the
sidebar list, so they could not be selected.
Now the editor looks the email up via /api/users/lookup and stores the
directory id plus name/email (falling back to manual:<email> only when the
lookup fails). Applying a profile adds its manual accounts to the sidebar
and resolves legacy manual:<email> entries, logging any that can't be
found. The scan engine resolves remaining manual:<email> ids (e.g. from
scheduled scans) via the new M365Connector.lookup_user(), skipping and
reporting failures without widening to all users.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Starting an M365 scan wrote the sidebar state back into the active profile
via _save_settings(), replacing its user_ids with the sidebar selection
(minus Google-only users and anything hidden by the role filter). With no
profile active it overwrote profiles[0]. Profiles are loaders, so scan
start no longer writes them; profile_id is audit-only.
The profile editor built its account list from S._allUsers only, so
manually added accounts, users not yet loaded, and users gone from the
directory were not rendered and got dropped on the next save. They now
render as checked "Manual" / "Not loaded" rows, and user_ids "all" is kept
when saved before users load.
Applying a profile cleared the pending user IDs before the un-awaited
Google merge had added google:<email> users, leaving them unticked.
Pending IDs are now kept until all are matched or the user list is final,
and _mergeGoogleUsers() re-applies them. A saved google:<email> also
matches an M365 user merged to platform 'both'.
Adds regression tests for scan start leaving profiles untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operators can now choose Tesseract language pack(s) per profile via a
sidebar select (#optOcrLang) and profile editor (#peOptOcrLang). Presets:
dan+eng (default), dan, eng, dan+eng+deu, dan+eng+swe, dan+eng+fra. The
ocr_lang option flows from the UI through all three scan engines (M365
files/attachments, Google Drive, Gmail) down to document_scanner.scan_pdf
and scan_image — including the spawned PDF-OCR subprocess worker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New profile options to reduce noise when scanning student accounts:
- skip_gps_images: images flagged solely by GPS coordinates are suppressed.
GPS data is still extracted and shown in the detail card when the item
is flagged by another signal (faces, EXIF author/comment).
- min_cpr_count (default 1): only flag a file if it contains at least N
distinct CPR numbers. Deduplication is by value. Faces and EXIF PII
still trigger flags regardless of CPR count.
Both options apply to M365, Google, and file scan paths. Saved in profiles
and editable in the Profile Manager editor. Docs, manuals, i18n (DA/EN/DE),
CHANGELOG, and VERSION (1.6.14 → 1.6.15) updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>