azure-cost-calculator
Azure Cost Calculator
Deterministic Azure cost estimation using the public Retail Prices API. Never guess prices; always query the live API via the scripts.
Runtime Detection
Choose the script runtime based on what is available:
| Runtime | Condition | Pricing script | Explore script |
|---|---|---|---|
| Bash (preferred) | curl and jq available |
scripts/get-azure-pricing.sh |
scripts/explore-azure-pricing.sh |
| PowerShell 7+ | pwsh available |
scripts/Get-AzurePricing.ps1 |
scripts/Explore-AzurePricing.ps1 |
| Windows PowerShell 5.1 | powershell.exe available (Windows only). Add -ExecutionPolicy RemoteSigned before -File to avoid silent failures from default policy restrictions. |
scripts/Get-AzurePricing.ps1 |
scripts/Explore-AzurePricing.ps1 |
Both produce identical JSON output. Bash flags use --kebab-case equivalents of PowerShell -PascalCase parameters (e.g., -ServiceName → --service-name).
Declarative Parameters
Service reference files specify query parameters as Key: Value pairs. Translate to Bash --kebab-case or PowerShell -PascalCase flags; quote string values with spaces. See workflow.md for the full parameter table, translation examples, and output formats.
Workflow
Phase 1: Analysis (no API queries)
-
Parse: extract resource types, quantities, and sizing from user's architecture
-
Clarify: if any of these are true, stop and ask before continuing:
- A resource maps to a category but not a specific service (e.g., "a database") → list 2–4 options
- A resource has no count, no sizing/tier, or no workload scale → ask for specifics
- A resource has no expected monthly volume: data transferred/ingested (GB), transactions, requests, messages, tokens, or users/devices → ask for estimated volume
- A multi-model or multi-feature service (e.g., Azure OpenAI, AI Services, Defender for Cloud) has no model or feature variant specified → ask which one (cost can vary 15–30×)
- User describes a goal without a hosting model (e.g., "a web app") → present 2–3 options with trade-offs
- Batch all gaps into one prompt. Offer concrete choices with sensible options (e.g., "100 GB/month?", "GPT-4o or GPT-4o-mini?"). One round max; if user declines a specific parameter, apply safe defaults only for Safe-default gaps and disclose them; if any Never-assume gap remains, do NOT proceed; state what cannot be estimated without the missing input.
-
Locate each service reference using the lookup workflow in shared.md (file search → routing map → category browse → broad search → discovery)
-
Read matched service files; check
billingNeedsand follow dependency chains (e.g., AKS → VMs → Managed Disks) -
Classify each parameter using the Disambiguation Protocol in shared.md:
- Specified: user provided value (use verbatim)
- Never-assume gap: required parameter missing (must ask)
- Safe-default gap: optional parameter missing (use default, disclose)
-
Specification Review: present a summary:
Service Specified Missing (will ask) Defaults (will assume) - If any never-assume parameter is missing → ask user before proceeding
- If only safe-default gaps remain → disclose defaults and proceed to Phase 2
- Single-service shortcut: skip this table for single-service estimates where all parameters are specified
Phase 2: Estimation
- Query: run the pricing script for each service using parameters from service files + user input + resolved defaults
- Calculate: apply cost formulas from service files; multiply by quantities
- Verify arithmetic: for each line item, restate the formula with actual numbers, compute, and confirm the result. If any intermediate calculation involves multiplication of two numbers > 10, compute it step-by-step (e.g.,
14.5 × 640 → 14 × 640 → 10 × 640 = 6,400; 4 × 640 = 2,560; subtotal = 8,960; 0.5 × 640 = 320; total = 9,280). Do not rely on mental math for multi-digit operations. - Present: output the estimate with:
- Assumptions block (see Disambiguation Protocol in shared.md), listed before cost numbers
- Line items: service, unit price, quantity/hours, monthly cost
- Grand total: re-sum all line-item monthly costs independently; if discrepancy, use re-summed value
Post-Estimate Iteration
After presenting the estimate, the user may request changes (switch region, add RI, resize instances, add/remove services). Re-run only the affected queries; do not restart the full workflow.
Reference Index (load on demand)
| Condition | Read |
|---|---|
| Always (entry point) | references/shared.md: constants, category index, alias lookup |
| Query returned 0 results or wrong data | references/pitfalls.md: troubleshooting and traps |
| User asks about Reserved Instances or savings plans | references/reserved-instances.md |
| Non-USD currency or non-eastus region | references/regions-and-currencies.md |
| User requests private endpoints or private access; confirm PE intent with user | references/services/networking/private-link.md: PE pricing, references/services/networking/private-dns.md: DNS zone pricing |
| File search returned 0 or ambiguous results | references/service-routing.md - implemented services routing |
| First time running scripts or unfamiliar with parameters | references/workflow.md: script parameters and output formats |
Critical Rules
- Never guess prices: always run the script against the live API
- Infer currency and region from user context: if unspecified, ask the user or default to USD and eastus
- Ask before assuming: if a required parameter is ambiguous or missing, stop, clarify and ask the user. Never silently default a never-assume parameter. At the request level, use the Clarify checks in Step 2 (for example monthly volume, data transfer, and model/feature variant). At the parameter level, use the authoritative Disambiguation Protocol table in shared.md.
- Default output format is Json: never use Summary (invisible to agents)
- Lazy-load service references: only read files from
references/services/directly required by the user's query. Use the file-search workflow (Step 2) to locate specific files. - PowerShell: use
-File, not-Command: run scripts withpwsh -Fileorpowershell.exe -File; on Linux/macOS, bash strips OData quotes from inline commands. PS 5.1 caveats: (a) Always add-ExecutionPolicy RemoteSignedbefore-Filewhen usingpowershell.exe; default Windows policies silently block script execution (see Runtime Detection note above). (b) Use-Commandinstead of-Filewhen passing array parameters (e.g.,-Region 'eastus','australiaeast'), because-Filemode does not parse PowerShell expression syntax and collapses the array into a single string. - Use exact category names: group line items using the exact Category Index names from shared.md verbatim (e.g., "Compute", "Databases", "AI + ML"). Do not paraphrase, abbreviate, or rename them.
- Scope to user-specified resources: only include resources explicitly stated in the user's architecture. Companion resources from
billingNeedsare included automatically. - MeterId: when the user requests meter IDs, add
--include-meter-id/-IncludeMeterId. No extra API calls needed.
Service File Metadata
YAML front matter fields. Optional fields use default elision; omitted means the default applies.
| Field | Required | Default | Action |
|---|---|---|---|
billingNeeds |
- | omit | Read and price listed dependency services |
billingConsiderations |
- | omit | Ask user about listed pricing factors before calculating |
primaryCost |
✔ | - | One-line billing summary for quick cost context |
apiServiceName |
- | omit | Use instead of serviceName in API queries |
hasMeters |
- | true |
false → skip API, use Known Rates table |
pricingRegion |
- | regional |
global → Region: Global; api-unavailable → skip API; empty-region → omit region |
hasKnownRates |
- | false |
true → file contains manual pricing table |
hasFreeGrant |
- | false |
true → apply free grant deduction from Cost Formula |
privateEndpoint |
- | false |
true → aggregate PE costs via networking/private-link.md |
Universal Traps
These apply to EVERY query:
serviceNameand all filter values are case-sensitive: use exact values from service reference files- Unfiltered queries return mixed SKU variants: always filter with
productName/skuNameto the specific variant needed - Multi-meter resources need separate queries: run one query per meter with
-MeterName
Batch Estimation Mode
When estimating 3 or more services, use these rules to reduce token consumption:
- Partial reads: read only lines 1–45 of each service file (YAML front matter, trap, first query pattern).
- Front matter routing: use YAML metadata to skip unnecessary work:
hasMeters: false/pricingRegion: api-unavailable→ skip API; use Known Rates orprimaryCostpricingRegion: global→Region: Global;empty-region→ omit regionapiServiceName→ use instead ofserviceNamein querieshasFreeGrant: true→ apply grant deduction;privateEndpoint: true→ add PE line item
- Full read triggers: no query pattern in partial read, non-default config, 0/unexpected results, or
billingConsiderationsapplies. - Parallel queries: run independent service queries in parallel, but limit to 3–5 concurrent requests to avoid API rate limiting. If querying more than 5 services, stagger starts in batches.
- Skip redundant references: read shared.md and pitfalls.md once at the start, not between services.
- Progressive distillation: after each service query returns, emit a summary row before proceeding:
| Category | Service | Resource | Unit Price | Unit | Qty | Monthly Cost | Notes |Multi-meter services get one row per line item. After all queries complete, assemble the final estimate from the accumulated rows. Do not re-read service files already distilled unless a full read trigger is needed. During Post-Estimate Iteration, replace the distillation row(s) for any re-queried service. - Compact output: use
OutputFormat: Compact(or--output-format Compactin Bash) for batch queries. Compact returns only the 9 fields needed for cost calculation (MeterName, ProductName, SkuName, UnitPrice, UnitOfMeasure, MonthlyCost, Currency, ReservationTerm, TierMinUnits); no query echo, no summary block. With--include-meter-id, Compact includes MeterId as a 10th field. Use fullJsonformat when debugging unexpected results or when a service file requires fields not in the Compact set.