// @title Preflight — can this workspace answer the question? // @summary Confirms both APIM tables exist and carry usable data in the selected window. Run this first: a missing diagnostic category is a configuration fact, not a broken query. // @posture observe // @posture-note Reports what the workspace contains. Establishes nothing about the estate. // // PREFLIGHT — run this first. It answers one question: can the Economic // Control Check produce evidence in this workspace, over this window? // // Read-only. Runs in your Log Analytics workspace; sends nothing anywhere. // // WHY THIS QUERY EXISTS. The two tables the check reads are created by APIM // diagnostic settings, and the LLM table is a SEPARATE category that is off // by default. A workspace that has never received that category does not // contain an empty table — it contains no table, and every later query // fails to resolve rather than returning zero rows. That failure looks like // a broken kit. It is a configuration fact, and it is knowable up front. // // `union isfuzzy=true` is what makes the question askable: it tolerates a // table reference that cannot be resolved instead of failing the query. The // empty typed datatable is not decoration — it pins the column schema so // the join below still resolves when BOTH tables are absent, which is // exactly the case this query exists to report. let _startTime = ago(30d); let _endTime = now(); let expected = datatable(Table:string, Provides:string) [ 'ApiManagementGatewayLogs', 'Request identity, operation, status, latency', 'ApiManagementGatewayLlmLog', 'Model, deployment, token counts' ]; let observed = union isfuzzy=true (datatable(Table:string, Records:long, Requests:long, TokenBearing:long, Earliest:datetime, Latest:datetime)[]), (ApiManagementGatewayLogs | where TimeGenerated between (_startTime .. _endTime) | summarize Records = count(), Requests = dcount(CorrelationId), TokenBearing = long(null), Earliest = min(TimeGenerated), Latest = max(TimeGenerated) | extend Table = 'ApiManagementGatewayLogs' | project Table, Records, Requests, TokenBearing, Earliest, Latest), (ApiManagementGatewayLlmLog | where TimeGenerated between (_startTime .. _endTime) // TokenBearing counts requests that actually reported usage. // A present table with no usage data is a third state, and it // is the one that silently produces empty economics later. | summarize Records = count(), Requests = dcount(CorrelationId), TokenBearing = dcountif(CorrelationId, isnotnull(TotalTokens) and TotalTokens > 0), Earliest = min(TimeGenerated), Latest = max(TimeGenerated) | extend Table = 'ApiManagementGatewayLlmLog' | project Table, Records, Requests, TokenBearing, Earliest, Latest); expected | join kind=leftouter (observed) on Table | extend Status = case( isnull(Records), 'NOT PRESENT — this table does not exist in this workspace. Enable the matching APIM diagnostic setting category.', Records == 0, 'PRESENT BUT EMPTY — no records in the selected window. Widen the window, or confirm traffic reached this gateway.', Table == 'ApiManagementGatewayLlmLog' and TokenBearing == 0, 'PRESENT WITHOUT TOKEN DATA — records exist but none report usage. Economics cannot be established from this.', 'READY'), Records = coalesce(Records, long(0)), Requests = coalesce(Requests, long(0)) | project Table, Provides, Status, Records, Requests, TokenBearing, Earliest, Latest | order by Table asc