Repository: Azure/azqr

Issue:
microsoft.web/sites panic: runtime error: invalid memory address or nil pointer dereference
Hello,
latest release, on China env, we receive

2025-11-05T10:20:43Z ERR Retry limit reached for Graph query: // Azure Resource Graph Query
// Check if Health Check is enabled for App Service

resources
| where type =~ 'microsoft.web/sites'
| where properties.kind has 'app'
| join kind = inner
    (
    appserviceresources
    | where isnull(properties.HealthCheckPath) == true
    | project  name
    ) on name
| project recommendationId = "fd049c28-ae6d-48f0-a641-cc3ba1a3fe1d", name, id, tags, param1 = "Healthcheckpath = not set"

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x8 pc=0x121196d]

goroutine 91 [running]:
github.com/Azure/azqr/internal/graph.(*GraphQueryClient).Query(0xc0004821e0, {0x22c95b0, 0xc0001da0f0}, {0xc000c0e8c0, 0x1b7}, {0xc000a12080, 0x1, 0x9a?})
        github.com/Azure/azqr/internal/graph/graph.go:128 +0x38d
github.com/Azure/azqr/internal/graph.AprlScanner.graphScan({{_, _, _}, {_, _, _}, _, _, _}, {0x22c95b0, ...}, ...)
        github.com/Azure/azqr/internal/graph/graph_scanner.go:261 +0x245
github.com/Azure/azqr/internal/graph.(*AprlScanner).worker(0xc0001da140, {0x22c95b0, 0xc0001da0f0}, 0xc0004821e0, 0xc00019d770, 0xc0001e8000, 0xc0001e8070, 0xc00078e470)
        github.com/Azure/azqr/internal/graph/graph_scanner.go:244 +0x2a5
created by github.com/Azure/azqr/internal/graph.AprlScanner.Scan in goroutine 1
        github.com/Azure/azqr/internal/graph/graph_scanner.go:206 +0x285

Regards,
Red.

Hints from issue comments:
@red-erik: Running the graph query we have

<img width="918" height="107" alt="Image" src="https://github.com/user-attachments/assets/c812aa78-e7dd-4a75-b8e9-de037bd1b00d" />

Regards,
Red.@cmendible: @red-erik the graph query has not changed and I can run it without issues on my side. Let me check what can be causing the null pointer.@cmendible: @red-erik if possible, test the binary form here: https://github.com/Azure/azqr/actions/runs/19102462180#artifacts 

Run `.\azqr scan asp --debug > stdout.txt 2> stderr.txt` and let me know what you find.

Solution patch:
diff --git a/README.md b/README.md
index 4d60a88..1255ade 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,8 @@ ng | Microsoft.Network/natGateways
 nic | Microsoft.Network/networkInterfaces
 nsg | Microsoft.Network/networkSecurityGroups
 nw | Microsoft.Network/networkWatchers
+odb | Oracle.Database/cloudExadataInfrastructures
+odb | Oracle.Database/cloudVmClusters
 pdnsz | Microsoft.Network/privateDnsZones
 pep | Microsoft.Network/privateEndpoints
 pip | Microsoft.Network/publicIPAddresses
@@ -260,6 +262,18 @@ You can explore your scan results with a lightweight embedded web UI using the `
 ./azqr show -f report.json --open
 ```
 
+### Compare Scan Reports (compare command)
+
+You can compare two azqr scan reports to identify differences in recommendations and resources using the `compare` command:
+
+```bash
+# Compare two Excel reports
+./azqr compare --file1 scan_before.xlsx --file2 scan_after.xlsx
+
+# Save comparison results to a file
+./azqr compare --file1 scan1.xlsx --file2 scan2.xlsx --output comparison.txt
+```
+
 ## Binary Verification
 
 To verify the authenticity of downloaded binaries, see our [Binary Verification Guide](SECURITY_VERIFICATION.md).
diff --git a/cmd/azqr/commands/compare.go b/cmd/azqr/commands/compare.go
new file mode 100644
index 0000000..5ca3551
--- /dev/null
+++ b/cmd/azqr/commands/compare.go
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package commands
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/Azure/azqr/internal/comparator"
+	"github.com/rs/zerolog/log"
+	"github.com/spf13/cobra"
+)
+
+func init() {
+	compareCmd.Flags().StringP("file1", "a", "", "Path to first report file (required)")
+	compareCmd.Flags().StringP("file2", "b", "", "Path to second report file (required)")
+	compareCmd.Flags().StringP("format", "f", "excel", "Report format to compare (excel)")
+	compareCmd.Flags().StringP("output", "o", "", "Output file for comparison results (optional, prints to console if not specified)")
+	_ = compareCmd.MarkFlagRequired("file1")
+	_ = compareCmd.MarkFlagRequired("file2")
+	rootCmd.AddCommand(compareCmd)
+}
+
+var compareCmd = &cobra.Command{
+	Use:   "compare",
+	Short: "Compare two azqr scan reports",
+	Long: `Compare two azqr scan reports to identify differences in recommendations and resources.
+Supports Excel (.xlsx).
+
+For Excel format, the comparison provides:
+  - Row count comparison for each sheet
+  - Detection of duplicate rows after row 4 (data rows)`,
+	Args: cobra.NoArgs,
+	RunE: func(cmd *cobra.Command, args []string) error {
+
+		file1, _ := cmd.Flags().GetString("file1")
+		file2, _ := cmd.Flags().GetString("file2")
+		format, _ := cmd.Flags().GetString("format")
+		output, _ := cmd.Flags().GetString("output")
+
+		// Validate files exist
+		if _, err := os.Stat(file1); err != nil {
+			return fmt.Errorf("cannot access file1: %w", err)
+		}
+		if _, err := os.Stat(file2); err != nil {
+			return fmt.Errorf("cannot access file2: %w", err)
+		}
+
+		// Normalize format
+		format = strings.ToLower(strings.TrimSpace(format))
+
+		// Perform comparison based on format
+		var resultStr string
+		var err error
+
+		switch format {
+		case "excel", "xlsx":
+			resultStr, err = compareExcelFiles(file1, file2)
+		default:
+			return fmt.Errorf("unsupported format: %s (supported: excel)", format)
+		}
+
+		if err != nil {
+			return err
+		}
+
+		// Output results
+		if output != "" {
+			// Write to file
+			if err := os.WriteFile(output, []byte(resultStr), 0644); err != nil {
+				return fmt.Errorf("failed to write output file: %w", err)
+			}
+			log.Info().Msgf("Comparison results written to: %s", output)
+		} else {
+			// Print to console
+			fmt.Println(resultStr)
+		}
+
+		return nil
+	},
+}
+
+// compareExcelFiles compares two Excel files and returns formatted results
+func compareExcelFiles(file1, file2 string) (string, error) {
+	log.Info().Msgf("Comparing Excel files...")
+
+	result, err := comparator.CompareExcelFiles(file1, file2)
+	if err != nil {
+		return "", fmt.Errorf("failed to compare Excel files: %w", err)
+	}
+
+	return comparator.FormatComparisonResult(result), nil
+}
diff --git a/cmd/azqr/commands/odb.go b/cmd/azqr/commands/odb.go
new file mode 100644
index 0000000..a750fa9
--- /dev/null
+++ b/cmd/azqr/commands/odb.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package commands
+
+import (
+	"github.com/spf13/cobra"
+)
+
+func init() {
+	scanCmd.AddCommand(odbCmd)
+}
+
+var odbCmd = &cobra.Command{
+	Use:   "odb",
+	Short: "Scan Oracle Database@Azure",
+	Long:  "Scan Oracle Database@Azure",
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		scan(cmd, []string{"odb"})
+	},
+}
diff --git a/cmd/azqr/commands/scan.go b/cmd/azqr/commands/scan.go
index 29c78e9..2224330 100644
--- a/cmd/azqr/commands/scan.go
+++ b/cmd/azqr/commands/scan.go
@@ -20,6 +20,7 @@ func init() {
 	scanCmd.PersistentFlags().BoolP("defender", "d", true, "Scan Defender Status (default) (default true)")
 	scanCmd.PersistentFlags().BoolP("advisor", "a", true, "Scan Azure Advisor Recommendations (default) (default true)")
 	scanCmd.PersistentFlags().BoolP("costs", "c", true, "Scan Azure Costs (default) (default true)")
+	scanCmd.PersistentFlags().BoolP("policy", "p", false, "Scan Azure Policy compliance")
 	// ...existing code...
 	// Add flags for all internal plugins, default to false
 	for _, pluginName := range requireInternalPluginsList() {
@@ -60,6 +61,7 @@ func scan(cmd *cobra.Command, scannerKeys []string) {
 	defender, _ := cmd.Flags().GetBool("defender")
 	advisor, _ := cmd.Flags().GetBool("advisor")
 	cost, _ := cmd.Flags().GetBool("costs")
+	policy, _ := cmd.Flags().GetBool("policy")
 	arc, _ := cmd.Flags().GetBool("arc")
 	xlsx, _ := cmd.Flags().GetBool("xlsx")
 	csv, _ := cmd.Flags().GetBool("csv")
@@ -95,6 +97,7 @@ func scan(cmd *cobra.Command, scannerKeys []string) {
 		Mask:                   mask,
 		Stdout:                 stdout,
 		Debug:                  debug,
+		Policy:                 policy,
 		ScannerKeys:            scannerKeys,
 		Filters:                filters,
 		UseAzqrRecommendations: useAzqr,
diff --git a/cmd/azqr/commands/scan_debug.go b/cmd/azqr/commands/scan_debug.go
index 1516baa..d13020f 100644
--- a/cmd/azqr/commands/scan_debug.go
+++ b/cmd/azqr/commands/scan_debug.go
@@ -23,6 +23,7 @@ func init() {
 	scanCmd.PersistentFlags().BoolP("defender", "d", true, "Scan Defender Status (default) (default true)")
 	scanCmd.PersistentFlags().BoolP("advisor", "a", true, "Scan Azure Advisor Recommendations (default) (default true)")
 	scanCmd.PersistentFlags().BoolP("costs", "c", true, "Scan Azure Costs (default) (default true)")
+	scanCmd.PersistentFlags().BoolP("policy", "p", false, "Scan Azure Policy compliance")
 	scanCmd.PersistentFlags().BoolP("xlsx", "", true, "Create Excel report (default) (default true)")
 	scanCmd.PersistentFlags().BoolP("json", "", false, "Create JSON report files")
 	scanCmd.PersistentFlags().BoolP("csv", "", false, "Create CSV report files")
@@ -60,6 +61,7 @@ func scan(cmd *cobra.Command, scannerKeys []string) {
 	defender, _ := cmd.Flags().GetBool("defender")
 	advisor, _ := cmd.Flags().GetBool("advisor")
 	cost, _ := cmd.Flags().GetBool("costs")
+	policy, _ := cmd.Flags().GetBool("policy")
 	xlsx, _ := cmd.Flags().GetBool("xlsx")
 	csv, _ := cmd.Flags().GetBool("csv")
 	json, _ := cmd.Flags().GetBool("json")
@@ -129,6 +131,7 @@ func scan(cmd *cobra.Command, scannerKeys []string) {
 		Mask:                   mask,
 		Stdout:                 stdout,
 		Debug:                  debug,
+		Policy:                 policy,
 		ScannerKeys:            scannerKeys,
 		Filters:                filters,
 		UseAzqrRecommendations: useAzqr,
diff --git a/data/recommendations.json b/data/recommendations.json
index 92c5ae7..ed64f99 100644
--- a/data/recommendations.json
+++ b/data/recommendations.json
@@ -2919,6 +2919,14 @@
 		"recommendationId": "48ea6480-6263-40ba-8937-326d790e63f6",
 		"resourceType": "Microsoft.MachineLearningServices/workspaces"
 	},
+	{
+		"category": "OtherBestPractices",
+		"impact": "High",
+		"learnMoreUrl": "https://learn.microsoft.com/rest/api/oracle/cloud-vm-clusters/get?view=rest-oracle-2023-09-01\u0026tabs=HTTP#cloudvmclusterlifecyclestate",
+		"recommendation": "Ensure ODAA clusters are in Available state under normal operations",
+		"recommendationId": "4b33324a-70cd-4bac-bdae-da4c382c436b",
+		"resourceType": "Oracle.Database/cloudVmClusters"
+	},
 	{
 		"category": "Governance",
 		"impact": "Medium",
@@ -3219,7 +3227,7 @@
 		"category": "OtherBestPractices",
 		"impact": "High",
 		"learnMoreUrl": "https://learn.microsoft.com/azure/application-gateway/configuration-infrastructure#size-of-the-subnet",
-		"recommendation": "Ensure Application Gateway Subnet is using a /24 subnet mask",
+		"recommendation": "A minimum subnet size of /24 is recommended for Application Gateway v2 subnets.",
 		"recommendationId": "8364fd0a-7c0e-e240-9d95-4bf965aec243",
 		"resourceType": "Microsoft.Network/applicationGateways"
 	},
@@ -3423,6 +3431,14 @@
 		"recommendationId": "a8d25876-7951-b646-b4e8-880c9031596b",
 		"resourceType": "Microsoft.Compute/virtualMachines"
 	},
+	{
+		"category": "OtherBestPractices",
+		"impact": "High",
+		"learnMoreUrl": "https://learn.microsoft.com/azure/aks/csi-storage-drivers",
+		"recommendation": "Upgrade Persistent Volumes using in-tree drivers to Azure CSI drivers",
+		"recommendationId": "b002c030-72e6-4a37-8217-1cb276c43169",
+		"resourceType": "Microsoft.ContainerService/managedClusters"
+	},
 	{
 		"category": "Scalability",
 		"impact": "High",
@@ -3559,6 +3575,14 @@
 		"recommendationId": "c72b7fee-1fa0-5b4b-98e5-54bcae95bb74",
 		"resourceType": "Microsoft.Network/azureFirewalls"
 	},
+	{
+		"category": "OtherBestPractices",
+		"impact": "High",
+		"learnMoreUrl": "https://learn.microsoft.com/rest/api/oracle/cloud-exadata-infrastructures/get?view=rest-oracle-2023-09-01\u0026tabs=HTTP#cloudexadatainfrastructurelifecyclestate",
+		"recommendation": "Ensure ODAA infrastructure is in Available state under normal operations",
+		"recommendationId": "c99d730b-8754-447f-bd5d-3e8850a12235",
+		"resourceType": "Oracle.Database/cloudExadataInfrastructures"
+	},
 	{
 		"category": "HighAvailability",
 		"impact": "High",
diff --git a/docs/content/en/docs/Usage/_index.md b/docs/content/en/docs/Usage/_index.md
index b53c71f..dc971b2 100644
--- a/docs/content/en/docs/Usage/_index.md
+++ b/docs/content/en/docs/Usage/_index.md
@@ -322,6 +322,18 @@ azqr scan --output-file "azqr_action_plan_$timestamp"
 
 > By default, the output file name is `azqr_action_plan_YYYY_MM_DD_THHMMSS`.
 
+## Compare Scan Reports (compare command)
+
+You can compare two azqr scan reports to identify differences in recommendations using the `compare` command:
+
+```bash
+# Compare two Excel reports
+./azqr compare --file1 scan_before.xlsx --file2 scan_after.xlsx
+
+# Save comparison results to a file
+./azqr compare --file1 scan1.xlsx --file2 scan2.xlsx --output comparison.txt
+```
+
 ## Help
 
 You can get help for `azqr` commands by running:
diff --git a/go.mod b/go.mod
index c260943..0245edf 100644
--- a/go.mod
+++ b/go.mod
@@ -59,6 +59,7 @@ require (
 	github.com/spf13/cobra v1.10.1
 	github.com/stretchr/testify v1.11.1
 	github.com/xuri/excelize/v2 v2.10.0
+	golang.org/x/time v0.14.0
 	gopkg.in/yaml.v3 v3.0.1
 )
 
diff --git a/go.sum b/go.sum
index be81381..eedb8b6 100644
--- a/go.sum
+++ b/go.sum
@@ -228,6 +228,8 @@ golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
 golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
 golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/comparator/excel.go b/internal/comparator/excel.go
new file mode 100644
index 0000000..0add1a1
--- /dev/null
+++ b/internal/comparator/excel.go
@@ -0,0 +1,539 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package comparator
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/xuri/excelize/v2"
+)
+
+// SheetComparison represents the comparison result for a single sheet
+type SheetComparison struct {
+	SheetName     string
+	OldRowCount   int
+	NewRowCount   int
+	RowDifference int
+	HasDuplicates bool
+	DuplicateRows []int // Row numbers (1-indexed) that are duplicates
+}
+
+// RecommendationChange represents a change in a recommendation row
+type RecommendationChange struct {
+	RecommendationID      string
+	Recommendation        string
+	Category              string
+	Impact                string
+	ResourceType          string
+	OldImpactedResources  string
+	NewImpactedResources  string
+	ImpactedResourcesDiff int
+	ChangeType            string // "added", "removed", "changed", "unchanged"
+}
+
+// ExcelComparisonResult represents the complete comparison result
+type ExcelComparisonResult struct {
+	OldFilePath           string
+	NewFilePath           string
+	OldFileLabel          string // Label for older file
+	NewFileLabel          string // Label for newer file
+	SheetComparisons      []SheetComparison
+	RecommendationChanges []RecommendationChange
+}
+
+// CompareExcelFiles compares two Excel files and returns row counts and duplicate detection
+// Determines which file is older/newer based on modification time
+func CompareExcelFiles(file1Path, file2Path string) (*ExcelComparisonResult, error) {
+	// Clean and normalize paths
+	cleanPath1 := filepath.Clean(file1Path)
+	cleanPath2 := filepath.Clean(file2Path)
+
+	// Get file modification times to determine old vs new
+	stat1, err := os.Stat(cleanPath1)
+	if err != nil {
+		return nil, fmt.Errorf("failed to stat file1 (%s): %w", cleanPath1, err)
+	}
+	stat2, err := os.Stat(cleanPath2)
+	if err != nil {
+		return nil, fmt.Errorf("failed to stat file2 (%s): %w", cleanPath2, err)
+	}
+
+	// Determine which file is older
+	var oldPath, newPath string
+	var oldFile, newFile *excelize.File
+	if stat1.ModTime().Before(stat2.ModTime()) {
+		oldPath = cleanPath1
+		newPath = cleanPath2
+	} else {
+		oldPath = cleanPath2
+		newPath = cleanPath1
+	}
+
+	// Open old file
+	oldFile, err = excelize.OpenFile(oldPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to open old file (%s): %w", oldPath, err)
+	}
+	defer func() {
+		_ = oldFile.Close()
+	}()
+
+	// Open new file
+	newFile, err = excelize.OpenFile(newPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to open new file (%s): %w", newPath, err)
+	}
+	defer func() {
+		_ = newFile.Close()
+	}()
+
+	result := &ExcelComparisonResult{
+		OldFilePath:      oldPath,
+		NewFilePath:      newPath,
+		OldFileLabel:     "Old",
+		NewFileLabel:     "New",
+		SheetComparisons: []SheetComparison{},
+	}
+
+	// Get all sheets from both files
+	sheetsOld := oldFile.GetSheetList()
+	sheetsNew := newFile.GetSheetList()
+
+	// Create a map of all unique sheet names
+	sheetMap := make(map[string]bool)
+	for _, sheet := range sheetsOld {
+		sheetMap[sheet] = true
+	}
+	for _, sheet := range sheetsNew {
+		sheetMap[sheet] = true
+	}
+
+	// Compare each sheet
+	for sheetName := range sheetMap {
+		comparison := SheetComparison{
+			SheetName:     sheetName,
+			DuplicateRows: []int{},
+		}
+
+		// Get row count from old file
+		rowsOld, err := oldFile.GetRows(sheetName)
+		if err == nil {
+			comparison.OldRowCount = len(rowsOld)
+		} else {
+			comparison.OldRowCount = 0
+		}
+
+		// Get row count from new file
+		rowsNew, err := newFile.GetRows(sheetName)
+		if err == nil {
+			comparison.NewRowCount = len(rowsNew)
+		} else {
+			comparison.NewRowCount = 0
+		}
+
+		// Calculate difference
+		comparison.RowDifference = comparison.NewRowCount - comparison.OldRowCount
+
+		// Check for duplicates after row 4 in new file (if it exists)
+		if comparison.NewRowCount > 0 {
+			comparison.HasDuplicates, comparison.DuplicateRows = findDuplicatesAfterRow4(rowsNew)
+		}
+
+		result.SheetComparisons = append(result.SheetComparisons, comparison)
+	}
+
+	// Compare Recommendations sheet in detail
+	result.RecommendationChanges = compareRecommendations(oldFile, newFile)
+
+	return result, nil
+}
+
+// findDuplicatesAfterRow4 checks for duplicate rows starting from row 5 (index 4)
+// Returns true if duplicates found and a list of duplicate row numbers
+func findDuplicatesAfterRow4(rows [][]string) (bool, []int) {
+	if len(rows) <= 4 {
+		return false, []int{}
+	}
+
+	// Create a map to track seen rows (starting from row 5, which is index 4)
+	seenRows := make(map[string]int)
+	duplicateRows := []int{}
+
+	for i := 4; i < len(rows); i++ {
+		// Convert row to a string representation
+		rowKey := rowToString(rows[i])
+
+		// Check if we've seen this row before
+		if firstOccurrence, exists := seenRows[rowKey]; exists {
+			// This is a duplicate - record both the first occurrence and current row
+			if !contains(duplicateRows, firstOccurrence+1) {
+				duplicateRows = append(duplicateRows, firstOccurrence+1)
+			}
+			duplicateRows = append(duplicateRows, i+1)
+		} else {
+			// First time seeing this row
+			seenRows[rowKey] = i
+		}
+	}
+
+	return len(duplicateRows) > 0, duplicateRows
+}
+
+// rowToString converts a row slice to a string for comparison
+func rowToString(row []string) string {
+	result := ""
+	for i, cell := range row {
+		if i > 0 {
+			result += "|"
+		}
+		result += cell
+	}
+	return result
+}
+
+// contains checks if a slice contains a value
+func contains(slice []int, val int) bool {
+	for _, item := range slice {
+		if item == val {
+			return true
+		}
+	}
+	return false
+}
+
+// compareRecommendations compares the Recommendations sheet in detail
+func compareRecommendations(f1, f2 *excelize.File) []RecommendationChange {
+	changes := []RecommendationChange{}
+
+	// Get rows from Recommendations sheet in both files
+	rows1, err1 := f1.GetRows("Recommendations")
+	rows2, err2 := f2.GetRows("Recommendations")
+
+	// If either file doesn't have Recommendations sheet, return empty
+	if err1 != nil || err2 != nil {
+		return changes
+	}
+
+	// Skip to data rows (row 5+, indices 4+)
+	if len(rows1) <= 4 || len(rows2) <= 4 {
+		return changes
+	}
+
+	// Get headers from row 4 (index 3)
+	headers := rows1[3]
+	if len(headers) == 0 {
+		return changes
+	}
+
+	// Find column indices for key fields
+	recIDCol := findColumnIndex(headers, "Recommendation Id")
+	recCol := findColumnIndex(headers, "Recommendation")
+	categoryCol := findColumnIndex(headers, "Category")
+	impactCol := findColumnIndex(headers, "Impact")
+	resourceTypeCol := findColumnIndex(headers, "Azure Service Category")
+	impactedCol := findColumnIndex(headers, "Number of Impacted Resources")
+
+	if recIDCol == -1 || impactedCol == -1 {
+		return changes
+	}
+
+	// Build maps of recommendations from both files
+	recs1 := buildRecommendationMap(rows1[4:], recIDCol, recCol, categoryCol, impactCol, resourceTypeCol, impactedCol)
+	recs2 := buildRecommendationMap(rows2[4:], recIDCol, recCol, categoryCol, impactCol, resourceTypeCol, impactedCol)
+
+	// Track processed recommendations
+	processed := make(map[string]bool)
+
+	// Find changes and additions
+	for id, rec2 := range recs2 {
+		processed[id] = true
+		if rec1, exists := recs1[id]; exists {
+			// Check if impacted resources changed
+			if rec1.impactedResources != rec2.impactedResources {
+				diff := parseInt(rec2.impactedResources) - parseInt(rec1.impactedResources)
+				changes = append(changes, RecommendationChange{
+					RecommendationID:      id,
+					Recommendation:        rec2.recommendation,
+					Category:              rec2.category,
+					Impact:                rec2.impact,
+					ResourceType:          rec2.resourceType,
+					OldImpactedResources:  rec1.impactedResources,
+					NewImpactedResources:  rec2.impactedResources,
+					ImpactedResourcesDiff: diff,
+					ChangeType:            "changed",
+				})
+			}
+		} else {
+			// New recommendation in file2
+			changes = append(changes, RecommendationChange{
+				RecommendationID:      id,
+				Recommendation:        rec2.recommendation,
+				Category:              rec2.category,
+				Impact:                rec2.impact,
+				ResourceType:          rec2.resourceType,
+				OldImpactedResources:  "0",
+				NewImpactedResources:  rec2.impactedResources,
+				ImpactedResourcesDiff: parseInt(rec2.impactedResources),
+				ChangeType:            "added",
+			})
+		}
+	}
+
+	// Find removals
+	for id, rec1 := range recs1 {
+		if !processed[id] {
+			changes = append(changes, RecommendationChange{
+				RecommendationID:      id,
+				Recommendation:        rec1.recommendation,
+				Category:              rec1.category,
+				Impact:                rec1.impact,
+				ResourceType:          rec1.resourceType,
+				OldImpactedResources:  rec1.impactedResources,
+				NewImpactedResources:  "0",
+				ImpactedResourcesDiff: -parseInt(rec1.impactedResources),
+				ChangeType:            "removed",
+			})
+		}
+	}
+
+	return changes
+}
+
+// recommendationData holds recommendation information
+type recommendationData struct {
+	recommendation    string
+	category          string
+	impact            string
+	resourceType      string
+	impactedResources string
+}
+
+// buildRecommendationMap builds a map of recommendations from rows
+func buildRecommendationMap(rows [][]string, recIDCol, recCol, categoryCol, impactCol, resourceTypeCol, impactedCol int) map[string]recommendationData {
+	recMap := make(map[string]recommendationData)
+
+	for _, row := range rows {
+		if len(row) <= recIDCol {
+			continue
+		}
+
+		id := getCell(row, recIDCol)
+		if id == "" {
+			continue
+		}
+
+		recMap[id] = recommendationData{
+			recommendation:    getCell(row, recCol),
+			category:          getCell(row, categoryCol),
+			impact:            getCell(row, impactCol),
+			resourceType:      getCell(row, resourceTypeCol),
+			impactedResources: getCell(row, impactedCol),
+		}
+	}
+
+	return recMap
+}
+
+// findColumnIndex finds the index of a column by header name (case-insensitive exact match first, then partial)
+func findColumnIndex(headers []string, name string) int {
+	lowerName := toLower(name)
+
+	// First pass: exact match
+	for i, header := range headers {
+		if toLower(header) == lowerName {
+			return i
+		}
+	}
+
+	// Second pass: partial match (only if exact match not found)
+	// But avoid matching substrings like "Recommendation" matching "Recommendation Source"
+	for i, header := range headers {
+		lowerHeader := toLower(header)
+		// Only match if the search term is at the start or is the whole header
+		if lowerHeader == lowerName || (len(lowerHeader) >= len(lowerName) && lowerHeader[:len(lowerName)] == lowerName) {
+			return i
+		}
+	}
+
+	return -1
+}
+
+// getCell safely gets a cell value from a row
+func getCell(row []string, col int) string {
+	if col >= 0 && col < len(row) {
+		return row[col]
+	}
+	return ""
+}
+
+// toLower converts string to lowercase
+func toLower(s string) string {
+	result := ""
+	for _, r := range s {
+		if r >= 'A' && r <= 'Z' {
+			result += string(r + 32)
+		} else {
+			result += string(r)
+		}
+	}
+	return result
+}
+
+// parseInt converts string to int, returns 0 if invalid
+func parseInt(s string) int {
+	result := 0
+	for _, r := range s {
+		if r >= '0' && r <= '9' {
+			result = result*10 + int(r-'0')
+		}
+	}
+	return result
+}
+
+// FormatComparisonResult formats the comparison result as a human-readable string
+func FormatComparisonResult(result *ExcelComparisonResult) string {
+	output := "Comparison Results:\n"
+	output += fmt.Sprintf("Old File: %s\n", result.OldFilePath)
+	output += fmt.Sprintf("New File: %s\n\n", result.NewFilePath)
+
+	output += fmt.Sprintf("%-40s | %10s | %10s | %10s | %s\n",
+		"Sheet Name", "Old Rows", "New Rows", "Difference", "Has Duplicates")
+	output += fmt.Sprintf("%s\n", "-----------------------------------------------------------------------------------------------------------")
+
+	for _, comparison := range result.SheetComparisons {
+		duplicateStatus := "No"
+		if comparison.HasDuplicates {
+			duplicateStatus = fmt.Sprintf("Yes (%d)", len(comparison.DuplicateRows))
+		}
+
+		diffStr := fmt.Sprintf("%+d", comparison.RowDifference)
+		output += fmt.Sprintf("%-40s | %10d | %10d | %10s | %s\n",
+			comparison.SheetName,
+			comparison.OldRowCount,
+			comparison.NewRowCount,
+			diffStr,
+			duplicateStatus)
+
+		// Show duplicate row numbers if present
+		if comparison.HasDuplicates && len(comparison.DuplicateRows) > 0 {
+			output += fmt.Sprintf("  Duplicate rows: %v\n", comparison.DuplicateRows)
+		}
+	}
+
+	// Add Recommendations diff section if available
+	if len(result.RecommendationChanges) > 0 {
+		output += "\n" + formatRecommendationChanges(result.RecommendationChanges)
+	}
+
+	return output
+}
+
+// formatRecommendationChanges formats recommendation changes as a table
+func formatRecommendationChanges(changes []RecommendationChange) string {
+	output := "\nRecommendations Detailed Diff:\n"
+	output += "==============================\n\n"
+
+	// Group changes by type
+	added := []RecommendationChange{}
+	removed := []RecommendationChange{}
+	changed := []RecommendationChange{}
+
+	for _, change := range changes {
+		switch change.ChangeType {
+		case "added":
+			added = append(added, change)
+		case "removed":
+			removed = append(removed, change)
+		case "changed":
+			changed = append(changed, change)
+		}
+	}
+
+	// Format changed recommendations as table
+	if len(changed) > 0 {
+		output += fmt.Sprintf("Changed Recommendations (%d):\n", len(changed))
+		output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s | %10s | %10s\n",
+			"Resource Type", "Category", "Impact", "Change", "Recommendation", "Old", "New", "Diff")
+		output += "-------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
+
+		for _, change := range changed {
+			diffStr := fmt.Sprintf("%+d", change.ImpactedResourcesDiff)
+			resType := change.ResourceType
+			if resType == "" {
+				resType = "N/A"
+			}
+			output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s | %10s | %10s\n",
+				truncate(resType, 20),
+				truncate(change.Category, 20),
+				truncate(change.Impact, 10),
+				"Changed",
+				truncate(change.Recommendation, 50),
+				change.OldImpactedResources,
+				change.NewImpactedResources,
+				diffStr)
+		}
+		output += "\n"
+	}
+
+	// Format added recommendations as table
+	if len(added) > 0 {
+		output += fmt.Sprintf("Added Recommendations (%d):\n", len(added))
+		output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s\n",
+			"Resource Type", "Category", "Impact", "Change", "Recommendation", "Impacted")
+		output += "---------------------------------------------------------------------------------------------------------------------------------------\n"
+
+		for _, change := range added {
+			resType := change.ResourceType
+			if resType == "" {
+				resType = "N/A"
+			}
+			output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s\n",
+				truncate(resType, 20),
+				truncate(change.Category, 20),
+				truncate(change.Impact, 10),
+				"Added",
+				truncate(change.Recommendation, 50),
+				change.NewImpactedResources)
+		}
+		output += "\n"
+	}
+
+	// Format removed recommendations as table
+	if len(removed) > 0 {
+		output += fmt.Sprintf("Removed Recommendations (%d):\n", len(removed))
+		output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s\n",
+			"Resource Type", "Category", "Impact", "Change", "Recommendation", "Impacted")
+		output += "---------------------------------------------------------------------------------------------------------------------------------------\n"
+
+		for _, change := range removed {
+			resType := change.ResourceType
+			if resType == "" {
+				resType = "N/A"
+			}
+			output += fmt.Sprintf("%-20s | %-20s | %-10s | %-8s | %-50s | %10s\n",
+				truncate(resType, 20),
+				truncate(change.Category, 20),
+				truncate(change.Impact, 10),
+				"Removed",
+				truncate(change.Recommendation, 50),
+				change.OldImpactedResources)
+		}
+	}
+
+	// Summary
+	output += fmt.Sprintf("Summary: %d changed, %d added, %d removed\n",
+		len(changed), len(added), len(removed))
+
+	return output
+}
+
+// truncate truncates a string to a maximum length
+func truncate(s string, maxLen int) string {
+	if len(s) <= maxLen {
+		return s
+	}
+	return s[:maxLen-3] + "..."
+}
diff --git a/internal/graph/aprl b/internal/graph/aprl
index 6c15d5c..990f04e 160000
--- a/internal/graph/aprl
+++ b/internal/graph/aprl
@@ -1 +1 @@
-Subproject commit 6c15d5c39404ff7e6c96fe6e4ee2b343935e57fc
+Subproject commit 990f04eedbeac48446a7eaf32ee60c5428c91418
diff --git a/internal/graph/graph.go b/internal/graph/graph.go
index 476c246..d6ce3dc 100644
--- a/internal/graph/graph.go
+++ b/internal/graph/graph.go
@@ -36,8 +36,8 @@ type GraphResult struct {
 // QueryRequestOptions represents options for the Resource Graph query.
 type QueryRequestOptions struct {
 	ResultFormat             string  `json:"resultFormat,omitempty"`             // Format of the result
-	Top                      *int32  `json:"top,omitempty"`                      // Max number of results
-	SkipToken                *string `json:"$skipToken,omitempty"`                // Token for pagination
+	Top                      *int32  `json:"$top,omitempty"`                      // Max number of results
+	SkipToken                *string `json:"$skipToken,omitempty"`               // Token for pagination
 	AuthorizationScopeFilter *string `json:"authorizationScopeFilter,omitempty"` // Filter by authorization scope
 }
 
@@ -110,7 +110,7 @@ func (q *GraphQueryClient) Query(ctx context.Context, query string, subscription
 		format := "objectArray"
 		options := &QueryRequestOptions{
 			ResultFormat:             format,
-			Top:                      to.Ptr(int32(1000)),
+			Top:                      to.Ptr(int32(5000)),
 			AuthorizationScopeFilter: to.Ptr("AtScopeAndAbove"), // Include management groups for Azure Policy queries
 		}
 
@@ -133,13 +133,13 @@ func (q *GraphQueryClient) Query(ctx context.Context, query string, subscription
 			}
 		}
 	}
