Fix classification/ directory never matching where data files lived
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled

The code has always expected classification/m365_skus.json and
classification/google_ou_roles.json (present since the initial commit
in m365_connector.py, google_connector.py, and build_gdpr.py's own
skus_dir variable) - but the actual data files lived in skus/ under a
different name (education.json) or just the wrong directory. Both
loaders degrade to a fallback on failure, so this was never fatal, but
it meant:

- M365 SKU-based role classification silently ran on fragment-matching
  only, never touching the real 13 student + 11 staff SKU list.
- Google Workspace OU-based role classification silently used a
  hardcoded 4-prefix fallback missing real categories the actual
  config has - /Elev, /Pupils (student), /Teachers, /Admin (staff).
  _load_ou_roles() had a bare `except Exception: return <fallback>`
  with no logging at all, so this had zero visibility anywhere.

Fix: moved both files to classification/ (git mv, so history is
preserved), updated .gitignore's exception rule from !skus/*.json to
!classification/*.json, fixed the stale "skus/google_ou_roles.json"
docstring, and added a module logger + proper warning/error logging to
_load_ou_roles() matching the pattern _load_sku_data() already used -
so if this class of bug recurs, it won't be silent again.

Verified: both loaders now log successful loads with the real counts
(13+11 SKUs, 4+6 OU prefixes including the previously-missing
categories), all 227 tests still pass, and a full app boot confirms
the startup warning is gone and the app still serves normally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
StyxX65andClaude Sonnet 5 committed 2026-09-03 22:58:24 +02:00
1 parent 2260a451a5
commit edc820051e
4 files changed
+11 -3

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
*.json *.json
!lang/*.json !lang/*.json
!keywords/*.json !keywords/*.json
!skus/*.json !classification/*.json
!package*.json !package*.json
# Be explicit about the most sensitive files # Be explicit about the most sensitive files
File renamed without changes.
File renamed without changes.
+10 -2
View File
@@ -31,6 +31,8 @@ import threading
from pathlib import Path from pathlib import Path
from typing import Iterator, Optional from typing import Iterator, Optional
logger = logging.getLogger(__name__)
# ── google-auth / google-api-python-client ──────────────────────────────────── # ── google-auth / google-api-python-client ────────────────────────────────────
try: try:
from google.oauth2 import service_account from google.oauth2 import service_account
@@ -93,15 +95,21 @@ _MAX_EXPORT_BYTES = 20 * 1024 * 1024 # 20 MB
_OU_ROLES_PATH = Path(__file__).parent / "classification" / "google_ou_roles.json" _OU_ROLES_PATH = Path(__file__).parent / "classification" / "google_ou_roles.json"
def _load_ou_roles() -> tuple[list, list]: def _load_ou_roles() -> tuple[list, list]:
"""Load student/staff OU prefix lists from skus/google_ou_roles.json. """Load student/staff OU prefix lists from classification/google_ou_roles.json.
Returns (student_prefixes, staff_prefixes) — both lowercased.""" Returns (student_prefixes, staff_prefixes) — both lowercased."""
try: try:
import json as _j import json as _j
data = _j.loads(_OU_ROLES_PATH.read_text(encoding="utf-8")) data = _j.loads(_OU_ROLES_PATH.read_text(encoding="utf-8"))
students = [p.lower() for p in data.get("student_ou_prefixes", [])] students = [p.lower() for p in data.get("student_ou_prefixes", [])]
staff = [p.lower() for p in data.get("staff_ou_prefixes", [])] staff = [p.lower() for p in data.get("staff_ou_prefixes", [])]
logger.info("[ou-roles] Loaded %d student + %d staff OU prefixes from classification/google_ou_roles.json",
len(students), len(staff))
return students, staff return students, staff
except Exception: except FileNotFoundError:
logger.warning("[ou-roles] classification/google_ou_roles.json not found — using hardcoded fallback prefixes only")
return ["/elever", "/students"], ["/personale", "/staff", "/lærere", "/ansatte"]
except Exception as e:
logger.error("[ou-roles] Failed to load classification/google_ou_roles.json: %s — using hardcoded fallback prefixes only", e)
return ["/elever", "/students"], ["/personale", "/staff", "/lærere", "/ansatte"] return ["/elever", "/students"], ["/personale", "/staff", "/lærere", "/ansatte"]
def classify_ou_role(org_unit_path: str) -> str: def classify_ou_role(org_unit_path: str) -> str: