"""Public API namespace exposed to plugins. Plugin authors at sister sites import from this module. The contract is locked in ADR-001 and versioned per ADR-002. Helpers added here become part of the platform contract; bumps follow ADR-002 rules. Currently exposed: - audit_log: record an audit log entry with consistent schema - resolve_asset_position: compute an asset's resolved map position Setting helpers are exposed via BasePlugin instance methods (plugin.get_setting, plugin.set_setting), not from this namespace. """ from typing import Any, Dict, Optional from shopdb.core.models import AuditLog def audit_log( action: str, entitytype: str, entityid: int = None, entityname: str = None, changes: Dict = None, details: Dict = None, ) -> AuditLog: """Record an audit log entry with the framework's standard schema. Plugins call this to record state changes on their own assets in a way that is consistent with core auditing. The function delegates to AuditLog.log() which already captures the current user, IP address, and user agent from the Flask request context. Args: action: Action verb in past tense ('created', 'updated', 'deleted') entitytype: Class name of the entity affected ('Computer', 'Printer') entityid: Primary key of the entity entityname: Human-readable identifier (hostname, asset number) changes: Dict with 'before' and 'after' snapshots for updates details: Arbitrary additional context Returns: The created AuditLog instance, already committed to the DB. """ return AuditLog.log( action=action, entitytype=entitytype, entityid=entityid, entityname=entityname, changes=changes, details=details, ) def resolve_asset_position(asset) -> Optional[Dict[str, Any]]: """Compute the resolved map position for an asset. Per ADR-001, position resolution follows this priority: 1. Asset-specific override (asset.mapx, asset.mapy) 2. Walk relationships where inheritsposition is true (partof, then controls) 3. Asset's location coords (asset.location.mapx, .mapy) 4. None (asset is rendered in an unplaced tray) Returns a dict {'mapx', 'mapy', 'positionsource'} or None if no position can be resolved. Note: Asset.mapx/mapy and AssetRelationship.inheritsposition columns are part of the locked ADR-001 contract surface but have not yet been added to the models. Until they are, this helper falls back to the location-only path. The full algorithm activates automatically once those columns exist. """ if hasattr(asset, 'mapx') and hasattr(asset, 'mapy'): if asset.mapx is not None and asset.mapy is not None: return { 'mapx': asset.mapx, 'mapy': asset.mapy, 'positionsource': 'self', } location = getattr(asset, 'location', None) if location is not None: location_mapx = getattr(location, 'mapx', None) location_mapy = getattr(location, 'mapy', None) if location_mapx is not None and location_mapy is not None: return { 'mapx': location_mapx, 'mapy': location_mapy, 'positionsource': 'location', } return None __all__ = ['audit_log', 'resolve_asset_position']