← Back to Index
↳ Project /04Azure · FinOps

Azure FinOps Dashboard

Cost visibility that answers what a bill cannot: which resource changed behavior, who owns the spend, where Advisor says it is wasted, and whether tags are governed at the control plane. Statistical anomaly detection, cost allocation by tag, Azure Advisor optimization, Azure Policy tag governance, and FOCUS normalization so Azure and AWS spend join on one schema, over the Cost Management API, written in C# and running credential-free.

Role
Cloud / FinOps
Cloud
Azure
Language
C# .NET 8
Governance
Azure Policy
Timer Triggers
06:00 · 06:30 · 07:00 UTC
Cost Mgmt API
7-day actuals by resource
Ingest Fn
C# .NET 8 · upsert
Anomaly Fn
30d rolling · 2σ
Forecast Fn
14d linear trend
Cosmos DB
RBAC only · key auth off
HTTP API
5 endpoints
React SPA
Static Web Apps
Daily ingest, then statistics: sigma-tiered anomalies and a 14-day projection, all under one managed identity

/01Problem

A cloud bill tells you what you spent after you have already spent it, aggregated to a level where nothing is actionable. By the time a runaway resource shows up as a line item, it has been running for most of a billing cycle, and the person who could have caught it has no signal that anything changed.

The three questions worth answering are earlier and more specific: which resources are behaving differently than they did last month, where is spend heading if nothing changes, and which resources cannot even be attributed to an owner because nobody tagged them.

/02Approach

  • A timer-triggered Function ingests the previous seven days of actual cost from the Cost Management REST API each morning, grouped by resource, resource type, and resource group, upserting into Cosmos DB keyed by resource ID so re-runs are idempotent.
  • Anomaly detection runs half an hour later against the stored history: a rolling 30-day mean and standard deviation per resource, flagging any resource whose latest daily cost exceeds two sigma, tiered Low at 2.0, Medium at 2.5, and High at 3.0.
  • Forecasting runs after that, projecting 14 days on a linear trend over the trailing 30-day window with confidence intervals derived from historical variance, and refuses to project at all on fewer than seven days of data rather than emitting a number nobody should trust.
  • A tag hygiene pass evaluates every subscription resource against a required-tag policy and reports both a compliance percentage and the specific resources missing specific tags, because a percentage alone is not something anyone can act on.
  • Cost allocation runs as a live query grouped by a cost allocation tag, so the dashboard answers what each project or owner costs, not just what each resource costs, and surfaces untagged spend as its own bucket. Optimization reads Azure Advisor cost recommendations, idle resources, right-sizing, and reservation purchases, each with an estimated monthly saving.
  • A /api/focus endpoint, backed by a pure, unit-testable C# FocusMapper, maps Azure cost records to the FOCUS open cost-and-usage schema (ProviderName, ServiceCategory, BilledCost, EffectiveCost, plus ResourceId, SubAccountId, and Tags). The AWS sibling emits the same core columns, so the two clouds join on one schema for cross-provider chargeback.

/03Governance: audit, not deny

The reporting layer flags untagged resources after the fact. The governance layer governs them at the control plane: a custom Azure Policy definition audits every taggable resource for the cost_center chargeback tag, assigned at resource-group scope. The effect is a deliberate design choice. The sample workload includes a purposely untagged storage account so the hygiene view has something to find; Deny would have blocked it at apply time and Modify would have auto-tagged it and erased the demo, so Audit flags non-compliance while leaving the resource exactly as it is.

Showing both layers, and knowing why they differ, is the point: the Function scanner reports, Azure Policy governs continuously.

/04Architecture and credential-free posture

Eight HTTP-triggered Functions expose the results as a REST API to a React single-page app on Static Web Apps. The read path never touches the Cost Management API for stored data, and cost-by-tag, Advisor, and FOCUS queries run live on demand. The whole thing runs without a stored credential: a system-assigned managed identity holds Cost Management Reader and Reader at subscription scope (covering Advisor too) and Cosmos DB Built-in Data Contributor at database scope. Key-based Cosmos access is disabled at the account level, so even a leaked connection string would be inert.

The AWS counterpart, the Cost Intelligence Dashboard, applies the same allocation, waste, and forecasting approach to Cost Explorer and emits the identical FOCUS schema at its own /focus endpoint; running it across both clouds on one schema is what makes the practice portable. The governance layer here deployed and was demoed live, the policy assignment created, resources carrying the new cost_center and team tags, the untagged resource flagged. The Consumption Function App could not deploy in this subscription because its App Service VM quota is zero, a subscription-wide limit that needs an Azure support quota increase; the FOCUS mapper and the two Function-hosted features build clean (dotnet build) and are proven end to end on the AWS side.

↳ Run Receipt/04
GovernanceAzure Policy Audit for cost_center deployed and demoed, untagged resource flagged
AllocationCost-by-tag and Advisor optimization endpoints, credential-free via managed identity
FOCUS/api/focus emits the same open cost-and-usage schema as the AWS sibling; pure C# mapper, dotnet build clean
AuthManaged identity throughout, key-based Cosmos access disabled
ConstraintFunction App blocked by a zero App Service VM quota, code proven on the AWS sibling
PostureKeyless, governed at the control plane
Azure FunctionsC# .NET 8Cosmos DBCost Management APIAzure AdvisorAzure PolicyFOCUSStatic Web AppsReactTerraform
StatusCheckingDeployed2026-09-20 13:33 UTCVisitsOriginPrivate S3 + CloudFront OACPipelineGitHub Actions OIDCCounterAPI Gateway + Lambda + DynamoDB