# Manual Backup Metering Design

## Status

Approved for implementation on 2026-06-30.

## Context

`cluster-service-ng` PR 2298 adds an hourly control-plane producer for manual backup storage usage. It writes `MeteringData` with category `manual-backup`, grouped by `SharedPoolID`. Each data row contains `cluster_id`, `backup_id`, and `backup_size_gb`. The producer derives `backup_size_gb` from manual backup `restore_size`, rounded up to whole GB.

The `metering` repository currently has backup billing support in two separate paths:

- Auto backup DFS stats use category `dfs-stats`, field `written_bytes`, and generate `backup-storage:{provider}:{region}:{plan}:auto`.
- Auto backup v2 uses category `resource-manager` plus backup metadata category `backup`. Metadata key `backup-storage-v2` supplies an extmeta value such as `T1` or `T2`, and the generated SKU is `backup-storage:{provider}:{region}:{plan}:{extmeta}`.

There is no handler for category `manual-backup` today, so the new producer data is skipped as an unknown category.

## Decision

Add independent manual backup billing support in `metering`.

Manual backup records generate this SKU:

```text
backup-storage:{provider}:{region}:{plan}:manual
```

For example:

```text
backup-storage:aws:ap-south-1:premium:manual
```

The billing quantity is GB. The `UnitValue` in `BillingReportV2Deltalake` remains the `backup_size_gb` value reported by `cluster-service-ng`. The final `BillingReport` conversion must not apply the existing bytes-to-MiB conversion for manual backup SKUs.

## Scope

In scope:

- Add constants for category `manual-backup`, metering field `backup_size_gb`, label `backupID`, and backup type `manual`.
- Add a premium/reserved handler for `manual-backup`.
- Generate one billing record per manual backup row when `backup_size_gb > 0`.
- Use cluster logic metadata for tenant, region, plan, and deleted metadata filtering, exactly like other cluster-scoped categories.
- Preserve `SharedPoolID` from the metering envelope in labels.
- Add tests for SKU generation, handler registration, end-to-end billing record generation, and reporter conversion.

Out of scope:

- Changing `cluster-service-ng` PR 2298 producer behavior.
- Changing existing auto backup DFS stats behavior.
- Changing existing auto backup v2 `backup-storage-v2=T1/T2/...` metadata behavior.
- Adding manual backup support to essential or BYOC plans unless a later requirement asks for it.

## Data Contract

Input `MeteringData`:

```go
&common.MeteringData{
    Timestamp:    1760000000,
    Category:     "manual-backup",
    SelfID:       "...",
    SharedPoolID: "pool-1",
    Data: []map[string]interface{}{
        {
            "cluster_id": "cluster-1",
            "backup_id":  "backup-1",
            "backup_size_gb": map[string]interface{}{
                "value": uint64(128),
                "unit":  "GB",
            },
        },
    },
}
```

Generated `BillingReportV2Deltalake`:

```text
Unit:      backup-storage:aws:ap-south-1:premium:manual
UnitValue: 128
Cost:      1
```

Labels include:

```json
{
  "sourceUnit": "gb",
  "backupType": "manual",
  "backupID": "backup-1",
  "clusterID": "cluster-1",
  "sharedPoolID": "pool-1"
}
```

Final `BillingReport` conversion:

```text
Cost = BillingReportV2Deltalake.Cost * UnitValue
```

For the example above, final cost is `128`.

## T1 And Existing Backup Paths

Existing T1/T2 behavior remains intact. The current path is:

1. `cluster-service-ng` receives `UpdateAutoBackupMetering(cluster_id, sku_name, sku_value)`.
2. It writes logic metadata with category `backup`, such as `{"backup-storage-v2": "T1"}`.
3. `metering` reads that metadata while processing `resource-manager` data with `write_bytes`.
4. `metering` generates `backup-storage:{provider}:{region}:{plan}:T1`.

Manual backup does not use that metadata and does not infer `T1`, `T2`, or `T3`.

## Architecture

The implementation follows existing nextgen metering patterns.

- Constants live in `pkg/common/models/nextgenmeteringrule.go`.
- SKU generation lives in `pkg/nextgenmetering/sku_rules.go`.
- Handler wiring lives in `pkg/nextgenmetering/premium_handler.go` and `pkg/nextgenmetering/sku_registry.go`.
- Reporter conversion logic lives in `pkg/common/models/billing.go`.

Manual backup is cluster-scoped because each row carries `cluster_id`. `dealSKU` already loads logic metadata for cluster-scoped rows and passes the resolved tenant, region, plan, shared pool, and enrichment data into category handlers.

## Error Handling

If `backup_size_gb` is missing, malformed, or zero, generation returns no records and no error. This matches the existing pattern for optional metrics such as `written_bytes`.

If `backup_id` is missing, generation still produces a record when size is positive, but omits the `backupID` label. The upstream producer is expected to send `backup_id`; the consumer remains tolerant.

If there is no cluster metadata, the existing `dealSKU` metadata-missing skip path applies.

## Testing

Focused tests should cover:

- `GenerateManualBackupStorageSKUs` generates `backup-storage:aws:ap-south-1:premium:manual` with `UnitValue` equal to GB.
- Missing or zero `backup_size_gb` returns no records.
- Premium and reserved registries include `manual-backup`; essential and BYOC do not.
- `generateSKURecords` handles `manual-backup` via the registry.
- `generateBillingRecords` can process a `manual-backup` `MeteringData` row when cluster metadata exists.
- `Convert2BillingReport` does not convert manual backup GB values as bytes-to-MiB.
- Existing auto backup conversion and T1/T2 generation tests continue to pass.

## Considered Alternatives

Option 1: Reuse `GenerateBackupStorageSKUs` by mapping `backup_size_gb` to `written_bytes`.

This was rejected because it mixes GB manual backup usage with bytes-based auto backup usage and hides source semantics.

Option 2: Convert manual backup GB to bytes before writing `BillingReportV2Deltalake`.

This was rejected because the manual backup SKU will be configured for GB billing. It would also make the intermediate billing record less direct and harder to audit against the producer payload.

Option 3: Add an independent manual backup handler and bypass bytes-to-MiB conversion for `:manual`.

This is the selected design. It keeps manual backup separate from T1/T2 auto backup metadata, preserves GB semantics, and limits changes to the consumer side that needs the new category.
