# Azure VM Non-Production Scheduling

> Positively non-production Azure VMs earn a recurring off-hours start/stop schedule when a 30-day weekly heatmap measures at least 20% idle time. Savings equal cost times the observed idle fraction; neutral-named untagged VMs, reserved-instance-covered VMs, and workloads with critical, stateful, or spot lifecycles are excluded, and missing heatmap data abstains.

Source: https://zop.dev/integrations/azure/recommendations/azure-vm-non-production-scheduling
Updated: 2026-08-19

---

## Requiring a positive dev/test signal to fire

- resource.Status: statusSucceeded
- Non-prod gate (audit 2026-06-26 pattern A, fixed): fires only on a POSITIVE non-production signal via isNonProdResource: a dev/test env tag, a dev/test resource name, or a dev/test resource-group name, with a prod env tag as an absolute veto. A neutral-named, untagged VM ("app-server-07") now abstains instead of being assumed non-production (the prior if isProdResource(...) \{ return nil } gate fired on the absence of a prod signal, not on a positive non-prod one). Matches ri_to_spot.go / sql_idle_vm.go.
- Critical-role + stateful-workload vetoes; interruptible (Metadata["lifecycle"]=spot/low_priority) veto; Databricks / VMSS-member skip
- ReservationCoverage: RI/SP-covered VMs skipped (stopping a reserved VM does not save; commitment continues as unused-reservation waste)
- MetricsAware (primary) and/or ScheduleAware (secondary, aggregator-precomputed ScheduleData)

## Idle slots in the observed weekly heatmap

savings = cost × idlePct, where idlePct is the observed idle fraction from the 30-day weekly heatmap (buildWeeklyHeatmap, idle slots / observed slots, never the fixed 168, so a short-window VM is not falsely flagged) on the primary path, or sd.IdlePercentage on the secondary aggregator-ScheduleData path. Gated on idlePct >= schedulingIdleMinPercent (20%). With neither a heatmap measurement nor pre-computed schedule data (and on the no-active-slot sentinel from DeriveStartStopCron), the rule abstains (returns nil). Never a fabricated flat fraction (the schedulingFallbackSavings ~67% literal lives in constants.go and is not consumed by the data-driven path), never a $0/advisory dump. Abstains on pricing absent (cost ≤ 0).

## Applying the start and stop cron

1. Review the recommended start/stop schedule (start cron, stop cron, timezone) and apply it from this recommendation

## The three series that build the heatmap

CPUUtilization + VMNetworkIn + VMNetworkOut (30d): drive the weekly heatmap
