"""Provision-policy match semantics — single source of truth. ``ProvisionPolicy`` rows match a person on three optional dimensions (``department_name`` × ``match_job_title`` × ``match_location``); a NULL rule column is a wildcard. Two domains need the same semantics: * ``provisioning`` — onboarding task creation + role-change diff * ``onboarding`` — the Day-1 readiness scanner, which must answer "which seat plans do active policies promise this person" without importing the provisioning service (domain modules don't import each other; shared logic lives here in core). Keep this in sync with nothing — this IS the canonical matcher. Both callers build their WHERE clause through :func:`policy_match_conditions`. """ from __future__ import annotations from typing import Any def policy_matches_values( *, rule_department: str | None, rule_job_title: str | None, rule_location: str | None, department: str | None, job_title: str | None, location: str | None, strict_null_on_missing: bool = False, ) -> bool: """Apply the canonical matcher to already-loaded policy/person values.""" for rule, value in ( (rule_department, department), (rule_job_title, job_title), (rule_location, location), ): if value is None: if strict_null_on_missing and rule is not None: return False elif rule is not None and rule != value: return False return True def policy_match_conditions( policy_model: Any, *, department: str | None, job_title: str | None, location: str | None, strict_null_on_missing: bool = False, ) -> list[Any]: """Build SQLAlchemy filter conditions for policy matching. ``policy_model`` is the ``ProvisionPolicy`` mapped class (passed in so this module stays import-light and free of domain imports). For each dimension: * person HAS a value → rule column IS NULL OR equals it * person LACKS the value → strict mode matches only wildcard rules; lenient mode skips the dimension entirely (a ``NULL == NULL`` comparison is 3-valued in SQL and would silently filter everything out). """ conditions: list[Any] = [policy_model.is_active.is_(True)] def _dim_filter(column: Any, value: str | None) -> Any | None: if value is not None: return (column.is_(None)) | (column == value) return column.is_(None) if strict_null_on_missing else None for f in ( _dim_filter(policy_model.department_name, department), _dim_filter(policy_model.match_job_title, job_title), _dim_filter(policy_model.match_location, location), ): if f is not None: conditions.append(f) return conditions