"""Administrator coverage across managed asset locations. Shared by the HTTP endpoint and its MCP twin. A location's administrator list is the ``asset_admin`` grant set for that region, so "who covers this place" and "where is nobody covering" are the same question asked two ways, and both surfaces have to answer them identically — a second implementation is a second set of numbers to reconcile. """ from __future__ import annotations from typing import Any from uuid import UUID from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.domains.asset.location_admin_sync import list_location_admin_ids_bulk from app.domains.asset.location_resolver import LocationIndex from app.domains.asset.models import AssetItem, AssetLocation from app.identity.person_models import IdentityPerson async def build_location_coverage(db: AsyncSession) -> dict[str, Any]: """Per-location administrator coverage, with what each one carries. The location settings page renders this. A location that holds assets or seats people but has no administrator is an operational gap — every write there falls through to the global tier, and the inventory / recycle flows have no one to address — so the numbers have to sit next to the control that fixes them rather than in a report somebody would have to go find. ``asset_count`` counts non-deleted assets whose ``location`` names this row. ``person_count`` counts active people whose HRIS work location resolves here, using the same resolver the region gate uses — so the number answers "whose assets would this administrator be responsible for", not "whose HR record happens to spell it this way". """ locations = list( ( await db.execute( select(AssetLocation).order_by( AssetLocation.sort_order, AssetLocation.name, ) ) ).scalars().all() ) admins_by_location = await list_location_admin_ids_bulk( db, [loc.id for loc in locations], ) asset_counts: dict[str, int] = {} for name, count in ( await db.execute( select(AssetItem.location, func.count(AssetItem.id)) .where(AssetItem.deleted_at.is_(None)) .group_by(AssetItem.location) ) ).all(): if name: asset_counts[name] = count # People are counted through the resolver rather than by a GROUP BY # on the raw HR string: ``Santa Clara`` and ``San Jose`` are both # 美国, and a per-string count would report zero for the location an # administrator actually covers. index = await LocationIndex.load(db) person_counts: dict[UUID, int] = {} for work_location, city, count in ( await db.execute( select( IdentityPerson.work_location_name, IdentityPerson.city, func.count(IdentityPerson.id), ) .where( IdentityPerson.deleted_at.is_(None), IdentityPerson.employment_status == "active", ) .group_by(IdentityPerson.work_location_name, IdentityPerson.city) ) ).all(): location_id = index.resolve_first_id(work_location, city) if location_id is not None: person_counts[location_id] = person_counts.get(location_id, 0) + count items = [] for loc in locations: admins = admins_by_location.get(loc.id, []) asset_count = asset_counts.get(loc.name, 0) person_count = person_counts.get(loc.id, 0) items.append({ "location_id": loc.id, "code": loc.code, "name": loc.name, "sort_order": loc.sort_order, "is_active": loc.is_active, "admins": admins, "asset_count": asset_count, "person_count": person_count, # "Uncovered" means it matters and nobody has it. A retired # location with nothing left at it is not a gap, and # flagging it would train operators to ignore the flag. "is_uncovered": bool( not admins and loc.is_active and (asset_count > 0 or person_count > 0) ), }) return { "items": items, "summary": { "total": len(items), "covered": sum(1 for i in items if i["admins"]), "uncovered": sum(1 for i in items if i["is_uncovered"]), "assets_uncovered": sum( i["asset_count"] for i in items if i["is_uncovered"] ), "people_uncovered": sum( i["person_count"] for i in items if i["is_uncovered"] ), }, }