$this->withDefaults($descriptor), [ [ 'module_key' => 'motorapi', 'module_label' => 'MotorAPI', 'metric_key' => 'lookup_calls', 'metric_label' => 'License plate lookups', 'unit' => 'calls', 'period' => 'day', 'source' => 'internal_counter', 'default_enforce_mode' => 'block', 'config_module' => 'motorapi', 'config_variable' => 'daily_limit', 'config_type' => 'int', 'legacy_count_table' => 'motorapi_lookups', 'primary' => true, 'writable_limit' => true, ], [ 'module_key' => 'motorapi', 'module_label' => 'MotorAPI', 'metric_key' => 'provider_usage', 'metric_label' => 'Provider usage', 'unit' => 'calls', 'period' => 'provider', 'source' => 'provider_snapshot', ], [ 'module_key' => 'fxratesapi', 'module_label' => 'FXRatesAPI', 'metric_key' => 'rate_fetch_calls', 'metric_label' => 'Currency rate fetches', 'unit' => 'calls', 'period' => 'day', 'source' => 'internal_counter', 'default_enforce_mode' => 'block', 'config_module' => 'fxratesapi', 'config_variable' => 'daily_limit', 'config_type' => 'int', 'legacy_count_table' => 'fxratesapi_conversion_rates', 'primary' => true, 'writable_limit' => true, ], [ 'module_key' => 'virkdata', 'module_label' => 'VirkData', 'metric_key' => 'company_search_calls', 'metric_label' => 'Company searches', 'unit' => 'calls', 'period' => 'month', 'source' => 'internal_counter', 'default_enforce_mode' => 'observe', 'config_module' => 'virkdata', 'config_variable' => 'monthly_limit', 'config_type' => 'int', 'legacy_log_module' => 'VIRKDATA', 'legacy_log_action' => 'VIRKDATA_SEARCH', 'primary' => true, 'writable_limit' => true, ], [ 'module_key' => 'licenseplaterecognizer', 'module_label' => 'License Plate Recognizer', 'metric_key' => 'plate_recognition_calls', 'metric_label' => 'Plate recognition calls', 'unit' => 'calls', 'period' => 'provider', 'source' => 'provider_snapshot', 'default_enforce_mode' => 'observe', 'primary' => true, ], [ 'module_key' => 'email', 'module_label' => 'Email', 'metric_key' => 'mailersend_messages', 'metric_label' => 'MailerSend messages', 'unit' => 'messages', 'period' => 'provider', 'source' => 'provider_snapshot', ], ['module_key' => 'openai', 'module_label' => 'OpenAI', 'metric_key' => 'api_calls', 'metric_label' => 'API calls', 'unit' => 'calls', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'openai', 'module_label' => 'OpenAI', 'metric_key' => 'total_tokens', 'metric_label' => 'Total tokens', 'unit' => 'tokens', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'weatherapi', 'module_label' => 'WeatherAPI', 'metric_key' => 'requests', 'metric_label' => 'Weather requests', 'unit' => 'calls', 'period' => 'day', 'source' => 'internal_counter'], ['module_key' => 'gatewayapi', 'module_label' => 'GatewayAPI', 'metric_key' => 'sms_messages', 'metric_label' => 'SMS messages', 'unit' => 'messages', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'bird', 'module_label' => 'Bird', 'metric_key' => 'messages_and_calls', 'metric_label' => 'Messages and calls', 'unit' => 'events', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'ocrspace', 'module_label' => 'OCRSpace', 'metric_key' => 'ocr_requests', 'metric_label' => 'OCR requests', 'unit' => 'calls', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'limble', 'module_label' => 'Limble', 'metric_key' => 'api_requests', 'metric_label' => 'API requests', 'unit' => 'calls', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'workfeed', 'module_label' => 'Workfeed', 'metric_key' => 'api_requests', 'metric_label' => 'API requests', 'unit' => 'calls', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'stripe', 'module_label' => 'Stripe', 'metric_key' => 'payment_events', 'metric_label' => 'Payment events', 'unit' => 'events', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'coolify', 'module_label' => 'Coolify', 'metric_key' => 'operations', 'metric_label' => 'Operations', 'unit' => 'events', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'backups', 'module_label' => 'Backups', 'metric_key' => 'backup_jobs', 'metric_label' => 'Backup jobs', 'unit' => 'jobs', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'backups', 'module_label' => 'Backups', 'metric_key' => 'stored_bytes', 'metric_label' => 'Stored backup data', 'unit' => 'bytes', 'period' => 'all_time', 'source' => 'derived'], ['module_key' => 'selfserve', 'module_label' => 'Self Serve', 'metric_key' => 'wash_sessions', 'metric_label' => 'Wash sessions', 'unit' => 'sessions', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'selfserve', 'module_label' => 'Self Serve', 'metric_key' => 'lane_commands', 'metric_label' => 'Lane commands', 'unit' => 'events', 'period' => 'month', 'source' => 'internal_counter', 'legacy_log_module' => 'SELFSERVE'], ['module_key' => 'xlvask', 'module_label' => 'XLVask', 'metric_key' => 'usage_rows', 'metric_label' => 'Usage rows', 'unit' => 'rows', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'attachments', 'module_label' => 'Attachments', 'metric_key' => 'stored_files', 'metric_label' => 'Stored files', 'unit' => 'files', 'period' => 'all_time', 'source' => 'derived'], ['module_key' => 'dynamicimages', 'module_label' => 'Dynamic Images', 'metric_key' => 'renders', 'metric_label' => 'Image renders', 'unit' => 'renders', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'html2pdf', 'module_label' => 'HTML2PDF', 'metric_key' => 'pdf_jobs', 'metric_label' => 'PDF jobs', 'unit' => 'jobs', 'period' => 'month', 'source' => 'internal_counter'], ['module_key' => 'forms', 'module_label' => 'Forms', 'metric_key' => 'submissions', 'metric_label' => 'Submissions', 'unit' => 'submissions', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'notifications', 'module_label' => 'Notifications', 'metric_key' => 'notification_sends', 'metric_label' => 'Notification sends', 'unit' => 'notifications', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'shelly', 'module_label' => 'Shelly', 'metric_key' => 'relay_commands', 'metric_label' => 'Relay commands', 'unit' => 'commands', 'period' => 'month', 'source' => 'internal_counter', 'legacy_log_module' => 'SHELLY'], ['module_key' => 'edgegateway', 'module_label' => 'Edge Gateway', 'metric_key' => 'relay_commands', 'metric_label' => 'Relay commands', 'unit' => 'commands', 'period' => 'month', 'source' => 'derived'], ['module_key' => 'system', 'module_label' => 'System', 'metric_key' => 'cron_runs', 'metric_label' => 'Cron runs', 'unit' => 'runs', 'period' => 'day', 'source' => 'derived'], ] ); } public function find(string $moduleKey, string $metricKey): ?array { $moduleKey = $this->normalizeModuleKey($moduleKey); $metricKey = $this->normalizeMetricKey($metricKey); foreach ($this->all() as $descriptor) { if ($descriptor['module_key'] === $moduleKey && $descriptor['metric_key'] === $metricKey) { return $descriptor; } } return null; } public function forModule(string $moduleKey): array { $moduleKey = $this->normalizeModuleKey($moduleKey); return array_values(array_filter( $this->all(), static fn(array $descriptor): bool => $descriptor['module_key'] === $moduleKey )); } public function normalizeModuleKey(string $moduleKey): string { return strtolower(trim($moduleKey)); } public function normalizeMetricKey(string $metricKey): string { return strtolower(trim($metricKey)); } private function withDefaults(array $descriptor): array { $descriptor['module_key'] = $this->normalizeModuleKey((string)$descriptor['module_key']); $descriptor['metric_key'] = $this->normalizeMetricKey((string)$descriptor['metric_key']); $descriptor['module_label'] = (string)($descriptor['module_label'] ?? $descriptor['module_key']); $descriptor['metric_label'] = (string)($descriptor['metric_label'] ?? $descriptor['metric_key']); $descriptor['unit'] = (string)($descriptor['unit'] ?? 'count'); $descriptor['period'] = (string)($descriptor['period'] ?? 'all_time'); $descriptor['scope_type'] = (string)($descriptor['scope_type'] ?? 'global'); $descriptor['scope_id'] = (string)($descriptor['scope_id'] ?? ''); $descriptor['source'] = (string)($descriptor['source'] ?? 'internal_counter'); $descriptor['default_enforce_mode'] = (string)($descriptor['default_enforce_mode'] ?? 'observe'); $descriptor['soft_limit_percent'] = (float)($descriptor['soft_limit_percent'] ?? self::DEFAULT_SOFT_LIMIT_PERCENT); $descriptor['writable_limit'] = (bool)($descriptor['writable_limit'] ?? false); $descriptor['primary'] = (bool)($descriptor['primary'] ?? false); return $descriptor; } }