+	log.Debug().Msgf("Graph query returned %d records", len(result.Data))
 	return &result
 }
 
 // retry executes the Resource Graph query with retries when throttling occurs.
 // Returns the QueryResponse or error.
 func (q *GraphQueryClient) retry(ctx context.Context, attempts int, request QueryRequest) (*QueryResponse, error) {
-	var err error
 	for i := 0; ; i++ {
 		resp, err := q.doRequest(ctx, request)
 		if err == nil {
@@ -148,7 +148,7 @@ func (q *GraphQueryClient) retry(ctx context.Context, attempts int, request Quer
 
 		if i >= (attempts - 1) {
 			log.Error().Msgf("Retry limit reached for Graph query: %s", request.Query)
-			break
+			return nil, err
 		}
 
 		// Quota limit reached, sleep for the duration specified in the response header
@@ -157,9 +157,7 @@ func (q *GraphQueryClient) retry(ctx context.Context, attempts int, request Quer
 			log.Debug().Msgf("Graph query quota limit reached. Sleeping for %s", duration)
 			time.Sleep(duration)
 		}
-
 	}
-	return nil, err
 }
 
 // doRequest sends the HTTP request to the Resource Graph API and returns the response.
@@ -180,11 +178,21 @@ func (q *GraphQueryClient) doRequest(ctx context.Context, request QueryRequest)
 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("Authorization", "Bearer "+q.accessToken)
 
-	// Wait for a token from the burstLimiter channel before making the request
-	<-throttling.GraphLimiter
+	// Wait for a token from the rate limiter before making the request
+	if err := throttling.WaitGraph(ctx); err != nil {
+		return nil, fmt.Errorf("rate limiter error: %w", err)
+	}
 
 	// Send request
 	resp, err := q.httpClient.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
+	}
+	defer func() {
+		if err := resp.Body.Close(); err != nil {
+			log.Fatal().Err(err).Msg("Failed to close response body")
+		}
+	}()
 
 	// Parse response JSON
 	queryResp := QueryResponse{}
@@ -214,15 +222,6 @@ func (q *GraphQueryClient) doRequest(ctx context.Context, request QueryRequest)
 
 	log.Debug().Msgf("Graph query quota remaining: %d, Retry after: %s", queryResp.Quota, queryResp.RetryAfter)
 
-	if err != nil {
-		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
-	}
-	defer func() {
-		if err := resp.Body.Close(); err != nil {
-			log.Fatal().Err(err).Msg("Failed to close response body")
-		}
-	}()
-
 	// Read response body
 	respBody, err := io.ReadAll(resp.Body)
 	if err != nil {
diff --git a/internal/graph/graph_scanner.go b/internal/graph/graph_scanner.go
index 78ab0ba..1eb9865 100644
--- a/internal/graph/graph_scanner.go
+++ b/internal/graph/graph_scanner.go
@@ -182,13 +182,14 @@ func (a AprlScanner) ListRecommendations() (map[string]map[string]models.AprlRec
 }
 
 // AprlScan scans Azure resources using Azure Proactive Resiliency Library v2 (APRL)
-func (a AprlScanner) Scan(ctx context.Context, cred azcore.TokenCredential) []models.AprlResult {
-	results := []models.AprlResult{}
+func (a AprlScanner) Scan(ctx context.Context, cred azcore.TokenCredential) []*models.AprlResult {
+	results := []*models.AprlResult{}
 	graph := NewGraphQuery(cred)
 
 	_, rules := a.ListRecommendations()
 
-	// Staggering queries to avoid throttling. Max 15 queries each 5 seconds. Azure Graph throttling is so agressive that only 1 worker will be used.
+	// Staggering queries to avoid throttling. Max 15 queries each 5 seconds.
+	// Use 10 workers to match burst capacity and fully utilize the 5-second window
 	// https://learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/guidance-for-throttled-requests#staggering-queries
 	batchSize := bucketCapacity
 	batches := int(math.Ceil(float64(len(rules)) / float64(batchSize)))
@@ -197,11 +198,12 @@ func (a AprlScanner) Scan(ctx context.Context, cred azcore.TokenCredential) []mo
 
 	// Buffer the jobs and results channels to the number of rules to avoid deadlocks.
 	jobs := make(chan models.AprlRecommendation, len(rules))
-	ch := make(chan []models.AprlResult, len(rules))
+	ch := make(chan []*models.AprlResult, len(rules))
 
 	var wg sync.WaitGroup
 
-	numWorkers := bucketCapacity
+	// Use 10 workers to match the rate limiter's burst capacity
+	numWorkers := 10
 	for w := 0; w < numWorkers; w++ {
 		go a.worker(ctx, graph, a.subscriptions, jobs, ch, &wg)
 	}
@@ -237,7 +239,7 @@ func (a AprlScanner) Scan(ctx context.Context, cred azcore.TokenCredential) []mo
 	return results
 }
 
-func (a *AprlScanner) worker(ctx context.Context, graph *GraphQueryClient, subscriptions map[string]string, jobs <-chan models.AprlRecommendation, results chan<- []models.AprlResult, wg *sync.WaitGroup) {
+func (a *AprlScanner) worker(ctx context.Context, graph *GraphQueryClient, subscriptions map[string]string, jobs <-chan models.AprlRecommendation, results chan<- []*models.AprlResult, wg *sync.WaitGroup) {
 	// worker processes batches of APRL recommendations from the jobs channel
 	for r := range jobs {
 		models.LogGraphRecommendationScan(r.ResourceType, r.RecommendationID)
@@ -250,11 +252,11 @@ func (a *AprlScanner) worker(ctx context.Context, graph *GraphQueryClient, subsc
 	}
 }
 
-func (a AprlScanner) graphScan(ctx context.Context, graphClient *GraphQueryClient, rule models.AprlRecommendation, subscriptions map[string]string) ([]models.AprlResult, error) {
-	results := []models.AprlResult{}
+func (a AprlScanner) graphScan(ctx context.Context, graphClient *GraphQueryClient, rule models.AprlRecommendation, subscriptions map[string]string) ([]*models.AprlResult, error) {
+	results := []*models.AprlResult{}
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 
 	if rule.GraphQuery != "" {
@@ -277,7 +279,7 @@ func (a AprlScanner) graphScan(ctx context.Context, graphClient *GraphQueryClien
 					subscriptionName = ""
 				}
 
-				results = append(results, models.AprlResult{
+				results = append(results, &models.AprlResult{
 					RecommendationID:    rule.RecommendationID,
 					Category:            models.RecommendationCategory(rule.Category),
 					Recommendation:      rule.Recommendation,
diff --git a/internal/models/models.go b/internal/models/models.go
index b03cddf..137440c 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -45,7 +45,7 @@ type (
 	IAzureScanner interface {
 		Init(config *ScannerConfig) error
 		GetRecommendations() map[string]AzqrRecommendation
-		Scan(scanContext *ScanContext) ([]AzqrServiceResult, error)
+		Scan(scanContext *ScanContext) ([]*AzqrServiceResult, error)
 		ResourceTypes() []string
 	}
 
@@ -57,7 +57,7 @@ type (
 		Location         string
 		Type             string
 		ServiceName      string
-		Recommendations  map[string]AzqrResult
+		Recommendations  map[string]*AzqrResult
 	}
 
 	AzqrRecommendation struct {
@@ -97,13 +97,9 @@ type (
 	}
 
 	ResourceTypeCount struct {
-		Subscription    string  `json:"Subscription"`
-		ResourceType    string  `json:"Resource Type"`
-		Count           float64 `json:"Number of Resources"`
-		AvailableInAPRL string  `json:"Available In APRL?"`
-		Custom1         string  `json:"Custom1"`
-		Custom2         string  `json:"Custom2"`
-		Custom3         string  `json:"Custom3"`
+		Subscription string  `json:"Subscription"`
+		ResourceType string  `json:"Resource Type"`
+		Count        float64 `json:"Number of Resources"`
 	}
 
 	AprlRecommendation struct {
@@ -258,15 +254,16 @@ func (r *AzqrRecommendation) ToAzureAprlRecommendation() AprlRecommendation {
 	}
 }
 
-func (e *RecommendationEngine) EvaluateRecommendations(rules map[string]AzqrRecommendation, target interface{}, scanContext *ScanContext) map[string]AzqrResult {
-	results := map[string]AzqrResult{}
+func (e *RecommendationEngine) EvaluateRecommendations(rules map[string]AzqrRecommendation, target interface{}, scanContext *ScanContext) map[string]*AzqrResult {
+	results := map[string]*AzqrResult{}
 
 	for k, rule := range rules {
 		if scanContext.Filters.Azqr.IsRecommendationExcluded(rule.RecommendationID) {
 			continue
 		}
 
-		results[k] = e.evaluateRecommendation(rule, target, scanContext)
+		result := e.evaluateRecommendation(rule, target, scanContext)
+		results[k] = &result
 	}
 
 	return results
diff --git a/internal/renderers/report_data.go b/internal/renderers/report_data.go
index 00685b2..c3a515e 100644
--- a/internal/renderers/report_data.go
+++ b/internal/renderers/report_data.go
@@ -14,13 +14,13 @@ type (
 	ReportData struct {
 		OutputFileName          string
 		Mask                    bool
-		Azqr                    []models.AzqrServiceResult
-		Aprl                    []models.AprlResult
-		Defender                []models.DefenderResult
-		DefenderRecommendations []models.DefenderRecommendation
-		Advisor                 []models.AdvisorResult
-		AzurePolicy             []models.AzurePolicyResult
-		ArcSQL                  []models.ArcSQLResult
+		Azqr                    []*models.AzqrServiceResult
+		Aprl                    []*models.AprlResult
+		Defender                []*models.DefenderResult
+		DefenderRecommendations []*models.DefenderRecommendation
+		Advisor                 []*models.AdvisorResult
+		AzurePolicy             []*models.AzurePolicyResult
+		ArcSQL                  []*models.ArcSQLResult
 		Cost                    *models.CostResult
 		Recommendations         map[string]map[string]models.AprlRecommendation
 		Resources               []*models.Resource
@@ -53,8 +53,18 @@ func (rd *ReportData) ExcludedResourcesTable() [][]string {
 func (rd *ReportData) ImpactedTable() [][]string {
 	headers := []string{"Validated Using", "Source", "Category", "Impact", "Resource Type", "Recommendation", "Recommendation Id", "Subscription Id", "Subscription Name", "Resource Group", "Resource Name", "Resource Id", "Param1", "Param2", "Param3", "Param4", "Param5", "Learn"}
 
+	// Use a map to track unique entries by ResourceID + RecommendationID
+	seen := make(map[string]bool)
 	rows := [][]string{}
+
 	for _, r := range rd.Aprl {
+		// Create composite key for deduplication
+		key := r.ResourceID + "|" + r.RecommendationID
+		if seen[key] {
+			continue // Skip duplicate
+		}
+		seen[key] = true
+
 		row := []string{
 			"Azure Resource Graph",
 			r.Source,
@@ -81,6 +91,13 @@ func (rd *ReportData) ImpactedTable() [][]string {
 	for _, d := range rd.Azqr {
 		for _, r := range d.Recommendations {
 			if r.NotCompliant {
+				// Create composite key for deduplication
+				key := d.ResourceID() + "|" + r.RecommendationID
+				if seen[key] {
+					continue // Skip duplicate
+				}
+				seen[key] = true
+
 				row := []string{
 					"Azure Resource Manager",
 					"AZQR",
@@ -297,17 +314,13 @@ func (rd *ReportData) RecommendationsTable() [][]string {
 }
 
 func (rd *ReportData) ResourceTypesTable() [][]string {
-	headers := []string{"Subscription Name", "Resource Type", "Number of Resources", "Available in APRL?", "Custom1", "Custom2", "Custom3"}
+	headers := []string{"Subscription Name", "Resource Type", "Number of Resources"}
 	rows := [][]string{}
 	for _, r := range rd.ResourceTypeCount {
 		row := []string{
 			r.Subscription,
 			r.ResourceType,
 			fmt.Sprint(r.Count),
-			r.AvailableInAPRL,
-			"",
-			"",
-			"",
 		}
 		rows = append(rows, row)
 	}
@@ -346,13 +359,13 @@ func NewReportData(outputFile string, mask bool) ReportData {
 		OutputFileName:          outputFile,
 		Mask:                    mask,
 		Recommendations:         map[string]map[string]models.AprlRecommendation{},
-		Azqr:                    []models.AzqrServiceResult{},
-		Aprl:                    []models.AprlResult{},
-		Defender:                []models.DefenderResult{},
-		DefenderRecommendations: []models.DefenderRecommendation{},
-		Advisor:                 []models.AdvisorResult{},
-		AzurePolicy:             []models.AzurePolicyResult{},
-		ArcSQL:                  []models.ArcSQLResult{},
+		Azqr:                    []*models.AzqrServiceResult{},
+		Aprl:                    []*models.AprlResult{},
+		Defender:                []*models.DefenderResult{},
+		DefenderRecommendations: []*models.DefenderRecommendation{},
+		Advisor:                 []*models.AdvisorResult{},
+		AzurePolicy:             []*models.AzurePolicyResult{},
+		ArcSQL:                  []*models.ArcSQLResult{},
 		Cost: &models.CostResult{
 			Items: []*models.CostResultItem{},
 		},
diff --git a/internal/scanner.go b/internal/scanner.go
index bb8c03b..d709a23 100644
--- a/internal/scanner.go
+++ b/internal/scanner.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"fmt"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/Azure/azqr/internal/az"
@@ -71,6 +72,7 @@ import (
 	_ "github.com/Azure/azqr/internal/scanners/nic"
 	_ "github.com/Azure/azqr/internal/scanners/nsg"
 	_ "github.com/Azure/azqr/internal/scanners/nw"
+	_ "github.com/Azure/azqr/internal/scanners/odb"
 	_ "github.com/Azure/azqr/internal/scanners/pdnsz"
 	_ "github.com/Azure/azqr/internal/scanners/pep"
 	_ "github.com/Azure/azqr/internal/scanners/pip"
@@ -112,6 +114,7 @@ type (
 		Json                   bool
 		Stdout                 bool
 		Debug                  bool
+		Policy                 bool
 		ScannerKeys            []string
 		Filters                *models.Filters
 		UseAzqrRecommendations bool
@@ -135,6 +138,7 @@ func NewScanParams() *ScanParams {
 		Csv:                    false,
 		Json:                   false,
 		Debug:                  false,
+		Policy:                 false,
 		ScannerKeys:            []string{},
 		Filters:                models.NewFilters(),
 		UseAzqrRecommendations: true,
@@ -412,26 +416,50 @@ func (sc Scanner) Scan(params *ScanParams) string {
 				PublicIPs:           pips,
 			}
 
-			ch := make(chan []models.AzqrServiceResult, len(filteredServiceScanners))
+			// Worker pool to limit concurrent scanner goroutines
+			const numScannerWorkers = 10
+			jobs := make(chan models.IAzureScanner, len(filteredServiceScanners))
+			results := make(chan []*models.AzqrServiceResult, len(filteredServiceScanners))
+
+			// Start worker pool
+			var workerWg sync.WaitGroup
+			for w := 0; w < numScannerWorkers; w++ {
+				workerWg.Add(1)
+				go func() {
+					defer workerWg.Done()
+					for s := range jobs {
+						// Initialize scanner with this subscription's config
+						err := s.Init(config)
+						if err != nil {
+							log.Fatal().Err(err).Msg("Failed to initialize scanner")
+						}
+
+						res, err := sc.retry(3, 10*time.Millisecond, s, &scanContext)
+						if err != nil {
+							cancel()
+							log.Fatal().Err(err).Msg("Failed to scan")
+						}
+						results <- res
+					}
+				}()
+			}
 
-			for _, s := range filteredServiceScanners {
-				err := s.Init(config)
-				if err != nil {
-					log.Fatal().Err(err).Msg("Failed to initialize scanner")
+			// Send scanner jobs to workers
+			go func() {
+				for _, s := range filteredServiceScanners {
+					jobs <- s
 				}
+				close(jobs)
+			}()
 
-				go func(s models.IAzureScanner) {
-					res, err := sc.retry(3, 10*time.Millisecond, s, &scanContext)
-					if err != nil {
-						cancel()
-						log.Fatal().Err(err).Msg("Failed to scan")
-					}
-					ch <- res
-				}(s)
-			}
+			// Wait for workers to finish and close results channel
+			go func() {
+				workerWg.Wait()
+				close(results)
+			}()
 
-			for i := 0; i < len(filteredServiceScanners); i++ {
-				res := <-ch
+			// Collect results from all scanners
+			for res := range results {
 				for _, r := range res {
 					// check if the resource is excluded
 					if filters.Azqr.IsServiceExcluded(r.ResourceID()) {
@@ -453,7 +481,9 @@ func (sc Scanner) Scan(params *ScanParams) string {
 	reportData.Advisor = append(reportData.Advisor, advisorScanner.Scan(ctx, params.Defender, cred, subscriptions, filters)...)
 
 	// scan Azure Policy
-	reportData.AzurePolicy = append(reportData.AzurePolicy, azurePolicyScanner.Scan(ctx, cred, subscriptions, filters)...)
+	if params.Policy {
+		reportData.AzurePolicy = append(reportData.AzurePolicy, azurePolicyScanner.Scan(ctx, cred, subscriptions, filters)...)
+	}
 
 	// scan Arc-enabled SQL Server
 	if params.Arc {
@@ -498,7 +528,7 @@ func (sc Scanner) Scan(params *ScanParams) string {
 }
 
 // retry retries the Azure scanner Scan, a number of times with an increasing delay between retries
-func (sc Scanner) retry(attempts int, sleep time.Duration, a models.IAzureScanner, scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (sc Scanner) retry(attempts int, sleep time.Duration, a models.IAzureScanner, scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	var err error
 	for i := 0; ; i++ {
 		res, err := a.Scan(scanContext)
@@ -507,7 +537,7 @@ func (sc Scanner) retry(attempts int, sleep time.Duration, a models.IAzureScanne
 		}
 
 		if models.ShouldSkipError(err) {
-			return []models.AzqrServiceResult{}, nil
+			return []*models.AzqrServiceResult{}, nil
 		}
 
 		errAsString := err.Error()
@@ -537,4 +567,3 @@ func (sc Scanner) generateOutputFileName(outputName string) string {
 	}
 	return outputFile
 }
-
diff --git a/internal/scanners/aa/aa.go b/internal/scanners/aa/aa.go
index 6d7babe..a531c63 100644
--- a/internal/scanners/aa/aa.go
+++ b/internal/scanners/aa/aa.go
@@ -23,9 +23,9 @@ func (a *AutomationAccountScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Automation Accounts in a Resource Group
-func (a *AutomationAccountScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AutomationAccountScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *AutomationAccountScanner) ResourceTypes() []string {
diff --git a/internal/scanners/adf/adf.go b/internal/scanners/adf/adf.go
index c2486f1..1dba4b5 100644
--- a/internal/scanners/adf/adf.go
+++ b/internal/scanners/adf/adf.go
@@ -28,7 +28,7 @@ func (a *DataFactoryScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Data Factories in a Resource Group
-func (a *DataFactoryScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *DataFactoryScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	factories, err := a.listFactories()
@@ -37,12 +37,12 @@ func (a *DataFactoryScanner) Scan(scanContext *models.ScanContext) ([]models.Azq
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range factories {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *DataFactoryScanner) listFactories() ([]*armdatafactory.Factory, error)
 	factories := make([]*armdatafactory.Factory, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/advisor.go b/internal/scanners/advisor.go
index 8261427..49e7f2f 100644
--- a/internal/scanners/advisor.go
+++ b/internal/scanners/advisor.go
@@ -16,9 +16,9 @@ import (
 // AdvisorScanner - Advisor scanner
 type AdvisorScanner struct{}
 
-func (s *AdvisorScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []models.AdvisorResult {
+func (s *AdvisorScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []*models.AdvisorResult {
 	models.LogResourceTypeScan("Advisor Recommendations")
-	resources := []models.AdvisorResult{}
+	resources := []*models.AdvisorResult{}
 
 	if scan {
 		graphClient := graph.NewGraphQuery(cred)
@@ -39,10 +39,11 @@ func (s *AdvisorScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenC
 		log.Debug().Msg(query)
 		subs := make([]*string, 0, len(subscriptions))
 		for s := range subscriptions {
-			subs = append(subs, &s)
+			subs = append(subs, to.Ptr(s))
 		}
 		result := graphClient.Query(ctx, query, subs)
-		resources = []models.AdvisorResult{}
+		resources = []*models.AdvisorResult{}
+		seen := make(map[string]bool) // Deduplication map
 		if result.Data != nil {
 			for _, row := range result.Data {
 				m := row.(map[string]interface{})
@@ -56,7 +57,7 @@ func (s *AdvisorScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenC
 					continue
 				}
 
-				resources = append(resources, models.AdvisorResult{
+				rec := &models.AdvisorResult{
 					SubscriptionID:   to.String(m["SubscriptionId"]),
 					SubscriptionName: to.String(m["SubscriptionName"]),
 					Name:             to.String(m["ImpactedValue"]),
@@ -66,7 +67,16 @@ func (s *AdvisorScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenC
 					Impact:           to.String(m["Impact"]),
 					Description:      to.String(m["Problem"]),
 					RecommendationID: to.String(m["RecommendationTypeId"]),
-				})
+				}
+
+				// Create unique key from ResourceID + RecommendationID
+				// This combination should uniquely identify an advisor recommendation
+				key := rec.ResourceID + "|" + rec.RecommendationID + "|" + rec.Category
+
+				if !seen[key] {
+					seen[key] = true
+					resources = append(resources, rec)
+				}
 			}
 		}
 	}
diff --git a/internal/scanners/afd/afd.go b/internal/scanners/afd/afd.go
index 97ecb4b..77c9fd0 100644
--- a/internal/scanners/afd/afd.go
+++ b/internal/scanners/afd/afd.go
@@ -28,7 +28,7 @@ func (a *FrontDoorScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Front Doors in a Resource Group
-func (a *FrontDoorScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *FrontDoorScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	gateways, err := a.list()
@@ -37,12 +37,12 @@ func (a *FrontDoorScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrS
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range gateways {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *FrontDoorScanner) list() ([]*armcdn.Profile, error) {
 	services := make([]*armcdn.Profile, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/afw/afw.go b/internal/scanners/afw/afw.go
index 4b83897..39c378b 100644
--- a/internal/scanners/afw/afw.go
+++ b/internal/scanners/afw/afw.go
@@ -28,7 +28,7 @@ func (a *FirewallScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Azure Firewall in a Resource Group
-func (a *FirewallScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *FirewallScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	gateways, err := a.list()
@@ -37,12 +37,12 @@ func (a *FirewallScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range gateways {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *FirewallScanner) list() ([]*armnetwork.AzureFirewall, error) {
 	services := make([]*armnetwork.AzureFirewall, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/agw/agw.go b/internal/scanners/agw/agw.go
index ea3e59a..fe16c29 100644
--- a/internal/scanners/agw/agw.go
+++ b/internal/scanners/agw/agw.go
@@ -28,7 +28,7 @@ func (a *ApplicationGatewayScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Application Gateways in a Resource Group
-func (a *ApplicationGatewayScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ApplicationGatewayScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	gateways, err := a.listGateways()
@@ -37,12 +37,12 @@ func (a *ApplicationGatewayScanner) Scan(scanContext *models.ScanContext) ([]mod
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range gateways {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -60,7 +60,7 @@ func (a *ApplicationGatewayScanner) listGateways() ([]*armnetwork.ApplicationGat
 	results := []*armnetwork.ApplicationGateway{}
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/aif/aif.go b/internal/scanners/aif/aif.go
index c6fc99d..8a3151a 100644
--- a/internal/scanners/aif/aif.go
+++ b/internal/scanners/aif/aif.go
@@ -28,7 +28,7 @@ func (a *AIFoundryScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Cognitive Services Accounts in a Resource Group
-func (c *AIFoundryScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *AIFoundryScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	services, err := c.list()
@@ -37,12 +37,12 @@ func (c *AIFoundryScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrS
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, service := range services {
 		rr := engine.EvaluateRecommendations(rules, service, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*service.ID),
@@ -61,7 +61,7 @@ func (c *AIFoundryScanner) list() ([]*armcognitiveservices.Account, error) {
 	namespaces := make([]*armcognitiveservices.Account, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/aks/aks.go b/internal/scanners/aks/aks.go
index 4b27b6d..7a6ad89 100644
--- a/internal/scanners/aks/aks.go
+++ b/internal/scanners/aks/aks.go
@@ -28,7 +28,7 @@ func (a *AKSScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all AKS Clusters in a Resource Group
-func (a *AKSScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AKSScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	clusters, err := a.listClusters()
@@ -37,13 +37,13 @@ func (a *AKSScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrService
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, c := range clusters {
 
 		rr := engine.EvaluateRecommendations(rules, c, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*c.ID),
@@ -63,7 +63,7 @@ func (a *AKSScanner) listClusters() ([]*armcontainerservice.ManagedCluster, erro
 	clusters := make([]*armcontainerservice.ManagedCluster, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/amg/amg.go b/internal/scanners/amg/amg.go
index bdb5006..e5028f3 100644
--- a/internal/scanners/amg/amg.go
+++ b/internal/scanners/amg/amg.go
@@ -28,7 +28,7 @@ func (a *ManagedGrafanaScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Managed Grafana in a Resource Group
-func (a *ManagedGrafanaScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ManagedGrafanaScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	workspaces, err := a.listWorkspaces()
@@ -37,12 +37,12 @@ func (a *ManagedGrafanaScanner) Scan(scanContext *models.ScanContext) ([]models.
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *ManagedGrafanaScanner) listWorkspaces() ([]*armdashboard.ManagedGrafana
 	workspaces := make([]*armdashboard.ManagedGrafana, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/apim/apim.go b/internal/scanners/apim/apim.go
index 858a89c..0f23155 100644
--- a/internal/scanners/apim/apim.go
+++ b/internal/scanners/apim/apim.go
@@ -28,7 +28,7 @@ func (a *APIManagementScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan -Scans all API Management Services in a Resource Group
-func (a *APIManagementScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *APIManagementScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	services, err := a.listServices()
@@ -37,12 +37,12 @@ func (a *APIManagementScanner) Scan(scanContext *models.ScanContext) ([]models.A
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, s := range services {
 		rr := engine.EvaluateRecommendations(rules, s, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*s.ID),
@@ -61,7 +61,7 @@ func (a *APIManagementScanner) listServices() ([]*armapimanagement.ServiceResour
 	services := make([]*armapimanagement.ServiceResource, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/appcs/appcs.go b/internal/scanners/appcs/appcs.go
index 607914a..672a690 100644
--- a/internal/scanners/appcs/appcs.go
+++ b/internal/scanners/appcs/appcs.go
@@ -28,7 +28,7 @@ func (a *AppConfigurationScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all App Configurations in a Resource Group
-func (a *AppConfigurationScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AppConfigurationScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	apps, err := a.list()
@@ -37,12 +37,12 @@ func (a *AppConfigurationScanner) Scan(scanContext *models.ScanContext) ([]model
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, app := range apps {
 		rr := engine.EvaluateRecommendations(rules, app, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*app.ID),
@@ -60,7 +60,7 @@ func (a *AppConfigurationScanner) list() ([]*armappconfiguration.ConfigurationSt
 	apps := make([]*armappconfiguration.ConfigurationStore, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/appi/appi.go b/internal/scanners/appi/appi.go
index 734d02b..a60bdd9 100644
--- a/internal/scanners/appi/appi.go
+++ b/internal/scanners/appi/appi.go
@@ -28,7 +28,7 @@ func (a *AppInsightsScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Application Insights in a Resource Group
-func (a *AppInsightsScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AppInsightsScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	gateways, err := a.list()
@@ -37,12 +37,12 @@ func (a *AppInsightsScanner) Scan(scanContext *models.ScanContext) ([]models.Azq
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range gateways {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *AppInsightsScanner) list() ([]*armapplicationinsights.Component, error)
 	services := make([]*armapplicationinsights.Component, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/arc/arc.go b/internal/scanners/arc/arc.go
index a16eccc..a975007 100644
--- a/internal/scanners/arc/arc.go
+++ b/internal/scanners/arc/arc.go
@@ -23,11 +23,11 @@ func (c *ArcScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Azure Arc-enabled machines in a Resource Group
-func (c *ArcScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *ArcScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 	// This scanner doesn't perform actual scans - it's here to register the resource type
 	// Actual Arc SQL scanning is done by the graph-based ArcSQLScanner
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 // ResourceTypes - Returns the resource types that this scanner covers
diff --git a/internal/scanners/arc_sql.go b/internal/scanners/arc_sql.go
index 55852e0..a9173d4 100644
--- a/internal/scanners/arc_sql.go
+++ b/internal/scanners/arc_sql.go
@@ -17,7 +17,7 @@ import (
 type ArcSQLScanner struct{}
 
 // Scan queries Azure Resource Graph for Arc-enabled machines with SQL Server discovered but without the SQL Server extension installed
-func (s *ArcSQLScanner) Scan(ctx context.Context, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []models.ArcSQLResult {
+func (s *ArcSQLScanner) Scan(ctx context.Context, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []*models.ArcSQLResult {
 	models.LogResourceTypeScan("Azure Arc-enabled SQL Server")
 
 	graphClient := graph.NewGraphQuery(cred)
@@ -53,10 +53,10 @@ func (s *ArcSQLScanner) Scan(ctx context.Context, cred azcore.TokenCredential, s
 	log.Debug().Msg(query)
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 	result := graphClient.Query(ctx, query, subs)
-	resources := []models.ArcSQLResult{}
+	resources := []*models.ArcSQLResult{}
 
 	if result.Data != nil {
 		for _, row := range result.Data {
@@ -77,7 +77,7 @@ func (s *ArcSQLScanner) Scan(ctx context.Context, cred azcore.TokenCredential, s
 				subscriptionName = ""
 			}
 
-			resources = append(resources, models.ArcSQLResult{
+			resources = append(resources, &models.ArcSQLResult{
 				SubscriptionID:   subscription,
 				SubscriptionName: subscriptionName,
 				Status:           to.String(m["status"]),
diff --git a/internal/scanners/as/as.go b/internal/scanners/as/as.go
index 5153420..3711b12 100644
--- a/internal/scanners/as/as.go
+++ b/internal/scanners/as/as.go
@@ -28,7 +28,7 @@ func (c *AnalysisServicesScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Analysis Services in a Resource Group
-func (c *AnalysisServicesScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *AnalysisServicesScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	workspaces, err := c.listWorkspaces()
@@ -37,12 +37,12 @@ func (c *AnalysisServicesScanner) Scan(scanContext *models.ScanContext) ([]model
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, ws := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, ws, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*ws.ID),
@@ -61,7 +61,7 @@ func (c *AnalysisServicesScanner) listWorkspaces() ([]*armanalysisservices.Serve
 	registries := make([]*armanalysisservices.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/asp/asp.go b/internal/scanners/asp/asp.go
index 86cb357..b970c49 100644
--- a/internal/scanners/asp/asp.go
+++ b/internal/scanners/asp/asp.go
@@ -38,7 +38,7 @@ func (a *AppServiceScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all App Service Plans in a Resource Group
-func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	plan, err := a.listPlans()
@@ -50,14 +50,14 @@ func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	appRules := a.getAppRules()
 	functionRules := a.getFunctionRules()
 	logicRules := a.getLogicRules()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, p := range plan {
 		rr := engine.EvaluateRecommendations(rules, p, scanContext)
 
 		resourceGroupName := models.GetResourceGroupFromResourceID(*p.ID)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    resourceGroupName,
@@ -79,14 +79,14 @@ func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 			}
 			scanContext.SiteConfig = &config
 
-			var result models.AzqrServiceResult
+			var result *models.AzqrServiceResult
 			// https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings
 			kind := strings.ToLower(*s.Kind)
 			switch kind {
 			case "functionapp,linux", "functionapp":
 				rr := engine.EvaluateRecommendations(functionRules, s, scanContext)
 
-				result = models.AzqrServiceResult{
+				result = &models.AzqrServiceResult{
 					SubscriptionID:   a.config.SubscriptionID,
 					SubscriptionName: a.config.SubscriptionName,
 					ResourceGroup:    resourceGroupName,
@@ -98,7 +98,7 @@ func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 			case "functionapp,workflowapp":
 				rr := engine.EvaluateRecommendations(logicRules, s, scanContext)
 
-				result = models.AzqrServiceResult{
+				result = &models.AzqrServiceResult{
 					SubscriptionID:   a.config.SubscriptionID,
 					SubscriptionName: a.config.SubscriptionName,
 					ResourceGroup:    resourceGroupName,
@@ -109,7 +109,7 @@ func (a *AppServiceScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 				}
 			default:
 				rr := engine.EvaluateRecommendations(appRules, s, scanContext)
-				result = models.AzqrServiceResult{
+				result = &models.AzqrServiceResult{
 					SubscriptionID:   a.config.SubscriptionID,
 					SubscriptionName: a.config.SubscriptionName,
 					ResourceGroup:    resourceGroupName,
@@ -132,7 +132,7 @@ func (a *AppServiceScanner) listPlans() ([]*armappservice.Plan, error) {
 	results := []*armappservice.Plan{}
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -148,7 +148,7 @@ func (a *AppServiceScanner) listSites(resourceGroupName string, plan string) ([]
 	results := []*armappservice.Site{}
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/avail/avail.go b/internal/scanners/avail/avail.go
index 53ac505..1075803 100644
--- a/internal/scanners/avail/avail.go
+++ b/internal/scanners/avail/avail.go
@@ -23,9 +23,9 @@ func (a *AvailabilitySetScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Availability Sets in a Resource Group
-func (a *AvailabilitySetScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AvailabilitySetScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *AvailabilitySetScanner) ResourceTypes() []string {
diff --git a/internal/scanners/avd/avd.go b/internal/scanners/avd/avd.go
index c42a291..b7ab00e 100644
--- a/internal/scanners/avd/avd.go
+++ b/internal/scanners/avd/avd.go
@@ -23,9 +23,9 @@ func (a *AzureVirtualDesktopScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all AVD in a Resource Group
-func (a *AzureVirtualDesktopScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AzureVirtualDesktopScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *AzureVirtualDesktopScanner) ResourceTypes() []string {
diff --git a/internal/scanners/avs/avs.go b/internal/scanners/avs/avs.go
index a39bd47..acc20d7 100644
--- a/internal/scanners/avs/avs.go
+++ b/internal/scanners/avs/avs.go
@@ -23,9 +23,9 @@ func (a *AVSScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all AVS in a Resource Group
-func (a *AVSScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AVSScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *AVSScanner) ResourceTypes() []string {
diff --git a/internal/scanners/azure_policy.go b/internal/scanners/azure_policy.go
index 4e697bc..68360bb 100644
--- a/internal/scanners/azure_policy.go
+++ b/internal/scanners/azure_policy.go
@@ -17,7 +17,7 @@ import (
 type AzurePolicyScanner struct{}
 
 // Scan queries Azure Resource Graph for non-compliant policy states across the specified subscriptions
-func (s *AzurePolicyScanner) Scan(ctx context.Context, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []models.AzurePolicyResult {
+func (s *AzurePolicyScanner) Scan(ctx context.Context, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []*models.AzurePolicyResult {
 	models.LogResourceTypeScan("Azure Policy (Non Compliant Resources)")
 
 	graphClient := graph.NewGraphQuery(cred)
@@ -51,10 +51,11 @@ func (s *AzurePolicyScanner) Scan(ctx context.Context, cred azcore.TokenCredenti
 	log.Debug().Msg(query)
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 	result := graphClient.Query(ctx, query, subs)
-	resources := []models.AzurePolicyResult{}
+	resources := []*models.AzurePolicyResult{}
+	seen := make(map[string]bool) // Deduplication map
 
 	if result.Data != nil {
 		for _, row := range result.Data {
@@ -69,7 +70,7 @@ func (s *AzurePolicyScanner) Scan(ctx context.Context, cred azcore.TokenCredenti
 				continue
 			}
 
-			resources = append(resources, models.AzurePolicyResult{
+			rec := &models.AzurePolicyResult{
 				SubscriptionID:       to.String(m["subscriptionId"]),
 				SubscriptionName:     to.String(m["subscriptionName"]),
 				Type:                 models.GetResourceTypeFromResourceID(resourceId),
@@ -84,7 +85,16 @@ func (s *AzurePolicyScanner) Scan(ctx context.Context, cred azcore.TokenCredenti
 				PolicyAssignmentName: to.String(m["policyAssignmentName"]),
 				PolicyAssignmentID:   to.String(m["policyAssignmentId"]),
 				ComplianceState:      to.String(m["complianceState"]),
-			})
+			}
+
+			// Create unique key from ResourceID + PolicyDefinitionID
+			// This combination should uniquely identify a policy compliance state
+			key := rec.ResourceID + "|" + rec.PolicyDefinitionID
+
+			if !seen[key] {
+				seen[key] = true
+				resources = append(resources, rec)
+			}
 		}
 	}
 
diff --git a/internal/scanners/ba/ba.go b/internal/scanners/ba/ba.go
index 408e742..86a7052 100644
--- a/internal/scanners/ba/ba.go
+++ b/internal/scanners/ba/ba.go
@@ -23,9 +23,9 @@ func (a *BatchAccountScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Batch Accounts in a Resource Group
-func (a *BatchAccountScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *BatchAccountScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *BatchAccountScanner) ResourceTypes() []string {
diff --git a/internal/scanners/ca/ca.go b/internal/scanners/ca/ca.go
index 58ac6b6..9b97744 100644
--- a/internal/scanners/ca/ca.go
+++ b/internal/scanners/ca/ca.go
@@ -28,7 +28,7 @@ func (a *ContainerAppsScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Container Apps in a Resource Group
-func (a *ContainerAppsScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ContainerAppsScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	apps, err := a.listApps()
@@ -37,12 +37,12 @@ func (a *ContainerAppsScanner) Scan(scanContext *models.ScanContext) ([]models.A
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, app := range apps {
 		rr := engine.EvaluateRecommendations(rules, app, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*app.ID),
@@ -60,7 +60,7 @@ func (a *ContainerAppsScanner) listApps() ([]*armappcontainers.ContainerApp, err
 	apps := make([]*armappcontainers.ContainerApp, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/cae/cae.go b/internal/scanners/cae/cae.go
index 237e941..073395a 100644
--- a/internal/scanners/cae/cae.go
+++ b/internal/scanners/cae/cae.go
@@ -28,7 +28,7 @@ func (a *ContainerAppsEnvironmentScanner) Init(config *models.ScannerConfig) err
 }
 
 // Scan - Scans all Container Apps in a Resource Group
-func (a *ContainerAppsEnvironmentScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ContainerAppsEnvironmentScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	apps, err := a.listApps()
@@ -37,12 +37,12 @@ func (a *ContainerAppsEnvironmentScanner) Scan(scanContext *models.ScanContext)
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, app := range apps {
 		rr := engine.EvaluateRecommendations(rules, app, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*app.ID),
@@ -60,7 +60,7 @@ func (a *ContainerAppsEnvironmentScanner) listApps() ([]*armappcontainers.Manage
 	apps := make([]*armappcontainers.ManagedEnvironment, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/ci/ci.go b/internal/scanners/ci/ci.go
index 868a377..3d5de9e 100644
--- a/internal/scanners/ci/ci.go
+++ b/internal/scanners/ci/ci.go
@@ -28,7 +28,7 @@ func (c *ContainerInstanceScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Container Instances in a Resource Group
-func (c *ContainerInstanceScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *ContainerInstanceScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	instances, err := c.listInstances()
@@ -37,12 +37,12 @@ func (c *ContainerInstanceScanner) Scan(scanContext *models.ScanContext) ([]mode
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, instance := range instances {
 		rr := engine.EvaluateRecommendations(rules, instance, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*instance.ID),
@@ -60,7 +60,7 @@ func (c *ContainerInstanceScanner) listInstances() ([]*armcontainerinstance.Cont
 	apps := make([]*armcontainerinstance.ContainerGroup, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/conn/conn.go b/internal/scanners/conn/conn.go
index fd4c320..725ff27 100644
--- a/internal/scanners/conn/conn.go
+++ b/internal/scanners/conn/conn.go
@@ -23,9 +23,9 @@ func (a *ConnectionScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Automation Accounts in a Resource Group
-func (a *ConnectionScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ConnectionScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *ConnectionScanner) ResourceTypes() []string {
diff --git a/internal/scanners/cosmos/cosmos.go b/internal/scanners/cosmos/cosmos.go
index 25f984e..bad3b19 100644
--- a/internal/scanners/cosmos/cosmos.go
+++ b/internal/scanners/cosmos/cosmos.go
@@ -28,7 +28,7 @@ func (a *CosmosDBScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all CosmosDB Databases in a Resource Group
-func (c *CosmosDBScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *CosmosDBScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	databases, err := c.listDatabases()
@@ -37,12 +37,12 @@ func (c *CosmosDBScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, database := range databases {
 		rr := engine.EvaluateRecommendations(rules, database, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*database.ID),
@@ -61,7 +61,7 @@ func (c *CosmosDBScanner) listDatabases() ([]*armcosmos.DatabaseAccountGetResult
 	domains := make([]*armcosmos.DatabaseAccountGetResults, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/cost.go b/internal/scanners/cost.go
index a3696c9..5ac308c 100644
--- a/internal/scanners/cost.go
+++ b/internal/scanners/cost.go
@@ -64,7 +64,7 @@ func (s *CostScanner) QueryCosts() (*models.CostResult, error) {
 	}
 
 	// Wait for a token from the burstLimiter channel before making the request
-	<-throttling.ARMLimiter
+	_ = throttling.WaitARM(s.config.Ctx); // nolint:errcheck
 	resp, err := s.client.Usage(s.config.Ctx, fmt.Sprintf("/subscriptions/%s", s.config.SubscriptionID), qd, nil)
 	if err != nil {
 		return nil, err
diff --git a/internal/scanners/cr/cr.go b/internal/scanners/cr/cr.go
index 05fc786..bb863ca 100644
--- a/internal/scanners/cr/cr.go
+++ b/internal/scanners/cr/cr.go
@@ -28,7 +28,7 @@ func (c *ContainerRegistryScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Container Registries in a Resource Group
-func (c *ContainerRegistryScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *ContainerRegistryScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	regsitries, err := c.listRegistries()
@@ -37,12 +37,12 @@ func (c *ContainerRegistryScanner) Scan(scanContext *models.ScanContext) ([]mode
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, registry := range regsitries {
 		rr := engine.EvaluateRecommendations(rules, registry, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*registry.ID),
@@ -61,7 +61,7 @@ func (c *ContainerRegistryScanner) listRegistries() ([]*armcontainerregistry.Reg
 	registries := make([]*armcontainerregistry.Registry, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/dbw/dbw.go b/internal/scanners/dbw/dbw.go
index 6c6add8..f7aa36d 100644
--- a/internal/scanners/dbw/dbw.go
+++ b/internal/scanners/dbw/dbw.go
@@ -28,7 +28,7 @@ func (c *DatabricksScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Azure Databricks in a Resource Group
-func (c *DatabricksScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *DatabricksScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	workspaces, err := c.listWorkspaces()
@@ -37,12 +37,12 @@ func (c *DatabricksScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, ws := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, ws, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*ws.ID),
@@ -61,7 +61,7 @@ func (c *DatabricksScanner) listWorkspaces() ([]*armdatabricks.Workspace, error)
 	registries := make([]*armdatabricks.Workspace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/dec/dec.go b/internal/scanners/dec/dec.go
index 83f0dea..6bcaa39 100644
--- a/internal/scanners/dec/dec.go
+++ b/internal/scanners/dec/dec.go
@@ -28,7 +28,7 @@ func (a *DataExplorerScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Data Explorers in a Resource Group
-func (a *DataExplorerScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *DataExplorerScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	kustoclusters, err := a.listClusters()
@@ -37,12 +37,12 @@ func (a *DataExplorerScanner) Scan(scanContext *models.ScanContext) ([]models.Az
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range kustoclusters {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *DataExplorerScanner) listClusters() ([]*armkusto.Cluster, error) {
 	kustoclusters := make([]*armkusto.Cluster, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/defender.go b/internal/scanners/defender.go
index a154d92..3eee7cb 100644
--- a/internal/scanners/defender.go
+++ b/internal/scanners/defender.go
@@ -17,9 +17,9 @@ import (
 // DefenderScanner - Defender scanner
 type DefenderScanner struct{}
 
-func (s *DefenderScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []models.DefenderResult {
+func (s *DefenderScanner) Scan(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []*models.DefenderResult {
 	models.LogResourceTypeScan("Defender Status")
-	resources := []models.DefenderResult{}
+	resources := []*models.DefenderResult{}
 
 	if scan {
 		graphClient := graph.NewGraphQuery(cred)
@@ -36,10 +36,10 @@ func (s *DefenderScanner) Scan(ctx context.Context, scan bool, cred azcore.Token
 		log.Debug().Msg(query)
 		subs := make([]*string, 0, len(subscriptions))
 		for s := range subscriptions {
-			subs = append(subs, &s)
+			subs = append(subs, to.Ptr(s))
 		}
 		result := graphClient.Query(ctx, query, subs)
-		resources = []models.DefenderResult{}
+		resources = []*models.DefenderResult{}
 		if result.Data != nil {
 			for _, row := range result.Data {
 				m := row.(map[string]interface{})
@@ -48,7 +48,7 @@ func (s *DefenderScanner) Scan(ctx context.Context, scan bool, cred azcore.Token
 					continue
 				}
 
-				resources = append(resources, models.DefenderResult{
+				resources = append(resources, &models.DefenderResult{
 					SubscriptionID:   to.String(m["SubscriptionId"]),
 					SubscriptionName: to.String(m["SubscriptionName"]),
 					Name:             to.String(m["Name"]),
@@ -60,9 +60,9 @@ func (s *DefenderScanner) Scan(ctx context.Context, scan bool, cred azcore.Token
 	return resources
 }
 
-func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []models.DefenderRecommendation {
+func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cred azcore.TokenCredential, subscriptions map[string]string, filters *models.Filters) []*models.DefenderRecommendation {
 	models.LogResourceTypeScan("Defender Recommendations")
-	resources := []models.DefenderRecommendation{}
+	resources := []*models.DefenderRecommendation{}
 
 	if scan {
 		graphClient := graph.NewGraphQuery(cred)
@@ -74,18 +74,19 @@ func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cre
 		| extend
 			AssessmentId = id,
 			AssessmentKey = name,
-			ResourceId = properties.resourceDetails.Id,
+			ResourceId = tostring(properties.resourceDetails.Id),
 			ResourceIdsplit = split(properties.resourceDetails.Id, '/'),
-			RecommendationName = properties.displayName,
-			RecommendationState = properties.status.code,
-			ActionDescription = properties.metadata.description,
-			RemediationDescription = properties.metadata.remediationDescription,
-			RecommendationSeverity = properties.metadata.severity,
+			RecommendationName = tostring(properties.displayName),
+			RecommendationState = tostring(properties.status.code),
+			ActionDescription = tostring(properties.metadata.description),
+			RemediationDescription = tostring(properties.metadata.remediationDescription),
+			RecommendationSeverity = tostring(properties.metadata.severity),
 			PolicyDefinitionId = properties.metadata.policyDefinitionId,
 			AssessmentType = properties.metadata.assessmentType,
 			Threats = properties.metadata.threats,
 			UserImpact = properties.metadata.userImpact,
-			AzPortalLink = tostring(properties.links.azurePortal)
+			AzPortalLink = tostring(properties.links.azurePortal),
+			CategoryString = tostring(Category)
 		| extend
 			ResourceSubId = tostring(ResourceIdsplit[2]),
 			ResourceGroupName = tostring(ResourceIdsplit[4]),
@@ -95,16 +96,18 @@ func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cre
 			| where type == 'microsoft.resources/subscriptions'
 			| project SubscriptionName = name, subscriptionId) on subscriptionId
 		| project SubscriptionId=subscriptionId, SubscriptionName, ResourceGroupName, ResourceType,
-			ResourceName, Category, RecommendationSeverity, RecommendationName, ActionDescription,
+			ResourceName, Category=CategoryString, RecommendationSeverity, RecommendationName, ActionDescription,
 			RemediationDescription, AzPortalLink, ResourceId
+		| distinct SubscriptionId, SubscriptionName, ResourceGroupName, ResourceType, ResourceName, Category, RecommendationSeverity, RecommendationName, ActionDescription, RemediationDescription, AzPortalLink, ResourceId
 	`
 		log.Debug().Msg(query)
 		subs := make([]*string, 0, len(subscriptions))
 		for s := range subscriptions {
-			subs = append(subs, &s)
+			subs = append(subs, to.Ptr(s))
 		}
 		result := graphClient.Query(ctx, query, subs)
-		resources = []models.DefenderRecommendation{}
+		resources = []*models.DefenderRecommendation{}
+		seen := make(map[string]bool) // Deduplication map
 		if result.Data != nil {
 			for _, row := range result.Data {
 				m := row.(map[string]interface{})
@@ -113,7 +116,8 @@ func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cre
 					continue
 				}
 
-				resources = append(resources, models.DefenderRecommendation{
+				// Create a unique key for deduplication based on all fields
+				rec := &models.DefenderRecommendation{
 					SubscriptionId:         to.String(m["SubscriptionId"]),
 					SubscriptionName:       to.String(m["SubscriptionName"]),
 					ResourceGroupName:      to.String(m["ResourceGroupName"]),
@@ -126,7 +130,17 @@ func (s *DefenderScanner) GetRecommendations(ctx context.Context, scan bool, cre
 					RemediationDescription: to.String(m["RemediationDescription"]),
 					AzPortalLink:           fmt.Sprintf("https://%s", to.String(m["AzPortalLink"])),
 					ResourceId:             to.String(m["ResourceId"]),
-				})
+				}
+
+				// Create unique key from ResourceId + Category + RecommendationName
+				// This combination should uniquely identify a defender recommendation
+				key := fmt.Sprintf("%s|%s|%s", rec.ResourceId, rec.Category, rec.RecommendationName)
+
+				if !seen[key] {
+					seen[key] = true
+					// Create a unique key for deduplication based on all fields
+					resources = append(resources, rec)
+				}
 			}
 		}
 	}
diff --git a/internal/scanners/diagnostics_settings.go b/internal/scanners/diagnostics_settings.go
index 7f22e23..f6205bb 100644
--- a/internal/scanners/diagnostics_settings.go
+++ b/internal/scanners/diagnostics_settings.go
@@ -109,7 +109,6 @@ type DiagnosticSettingsScanner struct {
 
 const (
 	bucketCapacity = 250
-	refillRate     = 25
 )
 
 // Init - Initializes the DiagnosticSettingsScanner
@@ -298,7 +297,7 @@ func (d *DiagnosticSettingsScanner) doRequest(ctx context.Context, resourceIds [
 	req.Header.Set("Authorization", "Bearer "+d.accessToken)
 
 	// Wait for a token from the burstLimiter channel before making the request
-	<-throttling.ARMLimiter
+	_ = throttling.WaitARM(ctx); // nolint:errcheck
 
 	// Send the HTTP request.
 	resp, err := d.httpClient.Do(req)
diff --git a/internal/scanners/disk/disk.go b/internal/scanners/disk/disk.go
index 948dab9..58aca89 100644
--- a/internal/scanners/disk/disk.go
+++ b/internal/scanners/disk/disk.go
@@ -23,9 +23,9 @@ func (a *DiskScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Disk in a Resource Group
-func (a *DiskScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *DiskScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *DiskScanner) ResourceTypes() []string {
diff --git a/internal/scanners/erc/erc.go b/internal/scanners/erc/erc.go
index 5a2ed54..a767a1f 100644
--- a/internal/scanners/erc/erc.go
+++ b/internal/scanners/erc/erc.go
@@ -23,9 +23,9 @@ func (a *ExpressRouteScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Express Routes in a Resource Group
-func (a *ExpressRouteScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *ExpressRouteScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *ExpressRouteScanner) ResourceTypes() []string {
diff --git a/internal/scanners/evgd/evgd.go b/internal/scanners/evgd/evgd.go
index 3273c58..788a1a2 100644
--- a/internal/scanners/evgd/evgd.go
+++ b/internal/scanners/evgd/evgd.go
@@ -28,7 +28,7 @@ func (a *EventGridScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all EventGrid Domains in a Resource Group
-func (a *EventGridScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *EventGridScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	domains, err := a.listDomain()
@@ -37,12 +37,12 @@ func (a *EventGridScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrS
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, d := range domains {
 		rr := engine.EvaluateRecommendations(rules, d, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*d.ID),
@@ -61,7 +61,7 @@ func (a *EventGridScanner) listDomain() ([]*armeventgrid.Domain, error) {
 	domains := make([]*armeventgrid.Domain, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/evh/evh.go b/internal/scanners/evh/evh.go
index 0a49653..1a82015 100644
--- a/internal/scanners/evh/evh.go
+++ b/internal/scanners/evh/evh.go
@@ -28,7 +28,7 @@ func (a *EventHubScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Event Hubs in a Resource Group
-func (c *EventHubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *EventHubScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	eventHubs, err := c.listEventHubs()
@@ -37,12 +37,12 @@ func (c *EventHubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, eventHub := range eventHubs {
 		rr := engine.EvaluateRecommendations(rules, eventHub, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*eventHub.ID),
@@ -61,7 +61,7 @@ func (c *EventHubScanner) listEventHubs() ([]*armeventhub.EHNamespace, error) {
 	namespaces := make([]*armeventhub.EHNamespace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/fdfp/fdfp.go b/internal/scanners/fdfp/fdfp.go
index 5bfb2b1..8824b6d 100644
--- a/internal/scanners/fdfp/fdfp.go
+++ b/internal/scanners/fdfp/fdfp.go
@@ -23,9 +23,9 @@ func (a *FrontDoorWAFPolicyScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Front Door Web Application Policy in a Resource Group
-func (a *FrontDoorWAFPolicyScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *FrontDoorWAFPolicyScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *FrontDoorWAFPolicyScanner) ResourceTypes() []string {
diff --git a/internal/scanners/gal/gal.go b/internal/scanners/gal/gal.go
index 8e43284..db2fd3c 100644
--- a/internal/scanners/gal/gal.go
+++ b/internal/scanners/gal/gal.go
@@ -23,9 +23,9 @@ func (a *GalleryScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Galleries in a Resource Group
-func (a *GalleryScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *GalleryScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *GalleryScanner) ResourceTypes() []string {
diff --git a/internal/scanners/hpc/hpc.go b/internal/scanners/hpc/hpc.go
index 14f1871..1932fe4 100644
--- a/internal/scanners/hpc/hpc.go
+++ b/internal/scanners/hpc/hpc.go
@@ -23,9 +23,9 @@ func (a *HighPerformanceComputingScanner) Init(config *models.ScannerConfig) err
 }
 
 // Scan - Scans all HPC in a Resource Group
-func (a *HighPerformanceComputingScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *HighPerformanceComputingScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *HighPerformanceComputingScanner) ResourceTypes() []string {
diff --git a/internal/scanners/hub/hub.go b/internal/scanners/hub/hub.go
index 680b280..b362c55 100644
--- a/internal/scanners/hub/hub.go
+++ b/internal/scanners/hub/hub.go
@@ -28,7 +28,7 @@ func (a *AIFoundryHubScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all AI Foundry Hubs
-func (a *AIFoundryHubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AIFoundryHubScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	workspaces, err := a.listWorkspaces()
@@ -37,12 +37,12 @@ func (a *AIFoundryHubScanner) Scan(scanContext *models.ScanContext) ([]models.Az
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *AIFoundryHubScanner) listWorkspaces() ([]*armmachinelearning.Workspace,
 	workspaces := make([]*armmachinelearning.Workspace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/iot/iot.go b/internal/scanners/iot/iot.go
index 599412c..fe45428 100644
--- a/internal/scanners/iot/iot.go
+++ b/internal/scanners/iot/iot.go
@@ -23,9 +23,9 @@ func (a *IoTHubScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all IoT Hub in a Resource Group
-func (a *IoTHubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *IoTHubScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *IoTHubScanner) ResourceTypes() []string {
diff --git a/internal/scanners/it/it.go b/internal/scanners/it/it.go
index 6abd364..3c40143 100644
--- a/internal/scanners/it/it.go
+++ b/internal/scanners/it/it.go
@@ -28,7 +28,7 @@ func (a *ImageTemplateScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Image Template in a Resource Group
-func (c *ImageTemplateScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *ImageTemplateScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *ImageTemplateScanner) Scan(scanContext *models.ScanContext) ([]models.A
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *ImageTemplateScanner) list() ([]*armvirtualmachineimagebuilder.ImageTem
 	svcs := make([]*armvirtualmachineimagebuilder.ImageTemplate, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx) // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/kv/kv.go b/internal/scanners/kv/kv.go
index 08ffbd9..16608ac 100644
--- a/internal/scanners/kv/kv.go
+++ b/internal/scanners/kv/kv.go
@@ -28,7 +28,7 @@ func (c *KeyVaultScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Key Vaults in a Resource Group
-func (c *KeyVaultScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *KeyVaultScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vaults, err := c.listVaults()
@@ -37,12 +37,12 @@ func (c *KeyVaultScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, vault := range vaults {
 		rr := engine.EvaluateRecommendations(rules, vault, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*vault.ID),
@@ -61,7 +61,7 @@ func (c *KeyVaultScanner) listVaults() ([]*armkeyvault.Vault, error) {
 	vaults := make([]*armkeyvault.Vault, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/lb/lb.go b/internal/scanners/lb/lb.go
index 0f9cd84..189f554 100644
--- a/internal/scanners/lb/lb.go
+++ b/internal/scanners/lb/lb.go
@@ -28,7 +28,7 @@ func (c *LoadBalancerScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Loadbalancer in a Resource Group
-func (c *LoadBalancerScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *LoadBalancerScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	lbs, err := c.list()
@@ -37,12 +37,12 @@ func (c *LoadBalancerScanner) Scan(scanContext *models.ScanContext) ([]models.Az
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range lbs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *LoadBalancerScanner) list() ([]*armnetwork.LoadBalancer, error) {
 	lbs := make([]*armnetwork.LoadBalancer, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/log/log.go b/internal/scanners/log/log.go
index e2d6f9d..2a72816 100644
--- a/internal/scanners/log/log.go
+++ b/internal/scanners/log/log.go
@@ -28,7 +28,7 @@ func (a *LogAnalyticsScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Log Analytics workspace in a Resource Group
-func (c *LogAnalyticsScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *LogAnalyticsScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *LogAnalyticsScanner) Scan(scanContext *models.ScanContext) ([]models.Az
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *LogAnalyticsScanner) list() ([]*armoperationalinsights.Workspace, error
 	svcs := make([]*armoperationalinsights.Workspace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/logic/logic.go b/internal/scanners/logic/logic.go
index c346515..fb2b683 100644
--- a/internal/scanners/logic/logic.go
+++ b/internal/scanners/logic/logic.go
@@ -28,7 +28,7 @@ func (c *LogicAppScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all LogicApps in a Resource Group
-func (c *LogicAppScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *LogicAppScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vnets, err := c.list()
@@ -37,12 +37,12 @@ func (c *LogicAppScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vnets {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *LogicAppScanner) list() ([]*armlogic.Workflow, error) {
 	logicApps := make([]*armlogic.Workflow, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/management_groups.go b/internal/scanners/management_groups.go
index cd0f00c..0121b4b 100644
--- a/internal/scanners/management_groups.go
+++ b/internal/scanners/management_groups.go
@@ -28,7 +28,7 @@ func (sc ManagementGroupsScanner) ListSubscriptions(ctx context.Context, cred az
 		subscriptions := make([]*armmanagementgroups.SubscriptionUnderManagementGroup, 0)
 		for resultPager.More() {
 			// Wait for a token from the burstLimiter channel before making the request
-			<-throttling.ARMLimiter
+			_ = throttling.WaitARM(ctx) // nolint:errcheck
 			pageResp, err := resultPager.NextPage(ctx)
 			if err != nil {
 				log.Fatal().Err(err).Msg("Failed to list management group subscriptions")
@@ -56,7 +56,7 @@ func (sc ManagementGroupsScanner) ListSubscriptions(ctx context.Context, cred az
 		decendantsPager := client.NewClient().NewGetDescendantsPager(group, nil)
 		for decendantsPager.More() {
 			// Wait for a token from the burstLimiter channel before making the request
-			<-throttling.ARMLimiter
+			_ = throttling.WaitARM(ctx) // nolint:errcheck
 			pageResp, err := decendantsPager.NextPage(ctx)
 			if err != nil {
 				log.Fatal().Err(err).Msg("Failed to list management group descendants")
diff --git a/internal/scanners/maria/maria.go b/internal/scanners/maria/maria.go
index 67eda98..cab81ad 100644
--- a/internal/scanners/maria/maria.go
+++ b/internal/scanners/maria/maria.go
@@ -36,7 +36,7 @@ func (c *MariaScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all MariaDB servers in a Resource Group
-func (c *MariaScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *MariaScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	servers, err := c.listServers()
@@ -46,14 +46,14 @@ func (c *MariaScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServi
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
 	databaseRules := c.GetDatabaseRules()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, server := range servers {
 		rr := engine.EvaluateRecommendations(rules, server, scanContext)
 
 		resourceGroupName := models.GetResourceGroupFromResourceID(*server.ID)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    resourceGroupName,
@@ -70,7 +70,7 @@ func (c *MariaScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServi
 		for _, database := range databases {
 			rr := engine.EvaluateRecommendations(databaseRules, database, scanContext)
 
-			results = append(results, models.AzqrServiceResult{
+			results = append(results, &models.AzqrServiceResult{
 				SubscriptionID:  c.config.SubscriptionID,
 				ResourceGroup:   resourceGroupName,
 				ServiceName:     *database.Name,
@@ -89,7 +89,7 @@ func (c *MariaScanner) listServers() ([]*armmariadb.Server, error) {
 	servers := make([]*armmariadb.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -105,7 +105,7 @@ func (c *MariaScanner) listDatabases(resourceGroupName, serverName string) ([]*a
 	databases := make([]*armmariadb.Database, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/mysql/mysql.go b/internal/scanners/mysql/mysql.go
index 1c1d038..bb52a0e 100644
--- a/internal/scanners/mysql/mysql.go
+++ b/internal/scanners/mysql/mysql.go
@@ -28,7 +28,7 @@ func (c *MySQLScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all MySQL in a Resource Group
-func (c *MySQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *MySQLScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	postgre, err := c.listMySQL()
@@ -37,12 +37,12 @@ func (c *MySQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServi
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, postgre := range postgre {
 		rr := engine.EvaluateRecommendations(rules, postgre, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*postgre.ID),
@@ -62,7 +62,7 @@ func (c *MySQLScanner) listMySQL() ([]*armmysql.Server, error) {
 	servers := make([]*armmysql.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/mysql/mysqlf.go b/internal/scanners/mysql/mysqlf.go
index ecfd68f..ff2aecd 100644
--- a/internal/scanners/mysql/mysqlf.go
+++ b/internal/scanners/mysql/mysqlf.go
@@ -24,7 +24,7 @@ func (c *MySQLFlexibleScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all MySQL in a Resource Group
-func (c *MySQLFlexibleScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *MySQLFlexibleScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	flexibles, err := c.list()
@@ -33,12 +33,12 @@ func (c *MySQLFlexibleScanner) Scan(scanContext *models.ScanContext) ([]models.A
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, postgre := range flexibles {
 		rr := engine.EvaluateRecommendations(rules, postgre, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*postgre.ID),
 			SubscriptionName: c.config.SubscriptionName,
@@ -57,7 +57,7 @@ func (c *MySQLFlexibleScanner) list() ([]*armmysqlflexibleservers.Server, error)
 	servers := make([]*armmysqlflexibleservers.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/netapp/netapp.go b/internal/scanners/netapp/netapp.go
index 7e4f273..e58ed20 100644
--- a/internal/scanners/netapp/netapp.go
+++ b/internal/scanners/netapp/netapp.go
@@ -23,9 +23,9 @@ func (a *NetAppScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all NetApp in a Resource Group
-func (a *NetAppScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *NetAppScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *NetAppScanner) ResourceTypes() []string {
diff --git a/internal/scanners/ng/ng.go b/internal/scanners/ng/ng.go
index f5a5337..6dc1022 100644
--- a/internal/scanners/ng/ng.go
+++ b/internal/scanners/ng/ng.go
@@ -28,7 +28,7 @@ func (a *NatGatewayScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all NAT Gateway in a Resource Group
-func (c *NatGatewayScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *NatGatewayScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *NatGatewayScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *NatGatewayScanner) list() ([]*armnetwork.NatGateway, error) {
 	svcs := make([]*armnetwork.NatGateway, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/nic/nic.go b/internal/scanners/nic/nic.go
index f605c25..1a15be7 100644
--- a/internal/scanners/nic/nic.go
+++ b/internal/scanners/nic/nic.go
@@ -23,9 +23,9 @@ func (a *NICScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all NICs
-func (a *NICScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *NICScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *NICScanner) ResourceTypes() []string {
diff --git a/internal/scanners/nsg/nsg.go b/internal/scanners/nsg/nsg.go
index b781de9..9e42366 100644
--- a/internal/scanners/nsg/nsg.go
+++ b/internal/scanners/nsg/nsg.go
@@ -28,7 +28,7 @@ func (a *NSGScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all NSG in a Resource Group
-func (c *NSGScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *NSGScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *NSGScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrService
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *NSGScanner) list() ([]*armnetwork.SecurityGroup, error) {
 	svcs := make([]*armnetwork.SecurityGroup, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/nw/nw.go b/internal/scanners/nw/nw.go
index e8e956f..c36adfd 100644
--- a/internal/scanners/nw/nw.go
+++ b/internal/scanners/nw/nw.go
@@ -28,7 +28,7 @@ func (a *NetworkWatcherScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Network Watcher in a Resource Group
-func (c *NetworkWatcherScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *NetworkWatcherScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *NetworkWatcherScanner) Scan(scanContext *models.ScanContext) ([]models.
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *NetworkWatcherScanner) list() ([]*armnetwork.Watcher, error) {
 	svcs := make([]*armnetwork.Watcher, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/odb/odb.go b/internal/scanners/odb/odb.go
new file mode 100644
index 0000000..dc8b17f
--- /dev/null
+++ b/internal/scanners/odb/odb.go
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package odb
+
+import (
+	"github.com/Azure/azqr/internal/models"
+)
+
+func init() {
+	models.ScannerList["odb"] = []models.IAzureScanner{&OracleDatabaseScanner{}}
+}
+
+// OracleDatabaseScanner - Scanner for Oracle Database@Azure
+type OracleDatabaseScanner struct {
+	config *models.ScannerConfig
+}
+
+// Init - Initializes the Oracle Database Scanner
+func (a *OracleDatabaseScanner) Init(config *models.ScannerConfig) error {
+	a.config = config
+	return nil
+}
+
+// Scan - Scans all Oracle Database@Azure resources in a Resource Group
+func (a *OracleDatabaseScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
+	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
+	return []*models.AzqrServiceResult{}, nil
+}
+
+func (a *OracleDatabaseScanner) ResourceTypes() []string {
+	return []string{
+		"Oracle.Database/cloudExadataInfrastructures",
+		"Oracle.Database/cloudVmClusters",
+	}
+}
+
+func (a *OracleDatabaseScanner) GetRecommendations() map[string]models.AzqrRecommendation {
+	return map[string]models.AzqrRecommendation{}
+}
diff --git a/internal/scanners/pdnsz/pdnsz.go b/internal/scanners/pdnsz/pdnsz.go
index f8619db..676a6dc 100644
--- a/internal/scanners/pdnsz/pdnsz.go
+++ b/internal/scanners/pdnsz/pdnsz.go
@@ -23,9 +23,9 @@ func (a *PrivateDNSZoneScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Private DNS Zone in a Resource Group
-func (a *PrivateDNSZoneScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *PrivateDNSZoneScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *PrivateDNSZoneScanner) ResourceTypes() []string {
diff --git a/internal/scanners/pep.go b/internal/scanners/pep.go
index 2fe7839..c803530 100644
--- a/internal/scanners/pep.go
+++ b/internal/scanners/pep.go
@@ -40,7 +40,7 @@ func (s *PrivateEndpointScanner) ListResourcesWithPrivateEndpoints() (map[string
 
 		for pager.More() {
 			// Wait for a token from the burstLimiter channel before making the request
-			<-throttling.ARMLimiter
+			_ = throttling.WaitARM(s.config.Ctx); // nolint:errcheck
 			resp, err := pager.NextPage(s.config.Ctx)
 			if err != nil {
 				return nil, err
diff --git a/internal/scanners/pep/pep.go b/internal/scanners/pep/pep.go
index f6a63b5..3d9768f 100644
--- a/internal/scanners/pep/pep.go
+++ b/internal/scanners/pep/pep.go
@@ -28,7 +28,7 @@ func (a *PrivateEndpointScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Private Endpoint in a Resource Group
-func (c *PrivateEndpointScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *PrivateEndpointScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *PrivateEndpointScanner) Scan(scanContext *models.ScanContext) ([]models
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *PrivateEndpointScanner) list() ([]*armnetwork.PrivateEndpoint, error) {
 	svcs := make([]*armnetwork.PrivateEndpoint, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/pip.go b/internal/scanners/pip.go
index 3f79b4c..7b53f6a 100644
--- a/internal/scanners/pip.go
+++ b/internal/scanners/pip.go
@@ -38,7 +38,7 @@ func (s *PublicIPScanner) ListPublicIPs() (map[string]*armnetwork.PublicIPAddres
 
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(s.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(s.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/pip/pip.go b/internal/scanners/pip/pip.go
index ba328b8..ee11e23 100644
--- a/internal/scanners/pip/pip.go
+++ b/internal/scanners/pip/pip.go
@@ -28,7 +28,7 @@ func (a *PublicIPScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Public IP in a Resource Group
-func (c *PublicIPScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *PublicIPScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *PublicIPScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *PublicIPScanner) list() ([]*armnetwork.PublicIPAddress, error) {
 	svcs := make([]*armnetwork.PublicIPAddress, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/plugins/carbon/emissions.go b/internal/scanners/plugins/carbon/emissions.go
index f3b7276..36a159a 100644
--- a/internal/scanners/plugins/carbon/emissions.go
+++ b/internal/scanners/plugins/carbon/emissions.go
@@ -121,7 +121,7 @@ func (s *EmissionsScanner) Scan(ctx context.Context, cred azcore.TokenCredential
 		}
 
 		// Wait for rate limiter
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(ctx) // nolint:errcheck
 
 		// Execute the request
 		resp, err := clientFactory.NewCarbonServiceClient().QueryCarbonEmissionReports(ctx, queryFilter, nil)
diff --git a/internal/scanners/plugins/openai/throttling.go b/internal/scanners/plugins/openai/throttling.go
index e924caa..6d6cd7f 100644
--- a/internal/scanners/plugins/openai/throttling.go
+++ b/internal/scanners/plugins/openai/throttling.go
@@ -113,7 +113,7 @@ func (s *ThrottlingScanner) scanSubscription(ctx context.Context, cred azcore.To
 
 	for pager.More() {
 		// Wait for rate limiter
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(ctx); // nolint:errcheck
 
 		page, err := pager.NextPage(ctx)
 		if err != nil {
@@ -222,7 +222,7 @@ func (s *ThrottlingScanner) getMetricsWithStatusCodeSplit(ctx context.Context, c
 	// Use wildcard filter to get all status codes and model names
 	filter := "StatusCode eq '*' and ModelDeploymentName eq '*' and ModelName eq '*'"
 
-	<-throttling.ARMLimiter
+	_ = throttling.WaitARM(ctx); // nolint:errcheck
 
 	result, err := client.List(ctx, resourceID, &armmonitor.MetricsClientListOptions{
 		Timespan:    &timespan,
@@ -300,7 +300,7 @@ func (s *ThrottlingScanner) getDeployments(ctx context.Context, client *armcogni
 
 	for pager.More() {
 		// Wait for rate limiter
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(ctx); // nolint:errcheck
 
 		page, err := pager.NextPage(ctx)
 		if err != nil {
diff --git a/internal/scanners/psql/psql.go b/internal/scanners/psql/psql.go
index df52c2c..dcc07e2 100644
--- a/internal/scanners/psql/psql.go
+++ b/internal/scanners/psql/psql.go
@@ -28,7 +28,7 @@ func (c *PostgreScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all PostgreSQL in a Resource Group
-func (c *PostgreScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *PostgreScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	postgre, err := c.listPostgre()
@@ -37,12 +37,12 @@ func (c *PostgreScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSer
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, postgre := range postgre {
 		rr := engine.EvaluateRecommendations(rules, postgre, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*postgre.ID),
@@ -62,7 +62,7 @@ func (c *PostgreScanner) listPostgre() ([]*armpostgresql.Server, error) {
 	servers := make([]*armpostgresql.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/psql/psqlf.go b/internal/scanners/psql/psqlf.go
index 00518d2..c925c3b 100644
--- a/internal/scanners/psql/psqlf.go
+++ b/internal/scanners/psql/psqlf.go
@@ -24,7 +24,7 @@ func (c *PostgreFlexibleScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all PostgreSQL in a Resource Group
-func (c *PostgreFlexibleScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *PostgreFlexibleScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	flexibles, err := c.listFlexiblePostgre()
@@ -33,12 +33,12 @@ func (c *PostgreFlexibleScanner) Scan(scanContext *models.ScanContext) ([]models
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, postgre := range flexibles {
 		rr := engine.EvaluateRecommendations(rules, postgre, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*postgre.ID),
@@ -58,7 +58,7 @@ func (c *PostgreFlexibleScanner) listFlexiblePostgre() ([]*armpostgresqlflexible
 	servers := make([]*armpostgresqlflexibleservers.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/redis/redis.go b/internal/scanners/redis/redis.go
index a8a8c32..a774d23 100644
--- a/internal/scanners/redis/redis.go
+++ b/internal/scanners/redis/redis.go
@@ -28,7 +28,7 @@ func (c *RedisScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Redis in a Resource Group
-func (c *RedisScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *RedisScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	redis, err := c.listRedis()
@@ -37,12 +37,12 @@ func (c *RedisScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServi
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, redis := range redis {
 		rr := engine.EvaluateRecommendations(rules, redis, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*redis.ID),
@@ -61,7 +61,7 @@ func (c *RedisScanner) listRedis() ([]*armredis.ResourceInfo, error) {
 	redis := make([]*armredis.ResourceInfo, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/resources.go b/internal/scanners/resources.go
index 4d91a83..261f740 100644
--- a/internal/scanners/resources.go
+++ b/internal/scanners/resources.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/Azure/azqr/internal/graph"
 	"github.com/Azure/azqr/internal/models"
+	"github.com/Azure/azqr/internal/to"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/rs/zerolog/log"
 )
@@ -16,11 +17,11 @@ func (sc ResourceScanner) GetAllResources(ctx context.Context, cred azcore.Token
 	models.LogResourceTypeScan("Resources")
 
 	graphClient := graph.NewGraphQuery(cred)
-	query := "resources | project id, subscriptionId, resourceGroup, location, type, name, sku.name, sku.tier, kind"
+	query := "resources | project id, subscriptionId, resourceGroup, location, type, name, sku.name, sku.tier, kind | order by subscriptionId, resourceGroup"
 	log.Debug().Msg(query)
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 	result := graphClient.Query(ctx, query, subs)
 	resources := []*models.Resource{}
@@ -96,7 +97,7 @@ func (sc ResourceScanner) GetCountPerResourceTypeAndSubscription(ctx context.Con
 	log.Debug().Msg(query)
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 	result := graphClient.Query(ctx, query, subs)
 	resources := []models.ResourceTypeCount{}
@@ -109,13 +110,9 @@ func (sc ResourceScanner) GetCountPerResourceTypeAndSubscription(ctx context.Con
 			}
 
 			resources = append(resources, models.ResourceTypeCount{
-				Subscription:    subscriptions[m["subscriptionId"].(string)],
-				ResourceType:    m["type"].(string),
-				Count:           m["count_"].(float64),
-				AvailableInAPRL: sc.isAvailableInAPRL(m["type"].(string), recommendations),
-				Custom1:         "",
-				Custom2:         "",
-				Custom3:         "",
+				Subscription: subscriptions[m["subscriptionId"].(string)],
+				ResourceType: m["type"].(string),
+				Count:        m["count_"].(float64),
 			})
 		}
 	}
@@ -130,7 +127,7 @@ func (sc ResourceScanner) GetCountPerResourceType(ctx context.Context, cred azco
 	log.Debug().Msg(query)
 	subs := make([]*string, 0, len(subscriptions))
 	for s := range subscriptions {
-		subs = append(subs, &s)
+		subs = append(subs, to.Ptr(s))
 	}
 	result := graphClient.Query(ctx, query, subs)
 	resources := map[string]float64{}
@@ -147,12 +144,3 @@ func (sc ResourceScanner) GetCountPerResourceType(ctx context.Context, cred azco
 	}
 	return resources
 }
-
-func (sc ResourceScanner) isAvailableInAPRL(resourceType string, recommendations map[string]map[string]models.AprlRecommendation) string {
-	_, available := recommendations[strings.ToLower(resourceType)]
-	if available {
-		return "Yes"
-	} else {
-		return "No"
-	}
-}
diff --git a/internal/scanners/rg/rg.go b/internal/scanners/rg/rg.go
index c412bf7..6810977 100644
--- a/internal/scanners/rg/rg.go
+++ b/internal/scanners/rg/rg.go
@@ -23,8 +23,8 @@ func (a *ResourceGroupScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Resource Groups
-func (a *ResourceGroupScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
-	return []models.AzqrServiceResult{}, nil
+func (a *ResourceGroupScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *ResourceGroupScanner) ResourceTypes() []string {
diff --git a/internal/scanners/rsv/rsv.go b/internal/scanners/rsv/rsv.go
index 6e4e01b..763c3ee 100644
--- a/internal/scanners/rsv/rsv.go
+++ b/internal/scanners/rsv/rsv.go
@@ -23,9 +23,9 @@ func (a *RecoveryServiceScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Recovery Service in a Resource Group
-func (a *RecoveryServiceScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *RecoveryServiceScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *RecoveryServiceScanner) ResourceTypes() []string {
diff --git a/internal/scanners/rt/rt.go b/internal/scanners/rt/rt.go
index 266c074..d9540d1 100644
--- a/internal/scanners/rt/rt.go
+++ b/internal/scanners/rt/rt.go
@@ -28,7 +28,7 @@ func (a *RouteTableScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Route Table in a Resource Group
-func (c *RouteTableScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *RouteTableScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	svcs, err := c.list()
@@ -37,12 +37,12 @@ func (c *RouteTableScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range svcs {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *RouteTableScanner) list() ([]*armnetwork.RouteTable, error) {
 	svcs := make([]*armnetwork.RouteTable, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/sap/sap.go b/internal/scanners/sap/sap.go
index 78d556f..30655c5 100644
--- a/internal/scanners/sap/sap.go
+++ b/internal/scanners/sap/sap.go
@@ -23,9 +23,9 @@ func (a *SAPScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all SAP in a Resource Group
-func (a *SAPScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *SAPScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *SAPScanner) ResourceTypes() []string {
diff --git a/internal/scanners/sb/sb.go b/internal/scanners/sb/sb.go
index 485a0aa..2d70db2 100644
--- a/internal/scanners/sb/sb.go
+++ b/internal/scanners/sb/sb.go
@@ -28,7 +28,7 @@ func (a *ServiceBusScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Service Bus in a Resource Group
-func (c *ServiceBusScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *ServiceBusScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	servicebus, err := c.listServiceBus()
@@ -37,12 +37,12 @@ func (c *ServiceBusScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, servicebus := range servicebus {
 		rr := engine.EvaluateRecommendations(rules, servicebus, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*servicebus.ID),
@@ -61,7 +61,7 @@ func (c *ServiceBusScanner) listServiceBus() ([]*armservicebus.SBNamespace, erro
 	namespaces := make([]*armservicebus.SBNamespace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/sigr/sigr.go b/internal/scanners/sigr/sigr.go
index 8707840..833162b 100644
--- a/internal/scanners/sigr/sigr.go
+++ b/internal/scanners/sigr/sigr.go
@@ -28,7 +28,7 @@ func (c *SignalRScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all SignalR in a Resource Group
-func (c *SignalRScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *SignalRScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	signalr, err := c.listSignalR()
@@ -37,12 +37,12 @@ func (c *SignalRScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSer
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, signalr := range signalr {
 		rr := engine.EvaluateRecommendations(rules, signalr, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*signalr.ID),
@@ -61,7 +61,7 @@ func (c *SignalRScanner) listSignalR() ([]*armsignalr.ResourceInfo, error) {
 	signalrs := make([]*armsignalr.ResourceInfo, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/sql/sql.go b/internal/scanners/sql/sql.go
index fa968b1..da43705 100644
--- a/internal/scanners/sql/sql.go
+++ b/internal/scanners/sql/sql.go
@@ -43,7 +43,7 @@ func (c *SQLScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all SQL in a Resource Group
-func (c *SQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *SQLScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	sql, err := c.listSQL()
@@ -54,14 +54,14 @@ func (c *SQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrService
 	rules := c.getServerRules()
 	databaseRules := c.getDatabaseRules()
 	poolRules := c.getPoolRules()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, sql := range sql {
 		rr := engine.EvaluateRecommendations(rules, sql, scanContext)
 
 		resourceGroupName := models.GetResourceGroupFromResourceID(*sql.ID)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:  c.config.SubscriptionID,
 			ResourceGroup:   resourceGroupName,
 			ServiceName:     *sql.Name,
@@ -77,7 +77,7 @@ func (c *SQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrService
 		for _, pool := range pools {
 			rr := engine.EvaluateRecommendations(poolRules, pool, scanContext)
 
-			results = append(results, models.AzqrServiceResult{
+			results = append(results, &models.AzqrServiceResult{
 				SubscriptionID:   c.config.SubscriptionID,
 				SubscriptionName: c.config.SubscriptionName,
 				ResourceGroup:    resourceGroupName,
@@ -99,7 +99,7 @@ func (c *SQLScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrService
 
 			rr := engine.EvaluateRecommendations(databaseRules, database, scanContext)
 
-			results = append(results, models.AzqrServiceResult{
+			results = append(results, &models.AzqrServiceResult{
 				SubscriptionID:   c.config.SubscriptionID,
 				SubscriptionName: c.config.SubscriptionName,
 				ResourceGroup:    resourceGroupName,
@@ -120,7 +120,7 @@ func (c *SQLScanner) listSQL() ([]*armsql.Server, error) {
 	servers := make([]*armsql.Server, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -136,7 +136,7 @@ func (c *SQLScanner) listDatabases(resourceGroupName, serverName string) ([]*arm
 	databases := make([]*armsql.Database, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -152,7 +152,7 @@ func (c *SQLScanner) listPools(resourceGroupName, serverName string) ([]*armsql.
 	pools := make([]*armsql.ElasticPool, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/srch/srch.go b/internal/scanners/srch/srch.go
index 893c725..ed65fb2 100644
--- a/internal/scanners/srch/srch.go
+++ b/internal/scanners/srch/srch.go
@@ -28,7 +28,7 @@ func (a *AISearchScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Azure AI Search
-func (a *AISearchScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *AISearchScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	workspaces, err := a.listWorkspaces()
@@ -37,12 +37,12 @@ func (a *AISearchScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSe
 	}
 	engine := models.RecommendationEngine{}
 	rules := a.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, g := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, g, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*g.ID),
@@ -61,7 +61,7 @@ func (a *AISearchScanner) listWorkspaces() ([]*armsearch.Service, error) {
 	workspaces := make([]*armsearch.Service, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/st/st.go b/internal/scanners/st/st.go
index 4b1b4d2..6375823 100644
--- a/internal/scanners/st/st.go
+++ b/internal/scanners/st/st.go
@@ -33,7 +33,7 @@ func (c *StorageScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Storage in a Resource Group
-func (c *StorageScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *StorageScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	storage, err := c.listStorage()
@@ -42,14 +42,14 @@ func (c *StorageScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSer
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, storage := range storage {
 		resourceGroupName := models.GetResourceGroupFromResourceID(*storage.ID)
 
 		scanContext.BlobServiceProperties = nil
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		blobServicesProperties, err := c.blobServicesClient.GetServiceProperties(c.config.Ctx, resourceGroupName, *storage.Name, nil)
 		if err == nil {
 			scanContext.BlobServiceProperties = &blobServicesProperties
@@ -57,7 +57,7 @@ func (c *StorageScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrSer
 
 		rr := engine.EvaluateRecommendations(rules, storage, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    resourceGroupName,
@@ -76,7 +76,7 @@ func (c *StorageScanner) listStorage() ([]*armstorage.Account, error) {
 	staccounts := make([]*armstorage.Account, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/subscriptions.go b/internal/scanners/subscriptions.go
index 8a9a281..c0ed100 100644
--- a/internal/scanners/subscriptions.go
+++ b/internal/scanners/subscriptions.go
@@ -25,7 +25,7 @@ func (sc SubcriptionScanner) ListSubscriptions(ctx context.Context, cred azcore.
 	subs := make([]*armsubscription.Subscription, 0)
 	for resultPager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(ctx) // nolint:errcheck
 		pageResp, err := resultPager.NextPage(ctx)
 		if err != nil {
 			log.Fatal().Err(err).Msg("Failed to list subscriptions")
diff --git a/internal/scanners/synw/synw.go b/internal/scanners/synw/synw.go
index b8e6fc8..7d91c84 100644
--- a/internal/scanners/synw/synw.go
+++ b/internal/scanners/synw/synw.go
@@ -41,7 +41,7 @@ func (a *SynapseWorkspaceScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Synapse Workspaces in a Resource Group
-func (a *SynapseWorkspaceScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *SynapseWorkspaceScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
 
 	workspaces, err := a.listWorkspaces()
@@ -53,14 +53,14 @@ func (a *SynapseWorkspaceScanner) Scan(scanContext *models.ScanContext) ([]model
 	rules := a.getWorkspaceRules()
 	sqlPoolRules := a.getSqlPoolRules()
 	sparkPoolRules := a.getSparkPoolRules()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range workspaces {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
 		resourceGroupName := models.GetResourceGroupFromResourceID(*w.ID)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   a.config.SubscriptionID,
 			SubscriptionName: a.config.SubscriptionName,
 			ResourceGroup:    resourceGroupName,
@@ -77,10 +77,9 @@ func (a *SynapseWorkspaceScanner) Scan(scanContext *models.ScanContext) ([]model
 			}
 
 			for _, s := range sqlPools {
-				var result models.AzqrServiceResult
 				rr := engine.EvaluateRecommendations(sqlPoolRules, s, scanContext)
 
-				result = models.AzqrServiceResult{
+				result := &models.AzqrServiceResult{
 					SubscriptionID:   a.config.SubscriptionID,
 					SubscriptionName: a.config.SubscriptionName,
 					ResourceGroup:    resourceGroupName,
@@ -99,10 +98,9 @@ func (a *SynapseWorkspaceScanner) Scan(scanContext *models.ScanContext) ([]model
 		}
 
 		for _, s := range sparkPools {
-			var result models.AzqrServiceResult
 			rr := engine.EvaluateRecommendations(sparkPoolRules, s, scanContext)
 
-			result = models.AzqrServiceResult{
+			result := &models.AzqrServiceResult{
 				SubscriptionID:   a.config.SubscriptionID,
 				SubscriptionName: a.config.SubscriptionName,
 				ResourceGroup:    resourceGroupName,
@@ -123,7 +121,7 @@ func (a *SynapseWorkspaceScanner) listWorkspaces() ([]*armsynapse.Workspace, err
 	workspaces := make([]*armsynapse.Workspace, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -138,7 +136,7 @@ func (a *SynapseWorkspaceScanner) listSqlPools(resourceGroupName string, workspa
 	results := make([]*armsynapse.SQLPool, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
@@ -153,7 +151,7 @@ func (a *SynapseWorkspaceScanner) listSparkPools(resourceGroupName string, works
 	results := make([]*armsynapse.BigDataPoolResourceInfo, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(a.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(a.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/traf/traf.go b/internal/scanners/traf/traf.go
index de2bdef..eb59372 100644
--- a/internal/scanners/traf/traf.go
+++ b/internal/scanners/traf/traf.go
@@ -28,7 +28,7 @@ func (c *TrafficManagerScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all TrafficManager in a Resource Group
-func (c *TrafficManagerScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *TrafficManagerScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vnets, err := c.list()
@@ -37,12 +37,12 @@ func (c *TrafficManagerScanner) Scan(scanContext *models.ScanContext) ([]models.
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vnets {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *TrafficManagerScanner) list() ([]*armtrafficmanager.Profile, error) {
 	vnets := make([]*armtrafficmanager.Profile, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/vdpool/vdpool.go b/internal/scanners/vdpool/vdpool.go
index 584fa52..7aad291 100644
--- a/internal/scanners/vdpool/vdpool.go
+++ b/internal/scanners/vdpool/vdpool.go
@@ -23,9 +23,9 @@ func (a *VirtualDesktopScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Virtual Desktop in a Resource Group
-func (a *VirtualDesktopScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (a *VirtualDesktopScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0])
-	return []models.AzqrServiceResult{}, nil
+	return []*models.AzqrServiceResult{}, nil
 }
 
 func (a *VirtualDesktopScanner) ResourceTypes() []string {
diff --git a/internal/scanners/vgw/vgw.go b/internal/scanners/vgw/vgw.go
index b71f2f5..a43aead 100644
--- a/internal/scanners/vgw/vgw.go
+++ b/internal/scanners/vgw/vgw.go
@@ -29,9 +29,9 @@ func (c *VirtualNetworkGatewayScanner) Init(config *models.ScannerConfig) error
 }
 
 // Scan - Scans all VirtualNetwork in a Resource Group
-func (c *VirtualNetworkGatewayScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *VirtualNetworkGatewayScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	rgs, err := models.ListResourceGroup(c.config.Ctx, c.config.Cred, c.config.SubscriptionID, c.config.ClientOptions)
 	if err != nil {
@@ -49,7 +49,7 @@ func (c *VirtualNetworkGatewayScanner) Scan(scanContext *models.ScanContext) ([]
 		for _, w := range vpns {
 			rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-			results = append(results, models.AzqrServiceResult{
+			results = append(results, &models.AzqrServiceResult{
 				SubscriptionID:   c.config.SubscriptionID,
 				SubscriptionName: c.config.SubscriptionName,
 				ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -70,7 +70,7 @@ func (c *VirtualNetworkGatewayScanner) listVirtualNetworkGateways(resourceGroupN
 	vpns := make([]*armnetwork.VirtualNetworkGateway, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/vm/vm.go b/internal/scanners/vm/vm.go
index fde2f7a..eba53fe 100644
--- a/internal/scanners/vm/vm.go
+++ b/internal/scanners/vm/vm.go
@@ -28,7 +28,7 @@ func (c *VirtualMachineScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all Virtual Machines in a Resource Group
-func (c *VirtualMachineScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *VirtualMachineScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vwans, err := c.list()
@@ -37,12 +37,12 @@ func (c *VirtualMachineScanner) Scan(scanContext *models.ScanContext) ([]models.
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vwans {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *VirtualMachineScanner) list() ([]*armcompute.VirtualMachine, error) {
 	vms := make([]*armcompute.VirtualMachine, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/vmss/vmss.go b/internal/scanners/vmss/vmss.go
index d47c968..c626bca 100644
--- a/internal/scanners/vmss/vmss.go
+++ b/internal/scanners/vmss/vmss.go
@@ -28,7 +28,7 @@ func (c *VirtualMachineScaleSetScanner) Init(config *models.ScannerConfig) error
 }
 
 // Scan - Scans all Virtual Machines Scale Sets in a Resource Group
-func (c *VirtualMachineScaleSetScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *VirtualMachineScaleSetScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vmss, err := c.list()
@@ -37,12 +37,12 @@ func (c *VirtualMachineScaleSetScanner) Scan(scanContext *models.ScanContext) ([
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vmss {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *VirtualMachineScaleSetScanner) list() ([]*armcompute.VirtualMachineScal
 	vmss := make([]*armcompute.VirtualMachineScaleSet, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/vnet/vnet.go b/internal/scanners/vnet/vnet.go
index 110c5f8..82e8b2a 100644
--- a/internal/scanners/vnet/vnet.go
+++ b/internal/scanners/vnet/vnet.go
@@ -28,7 +28,7 @@ func (c *VirtualNetworkScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all VirtualNetwork in a Resource Group
-func (c *VirtualNetworkScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *VirtualNetworkScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vnets, err := c.list()
@@ -37,12 +37,12 @@ func (c *VirtualNetworkScanner) Scan(scanContext *models.ScanContext) ([]models.
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vnets {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *VirtualNetworkScanner) list() ([]*armnetwork.VirtualNetwork, error) {
 	vnets := make([]*armnetwork.VirtualNetwork, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/vwan/vwan.go b/internal/scanners/vwan/vwan.go
index 65602b6..e2858ff 100644
--- a/internal/scanners/vwan/vwan.go
+++ b/internal/scanners/vwan/vwan.go
@@ -28,7 +28,7 @@ func (c *VirtualWanScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all VirtualWan in a Resource Group
-func (c *VirtualWanScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *VirtualWanScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	vwans, err := c.list()
@@ -37,12 +37,12 @@ func (c *VirtualWanScanner) Scan(scanContext *models.ScanContext) ([]models.Azqr
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range vwans {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *VirtualWanScanner) list() ([]*armnetwork.VirtualWAN, error) {
 	vwans := make([]*armnetwork.VirtualWAN, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/scanners/wps/wps.go b/internal/scanners/wps/wps.go
index 5234ac3..bfd9ff6 100644
--- a/internal/scanners/wps/wps.go
+++ b/internal/scanners/wps/wps.go
@@ -28,7 +28,7 @@ func (c *WebPubSubScanner) Init(config *models.ScannerConfig) error {
 }
 
 // Scan - Scans all WebPubSub in a Resource Group
-func (c *WebPubSubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrServiceResult, error) {
+func (c *WebPubSubScanner) Scan(scanContext *models.ScanContext) ([]*models.AzqrServiceResult, error) {
 	models.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0])
 
 	WebPubSub, err := c.listWebPubSub()
@@ -37,12 +37,12 @@ func (c *WebPubSubScanner) Scan(scanContext *models.ScanContext) ([]models.AzqrS
 	}
 	engine := models.RecommendationEngine{}
 	rules := c.GetRecommendations()
-	results := []models.AzqrServiceResult{}
+	results := []*models.AzqrServiceResult{}
 
 	for _, w := range WebPubSub {
 		rr := engine.EvaluateRecommendations(rules, w, scanContext)
 
-		results = append(results, models.AzqrServiceResult{
+		results = append(results, &models.AzqrServiceResult{
 			SubscriptionID:   c.config.SubscriptionID,
 			SubscriptionName: c.config.SubscriptionName,
 			ResourceGroup:    models.GetResourceGroupFromResourceID(*w.ID),
@@ -61,7 +61,7 @@ func (c *WebPubSubScanner) listWebPubSub() ([]*armwebpubsub.ResourceInfo, error)
 	WebPubSubs := make([]*armwebpubsub.ResourceInfo, 0)
 	for pager.More() {
 		// Wait for a token from the burstLimiter channel before making the request
-		<-throttling.ARMLimiter
+		_ = throttling.WaitARM(c.config.Ctx); // nolint:errcheck
 		resp, err := pager.NextPage(c.config.Ctx)
 		if err != nil {
 			return nil, err
diff --git a/internal/throttling/limiter.go b/internal/throttling/limiter.go
index c449655..b132c63 100644
--- a/internal/throttling/limiter.go
+++ b/internal/throttling/limiter.go
@@ -1,73 +1,28 @@
 package throttling
 
-import "time"
-
-type Limiter struct {
-	bucketCapacity int
-	refillRate     int
-	refillEvery    time.Duration
-	refillWait     time.Duration
-}
-
-func NewLimiter(bucketCapacity, refillRate int, refillEvery, refillWait time.Duration) *Limiter {
-	return &Limiter{
-		bucketCapacity: bucketCapacity,
-		refillRate:     refillRate,
-		refillEvery:    refillEvery,
-		refillWait:     refillWait,
-	}
-}
-
-func (l *Limiter) Start() chan struct{} {
-	bucketCapacity := l.bucketCapacity
-	refillRate := l.refillRate
-
-	// Create a channel to act as a burst limiter.
-	// This will allow up to bucketCapacity requests at once.
-
-	// Staggering queries to avoid throttling.
-	// https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling#regional-throttling-and-token-bucket-algorithm
-	// https://learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/guidance-for-throttled-requests#staggering-queries
-	burstLimiter := make(chan struct{}, bucketCapacity)
-
-	// Fill the burstLimiter channel with initial tokens.
-	for i := 0; i < bucketCapacity; i++ {
-		burstLimiter <- struct{}{}
-	}
-
-	// Create a ticker to limit the rate of requests
-	limiter := time.NewTicker(l.refillEvery)
-
-	// Start a goroutine to send ticks to the burstLimiter channel
-	go func() {
-		for range limiter.C {
-			for i := 0; i < refillRate; i++ {
-				// Only add a token if the channel is not full to avoid blocking
-				select {
-				case burstLimiter <- struct{}{}:
-					time.Sleep(l.refillWait) // Wait for the specified refill wait time
-				default:
-					// Channel is full, skip adding more tokens
-				}
-			}
-		}
-	}()
-	return burstLimiter
+import (
+	"context"
+
+	"golang.org/x/time/rate"
+)
+
+// ARMLimiter rate limits Azure Resource Manager API calls
+// Allows 3 operations per second with burst capacity of 100
+// https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling#regional-throttling-and-token-bucket-algorithm
+var ARMLimiter = rate.NewLimiter(rate.Limit(3), 100)
+
+// GraphLimiter rate limits Azure Resource Graph API calls
+// Allows 3 operations per second with burst capacity of 10
+// With higher burst capacity to better utilize the 5-second window
+// https://learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/guidance-for-throttled-requests#staggering-queries
+var GraphLimiter = rate.NewLimiter(rate.Limit(2), 10)
+
+// WaitARM waits for permission to make an ARM API call using the provided context
+func WaitARM(ctx context.Context) error {
+	return ARMLimiter.Wait(ctx)
 }
 
-var ARMLimiter chan struct{}
-var GraphLimiter chan struct{}
-
-func init() {
-	// Create a  limiter for ARM API
-	bucketCapacity := 250
-	refillRate := 25
-	armlimiter := NewLimiter(bucketCapacity, refillRate, 1*time.Second, 0*time.Millisecond)
-	ARMLimiter = armlimiter.Start()
-
-	// Create a separate limiter for Graph API
-	graphBucketCapacity := 3
-	graphRefillRate := 2
-	graphLimiter := NewLimiter(graphBucketCapacity, graphRefillRate, 1*time.Second, 100*time.Millisecond)
-	GraphLimiter = graphLimiter.Start()
+// WaitGraph waits for permission to make a Graph API call using the provided context
+func WaitGraph(ctx context.Context) error {
+	return GraphLimiter.Wait(ctx)
 }
diff --git a/internal/viewer/static/app.js b/internal/viewer/static/app.js
index 1fd9646..6fd6f22 100644
--- a/internal/viewer/static/app.js
+++ b/internal/viewer/static/app.js
@@ -54,6 +54,18 @@ const menuStructure = [
         ]
     }
 ];
+function showLoading(message = 'Loading data...') {
+    const overlay = document.getElementById('loading-overlay');
+    const text = overlay.querySelector('.loading-text');
+    if (text) text.textContent = message;
+    overlay.style.display = 'flex';
+}
+
+function hideLoading() {
+    const overlay = document.getElementById('loading-overlay');
+    overlay.style.display = 'none';
+}
+
 async function fetchJSON(u) { const r = await fetch(u); if (!r.ok) throw new Error(await r.text()); return r.json(); }
 function navigate() {
     const h = location.hash.replace('#', '');
@@ -184,10 +196,21 @@ async function showDashboard() {
     document.getElementById('dashboard').style.display = 'block';
     document.getElementById('dataset-view').style.display = 'none';
     const cards = document.getElementById('cards');
-    const [summary, analytics] = await Promise.all([
-        fetchJSON('/api/summary'),
-        fetchJSON('/api/analytics').catch(() => ({}))
-    ]);
+
+    showLoading('Loading dashboard...');
+    let summary, analytics;
+    try {
+        [summary, analytics] = await Promise.all([
+            fetchJSON('/api/summary'),
+            fetchJSON('/api/analytics').catch(() => ({}))
+        ]);
+    } catch (error) {
+        hideLoading();
+        console.error('Error loading dashboard:', error);
+        cards.innerHTML = '<div class="col-12"><div class="alert alert-danger"><i class="bi bi-exclamation-triangle me-2"></i>Error loading dashboard data</div></div>';
+        return;
+    }
+
     cards.innerHTML = '';
     // Basic summary cards (filter out low-value counts per request)
     const exclude = new Set([
@@ -271,6 +294,8 @@ async function showDashboard() {
         wrapper.appendChild(buildBootstrapTable('Top Impacted Resource Types', readableRTs, ['Resource Type', 'Count'], 'success'));
     }
     cards.parentElement.appendChild(wrapper);
+
+    hideLoading();
 }
 
 function getCardIcon(key) {
@@ -397,6 +422,7 @@ async function showDataset(name) {
     document.getElementById('dashboard').style.display = 'none';
     document.getElementById('dataset-view').style.display = 'block';
 
+    showLoading(`Loading ${routeLabels[name] || name}...`);
     try {
         const data = await fetchJSON(`/api/data/${name}`);
         console.log('Data fetched:', data.length, 'rows');
@@ -404,6 +430,8 @@ async function showDataset(name) {
     } catch (error) {
         console.error('Error fetching data:', error);
         document.getElementById('data-table').innerHTML = '<tbody><tr><td class="text-center text-danger py-4"><i class="bi bi-exclamation-triangle me-2"></i>Error loading data</td></tr></tbody>';
+    } finally {
+        hideLoading();
     }
 }
 
@@ -415,6 +443,7 @@ async function showPlugin(routeName) {
     const pluginName = routeName.replace('plugin-', '');
     const plugin = pluginMetadata[routeName];
 
+    showLoading(`Loading ${plugin?.displayName || pluginName}...`);
     try {
         const data = await fetchJSON(`/api/plugin/${pluginName}`);
         console.log('Plugin data fetched:', data.length, 'rows');
@@ -422,6 +451,8 @@ async function showPlugin(routeName) {
     } catch (error) {
         console.error('Error fetching plugin data:', error);
         document.getElementById('data-table').innerHTML = '<tbody><tr><td class="text-center text-danger py-4"><i class="bi bi-exclamation-triangle me-2"></i>Error loading plugin data</td></tr></tbody>';
+    } finally {
+        hideLoading();
     }
 }
 function renderTable(rows, datasetName) {
diff --git a/internal/viewer/static/index.html b/internal/viewer/static/index.html
index d567ad9..31ef3d1 100644
--- a/internal/viewer/static/index.html
+++ b/internal/viewer/static/index.html
@@ -49,6 +49,16 @@
         </div>
     </nav>
 
+    <!-- Loading Indicator -->
+    <div id="loading-overlay" class="loading-overlay" style="display: none;">
+        <div class="loading-spinner">
+            <div class="spinner-border text-primary" role="status">
+                <span class="visually-hidden">Loading...</span>
+            </div>
+            <div class="loading-text mt-3">Loading data...</div>
+        </div>
+    </div>
+
     <!-- Main Content -->
     <div class="container-fluid py-4">
         <section id="dashboard" class="view">
diff --git a/internal/viewer/static/styles.css b/internal/viewer/static/styles.css
index 71bce20..5f8f355 100644
--- a/internal/viewer/static/styles.css
+++ b/internal/viewer/static/styles.css
@@ -287,4 +287,40 @@ body {
     opacity: 0.6;
     pointer-events: none;
     transition: opacity 0.3s ease;
+}
+
+/* Loading overlay */
+.loading-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(255, 255, 255, 0.9);
+    backdrop-filter: blur(4px);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 2000;
+    animation: fadeIn 0.2s ease-out;
+}
+
+.loading-spinner {
+    text-align: center;
+    padding: 2rem;
+    background: white;
+    border-radius: 12px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+}
+
+.loading-spinner .spinner-border {
+    width: 3rem;
+    height: 3rem;
+    border-width: 0.3rem;
+}
+
+.loading-text {
+    color: var(--azure-blue);
+    font-weight: 500;
+    font-size: 1rem;
 }
\ No newline at end of file


Test patch:
diff --git a/internal/comparator/excel_test.go b/internal/comparator/excel_test.go
new file mode 100644
index 0000000..862cf82
--- /dev/null
+++ b/internal/comparator/excel_test.go
@@ -0,0 +1,455 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package comparator
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/xuri/excelize/v2"
+)
+
+// createTestExcelFile creates a test Excel file with specified sheets and data
+func createTestExcelFile(t *testing.T, filename string, sheets map[string][][]string) string {
+	t.Helper()
+
+	tmpDir := t.TempDir()
+	filePath := filepath.Join(tmpDir, filename)
+
+	f := excelize.NewFile()
+	defer func() {
+		if err := f.Close(); err != nil {
+			t.Fatalf("Failed to close test Excel file: %v", err)
+		}
+	}()
+
+	// Delete default Sheet1 if we have custom sheets
+	if len(sheets) > 0 {
+		_ = f.DeleteSheet("Sheet1")
+	}
+
+	// Create sheets with data
+	for sheetName, rows := range sheets {
+		_, err := f.NewSheet(sheetName)
+		if err != nil {
+			t.Fatalf("Failed to create sheet %s: %v", sheetName, err)
+		}
+
+		// Write rows
+		for rowIdx, row := range rows {
+			for colIdx, cell := range row {
+				cellName, err := excelize.CoordinatesToCellName(colIdx+1, rowIdx+1)
+				if err != nil {
+					t.Fatalf("Failed to get cell coordinates: %v", err)
+				}
+				err = f.SetCellValue(sheetName, cellName, cell)
+				if err != nil {
+					t.Fatalf("Failed to set cell value: %v", err)
+				}
+			}
+		}
+	}
+
+	// Save file
+	if err := f.SaveAs(filePath); err != nil {
+		t.Fatalf("Failed to save test Excel file: %v", err)
+	}
+
+	return filePath
+}
+
+func TestCompareExcelFiles_BasicComparison(t *testing.T) {
+	// Create first test file
+	file1Data := map[string][][]string{
+		"Recommendations": {
+			{"Header1", "Header2", "Header3"},
+			{"", "", ""},                 // Row 2 - empty
+			{"", "", ""},                 // Row 3 - empty
+			{"ID", "Name", "Status"},     // Row 4 - headers
+			{"1", "Resource1", "Active"}, // Row 5 - data
+			{"2", "Resource2", "Active"}, // Row 6 - data
+		},
+		"Inventory": {
+			{"Header1", "Header2"},
+			{"", ""},       // Row 2
+			{"", ""},       // Row 3
+			{"ID", "Name"}, // Row 4
+			{"A", "Item1"}, // Row 5
+		},
+	}
+
+	// Create second test file with different row counts
+	file2Data := map[string][][]string{
+		"Recommendations": {
+			{"Header1", "Header2", "Header3"},
+			{"", "", ""},                 // Row 2
+			{"", "", ""},                 // Row 3
+			{"ID", "Name", "Status"},     // Row 4
+			{"1", "Resource1", "Active"}, // Row 5
+			{"2", "Resource2", "Active"}, // Row 6
+			{"3", "Resource3", "Active"}, // Row 7 - new row
+		},
+		"Inventory": {
+			{"Header1", "Header2"},
+			{"", ""},       // Row 2
+			{"", ""},       // Row 3
+			{"ID", "Name"}, // Row 4
+			{"A", "Item1"}, // Row 5
+			{"B", "Item2"}, // Row 6 - new row
+		},
+		"NewSheet": {
+			{"Col1"},
+			{"", ""},   // Row 2
+			{"", ""},   // Row 3
+			{"Header"}, // Row 4
+			{"Data1"},  // Row 5
+		},
+	}
+
+	file1 := createTestExcelFile(t, "test1.xlsx", file1Data)
+	file2 := createTestExcelFile(t, "test2.xlsx", file2Data)
+
+	// Compare files
+	result, err := CompareExcelFiles(file1, file2)
+	if err != nil {
+		t.Fatalf("CompareExcelFiles failed: %v", err)
+	}
+
+	// Verify results
+	if result.OldFilePath != file1 {
+		t.Errorf("Expected OldFilePath=%s, got %s", file1, result.OldFilePath)
+	}
+	if result.NewFilePath != file2 {
+		t.Errorf("Expected NewFilePath=%s, got %s", file2, result.NewFilePath)
+	}
+
+	// Check sheet comparisons (should have at least 3, may have default sheet)
+	if len(result.SheetComparisons) < 3 {
+		t.Errorf("Expected at least 3 sheet comparisons, got %d", len(result.SheetComparisons))
+	}
+
+	// Find specific sheet comparisons
+	var recsComparison, invComparison, newSheetComparison *SheetComparison
+	for i := range result.SheetComparisons {
+		switch result.SheetComparisons[i].SheetName {
+		case "Recommendations":
+			recsComparison = &result.SheetComparisons[i]
+		case "Inventory":
+			invComparison = &result.SheetComparisons[i]
+		case "NewSheet":
+			newSheetComparison = &result.SheetComparisons[i]
+		}
+	}
+
+	// Test Recommendations sheet
+	if recsComparison == nil {
+		t.Fatal("Recommendations sheet comparison not found")
+	}
+	if recsComparison.OldRowCount != 6 {
+		t.Errorf("Recommendations OldRowCount: expected 6, got %d", recsComparison.OldRowCount)
+	}
+	if recsComparison.NewRowCount != 7 {
+		t.Errorf("Recommendations NewRowCount: expected 7, got %d", recsComparison.NewRowCount)
+	}
+	if recsComparison.RowDifference != 1 {
+		t.Errorf("Recommendations RowDifference: expected 1, got %d", recsComparison.RowDifference)
+	}
+
+	// Test Inventory sheet
+	if invComparison == nil {
+		t.Fatal("Inventory sheet comparison not found")
+	}
+	if invComparison.OldRowCount != 5 {
+		t.Errorf("Inventory OldRowCount: expected 5, got %d", invComparison.OldRowCount)
+	}
+	if invComparison.NewRowCount != 6 {
+		t.Errorf("Inventory NewRowCount: expected 6, got %d", invComparison.NewRowCount)
+	}
+
+	// Test new sheet (only in file2)
+	if newSheetComparison == nil {
+		t.Fatal("NewSheet comparison not found")
+	}
+	if newSheetComparison.OldRowCount != 0 {
+		t.Errorf("NewSheet OldRowCount: expected 0, got %d", newSheetComparison.OldRowCount)
+	}
+	if newSheetComparison.NewRowCount != 5 {
+		t.Errorf("NewSheet NewRowCount: expected 5, got %d", newSheetComparison.NewRowCount)
+	}
+}
+
+func TestCompareExcelFiles_DuplicateDetection(t *testing.T) {
+	// Create test file with duplicates after row 4
+	fileData := map[string][][]string{
+		"TestSheet": {
+			{"Logo", "Area"},
+			{"", ""},                       // Row 2
+			{"", ""},                       // Row 3
+			{"ID", "Name", "Status"},       // Row 4 - headers
+			{"1", "Resource1", "Active"},   // Row 5 - data
+			{"2", "Resource2", "Active"},   // Row 6 - data
+			{"1", "Resource1", "Active"},   // Row 7 - duplicate of row 5
+			{"3", "Resource3", "Inactive"}, // Row 8 - unique
+			{"2", "Resource2", "Active"},   // Row 9 - duplicate of row 6
+		},
+	}
+
+	file := createTestExcelFile(t, "test_duplicates.xlsx", fileData)
+
+	// Compare file with itself to trigger duplicate detection
+	result, err := CompareExcelFiles(file, file)
+	if err != nil {
+		t.Fatalf("CompareExcelFiles failed: %v", err)
+	}
+
+	// Find TestSheet comparison
+	var testSheetComparison *SheetComparison
+	for i := range result.SheetComparisons {
+		if result.SheetComparisons[i].SheetName == "TestSheet" {
+			testSheetComparison = &result.SheetComparisons[i]
+			break
+		}
+	}
+
+	if testSheetComparison == nil {
+		t.Fatal("TestSheet comparison not found")
+	}
+
+	// Check duplicate detection
+	if !testSheetComparison.HasDuplicates {
+		t.Error("Expected duplicates to be detected")
+	}
+
+	if len(testSheetComparison.DuplicateRows) == 0 {
+		t.Error("Expected duplicate rows to be listed")
+	}
+
+	// Expected duplicate rows: 5, 6, 7, 9 (1-indexed)
+	// Row 5 and 7 are duplicates, Row 6 and 9 are duplicates
+	expectedDuplicates := map[int]bool{5: true, 6: true, 7: true, 9: true}
+	for _, rowNum := range testSheetComparison.DuplicateRows {
+		if !expectedDuplicates[rowNum] {
+			t.Errorf("Unexpected duplicate row number: %d", rowNum)
+		}
+	}
+}
+
+func TestCompareExcelFiles_NoDuplicates(t *testing.T) {
+	// Create test file without duplicates
+	fileData := map[string][][]string{
+		"TestSheet": {
+			{"Logo", "Area"},
+			{"", ""},                       // Row 2
+			{"", ""},                       // Row 3
+			{"ID", "Name", "Status"},       // Row 4 - headers
+			{"1", "Resource1", "Active"},   // Row 5
+			{"2", "Resource2", "Active"},   // Row 6
+			{"3", "Resource3", "Inactive"}, // Row 7
+		},
+	}
+
+	file := createTestExcelFile(t, "test_no_duplicates.xlsx", fileData)
+
+	result, err := CompareExcelFiles(file, file)
+	if err != nil {
+		t.Fatalf("CompareExcelFiles failed: %v", err)
+	}
+
+	// Find TestSheet comparison
+	var testSheetComparison *SheetComparison
+	for i := range result.SheetComparisons {
+		if result.SheetComparisons[i].SheetName == "TestSheet" {
+			testSheetComparison = &result.SheetComparisons[i]
+			break
+		}
+	}
+
+	if testSheetComparison == nil {
+		t.Fatal("TestSheet comparison not found")
+	}
+
+	// Check no duplicates detected
+	if testSheetComparison.HasDuplicates {
+		t.Error("Expected no duplicates to be detected")
+	}
+
+	if len(testSheetComparison.DuplicateRows) > 0 {
+		t.Errorf("Expected no duplicate rows, got %v", testSheetComparison.DuplicateRows)
+	}
+}
+
+func TestCompareExcelFiles_InvalidFile(t *testing.T) {
+	tmpDir := t.TempDir()
+	nonExistentFile := filepath.Join(tmpDir, "nonexistent.xlsx")
+
+	// Create a valid test file
+	validFileData := map[string][][]string{
+		"Sheet1": {{"A", "B"}, {"1", "2"}},
+	}
+	validFile := createTestExcelFile(t, "valid.xlsx", validFileData)
+
+	// Test with non-existent file1
+	_, err := CompareExcelFiles(nonExistentFile, validFile)
+	if err == nil {
+		t.Error("Expected error for non-existent file1, got nil")
+	}
+
+	// Test with non-existent file2
+	_, err = CompareExcelFiles(validFile, nonExistentFile)
+	if err == nil {
+		t.Error("Expected error for non-existent file2, got nil")
+	}
+
+	// Test with invalid Excel file
+	invalidFile := filepath.Join(tmpDir, "invalid.xlsx")
+	if err := os.WriteFile(invalidFile, []byte("not an excel file"), 0644); err != nil {
+		t.Fatalf("Failed to create invalid test file: %v", err)
+	}
+
+	_, err = CompareExcelFiles(invalidFile, validFile)
+	if err == nil {
+		t.Error("Expected error for invalid Excel file, got nil")
+	}
+}
+
+func TestFormatComparisonResult(t *testing.T) {
+	result := &ExcelComparisonResult{
+		OldFilePath: "/path/to/file1.xlsx",
+		NewFilePath: "/path/to/file2.xlsx",
+		SheetComparisons: []SheetComparison{
+			{
+				SheetName:     "Recommendations",
+				OldRowCount:   10,
+				NewRowCount:   12,
+				RowDifference: 2,
+				HasDuplicates: false,
+				DuplicateRows: []int{},
+			},
+			{
+				SheetName:     "Inventory",
+				OldRowCount:   5,
+				NewRowCount:   5,
+				RowDifference: 0,
+				HasDuplicates: true,
+				DuplicateRows: []int{5, 7},
+			},
+		},
+	}
+
+	output := FormatComparisonResult(result)
+
+	// Check that output contains expected information
+	expectedStrings := []string{
+		"Comparison Results:",
+		"Old File: /path/to/file1.xlsx",
+		"New File: /path/to/file2.xlsx",
+		"Sheet Name",
+		"Old Rows",
+		"New Rows",
+		"Difference",
+		"Has Duplicates",
+		"Recommendations",
+		"Inventory",
+		"10",
+		"12",
+		"+2",
+		"Duplicate rows: [5 7]",
+	}
+
+	for _, expected := range expectedStrings {
+		if !containsString(output, expected) {
+			t.Errorf("Expected output to contain %q, but it doesn't. Output:\n%s", expected, output)
+		}
+	}
+}
+
+func TestRowToString(t *testing.T) {
+	tests := []struct {
+		name     string
+		row      []string
+		expected string
+	}{
+		{
+			name:     "simple row",
+			row:      []string{"A", "B", "C"},
+			expected: "A|B|C",
+		},
+		{
+			name:     "empty row",
+			row:      []string{},
+			expected: "",
+		},
+		{
+			name:     "single cell",
+			row:      []string{"single"},
+			expected: "single",
+		},
+		{
+			name:     "row with empty cells",
+			row:      []string{"A", "", "C"},
+			expected: "A||C",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := rowToString(tt.row)
+			if result != tt.expected {
+				t.Errorf("rowToString(%v) = %q, expected %q", tt.row, result, tt.expected)
+			}
+		})
+	}
+}
+
+func TestContains(t *testing.T) {
+	tests := []struct {
+		name     string
+		slice    []int
+		val      int
+		expected bool
+	}{
+		{
+			name:     "contains value",
+			slice:    []int{1, 2, 3, 4, 5},
+			val:      3,
+			expected: true,
+		},
+		{
+			name:     "does not contain value",
+			slice:    []int{1, 2, 3, 4, 5},
+			val:      6,
+			expected: false,
+		},
+		{
+			name:     "empty slice",
+			slice:    []int{},
+			val:      1,
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := contains(tt.slice, tt.val)
+			if result != tt.expected {
+				t.Errorf("contains(%v, %d) = %t, expected %t", tt.slice, tt.val, result, tt.expected)
+			}
+		})
+	}
+}
+
+// Helper function to check if a string contains a substring
+func containsString(s, substr string) bool {
+	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsSubstring(s, substr))
+}
+
+func containsSubstring(s, substr string) bool {
+	for i := 0; i <= len(s)-len(substr); i++ {
+		if s[i:i+len(substr)] == substr {
+			return true
+		}
+	}
+	return false
+}