# Cost Configuration

How CZBG calculates costs and how to configure them correctly.

## total_cost

`total_cost` is the **total spend for the entire date range**, not monthly.

```
total_cost = monthly_spend × number_of_months
```

Example: $5,000/month for 12 months → `total_cost: 60000`

CZBG distributes this across all hours in the date range, then applies
the cost pattern template's variance (daily, weekly, monthly growth).

### Cost per resource realism

| Category | Monthly per resource | Example |
|----------|---------------------|---------|
| Compute (EC2, VMs, GCE) | $300–$1,000 | c6i.2xlarge ~$350/mo |
| Storage (S3, EBS, Blob) | $10–$100 | gp3 volume ~$25/mo |
| Database (RDS, Cloud SQL) | $300–$1,500 | db.r5.xl ~$345/mo |
| Managed services (ALB, Redis) | $100–$500 | ALB ~$200/mo |
| Serverless (Lambda, Functions) | $50–$500 | Per-invocation |
| Data transfer | $50–$500 | Per-GB egress |

To check: `total_cost / resource_count / months` should fall in these ranges.

## Anomaly Costs

Jobs with anomaly profiles use `target_anomaly_cost` instead of (or in
addition to) `total_cost`.

### Pure anomaly job

All filters have anomaly profiles. `total_cost` is the **baseline** spend
before the anomaly. `target_anomaly_cost` is the monthly cost during the
anomalous state.

```yaml
- name: InsightAPI Spike
  total_cost: 60000          # baseline: $5K/month × 12 months
  resource_count: 10
  filters:
  - cloud_provider: AWS
    services: [...]
    anomaly_profile:
      type: daily_spike
      target_anomaly_cost: 50000   # spike month hits $50K
```

CZBG back-derives the baseline from `target_anomaly_cost` using:
`base = target_anomaly_cost / (step_multiplier ^ step_count)`

### Mixed job (anomaly + non-anomaly filters)

Some filters have anomaly profiles, others don't. `total_cost` must be
positive (it drives the non-anomaly filters). Each anomaly filter needs
its own `target_anomaly_cost`.

### Non-anomaly job

No anomaly profiles. `total_cost` drives all cost calculations.
`total_cost > 0` is required.

## Anomaly Types

| Type | Behavior | Demo use case |
|------|----------|--------------|
| `daily_spike` | Single-day cost spike, returns to baseline | Incident, traffic surge |
| `daily_step_up` | Cost steps up permanently | New feature launch, capacity expansion |
| `daily_step_down` | Cost steps down permanently | Optimization, decommission |
| `hourly_spike` | Single-hour spike within a day | Flash sale, batch job |
| `hourly_step_up` | Cost steps up permanently at an hour | Service migration |
| `hourly_step_down` | Cost steps down permanently at an hour | Rightsizing |

### Anomaly profile fields

```yaml
anomaly_profile:
  type: daily_spike              # required
  target_anomaly_cost: 50000     # required — monthly cost at anomalous state
  step_multiplier: 2.0           # optional — cost multiplier per step
  step_count: 3                  # optional — number of steps (step_up/step_down)
  intensity: 0.8                 # optional — 0.0-1.0, how pronounced
  day: 15                        # optional — day of month
  hour: 14                       # optional — hour (0-23) for hourly types
  duration_hours: 4              # optional — duration for hourly spikes
```

## Cost Pattern Templates

Templates control variance (daily/weekly/monthly patterns) and growth trends.

<!-- Source: src/generate/processing/variance_v2/templates/*.yaml
     If you add a template there, add a row here too. -->

| Template | Use for | Growth | Key behavior |
|----------|---------|--------|-------------|
| `saas_workload` | B2B compute (EC2, VMs) | 12%/mo | Business hours peak, quiet weekends |
| `saas_workload_seasonal` | SaaS with Q4 spike | 12%/mo | + Q4 seasonal surge |
| `consumer_gaming` | Game servers, player APIs | 5%/mo | **Inverted**: evening/weekend peak |
| `realtime_streaming` | Kafka, Kinesis, pipelines | 8%/mo | 24/7 moderate variance |
| `compute_cluster` | ECS/EKS worker nodes | 10%/mo | Autoscaling patterns |
| `storage_steady` | S3, EBS, Blob, GCS | 5%/mo | Minimal variance |
| `storage_active` | Hot storage, frequent access | 8%/mo | Moderate daily variance |
| `storage_declining` | Deprecated/migrating storage | -3%/mo | Declining usage |
| `database_workload` | RDS, ElastiCache, Cloud SQL | 8%/mo | Query-driven variance |
| `warehouse_compute` | Redshift, BigQuery, Snowflake | 10%/mo | Batch query spikes |
| `batch_processing` | EMR, Dataproc, Batch | 5%/mo | Scheduled bursts |
| `serverless` | Lambda, Cloud Functions | 15%/mo | High hourly variance |
| `network_egress` | Data Transfer, CloudFront | 8%/mo | Traffic-driven |
| `ml_training` | SageMaker, Vertex AI | 12%/mo | Burst training jobs |
| `managed_services` | Managed Kafka, Redis, MQ | 5%/mo | Low variance |
| `ai_api` | Bedrock, OpenAI API | 20%/mo | API call volume |
| `ai_api_seasonal` | AI APIs with seasonal demand | 20%/mo | + Q4 seasonal surge |

## Resource Groups

Use `resource_group` to link jobs that represent different billing
dimensions of the same logical resources.

### When to use

A single cloud resource often generates multiple billing line items:
- An EC2 instance has compute charges AND EBS volume charges
- An RDS instance has instance charges AND storage charges
- A GKE node has compute charges AND persistent disk charges

Without `resource_group`, each job generates independent resource IDs.
With it, linked jobs share the same resource IDs — which is how real
billing data works.

### How it works

```yaml
# These two jobs share resource IDs because of the same resource_group
- name: InsightAPI EC2 Instances
  resource_count: 20
  resource_group: insightapi-compute
  filters:
  - cloud_provider: AWS
    services:
    - name: AmazonEC2
      usage_families:
      - name: Compute Instance
        ...

- name: InsightAPI EBS Volumes
  resource_count: 20
  resource_group: insightapi-compute
  filters:
  - cloud_provider: AWS
    services:
    - name: AmazonEC2
      usage_families:
      - name: Storage
        ...
```

### Rules

- Jobs in the same `resource_group` must have the same `resource_count`
- Jobs in the same group should have the same allocation tags
- The group name is arbitrary but should be descriptive
- Resource groups are per-config-file (not global)

### Common patterns

| Pattern | Jobs to link |
|---------|-------------|
| EC2 + EBS | Compute Instance + Storage (EBS volumes) |
| EC2 + Data Transfer | Compute Instance + Data Transfer |
| RDS + Storage | Database Instance + Database Storage |
| GCE + Persistent Disk | Compute Engine + Storage |

## Filter Hierarchy

Configs use a hierarchical filter structure that must match reference data exactly:

```
cloud_provider → services → usage_families → resource_types → operations → usage_types
```

### Key rules

- **Never invent filter values** — copy from `examples/` or `czbg data explore`
- **Empty lists are wildcards** — `[]` at any filter level matches ALL values in
  reference data for that dimension. This is powerful but dangerous:
  - `usage_types: []` — **NEVER use** — matches 100+ types, causes line item explosion
  - `operations: []` — **Safe for providers with null operations** (MongoDB, GCP, Elastic Cloud,
    GitHub, New Relic DataPlatform). Required for GCP (not null, not omitted).
  - `resource_types: []` — **Safe for SaaS providers** with varied/unpredictable resource types
    (New Relic, MongoDB). Use when `czbg data explore` shows multiple resource type variants
    per usage family (e.g., `gigabytesingested`, `production`, empty string).
  - Hyperscalers (AWS, Azure, GCP) should always specify explicit resource types.
- **GCP resource types are plural**: `instances`, `buckets` (not `instance`)
- **Azure operation = instance size**: `D8 v3/D8s v3`
- **Azure usage_type = series family**: `Dv3/DSv3 Series`

## K8s Configuration

K8s jobs model containerized workloads running on managed Kubernetes.

### Hard constraints

- `resource_count` must be exactly **80** for compute resources
- Exactly **5** clusters
- Exactly **3** namespaces per cluster
- Exactly **3** workloads per namespace
- Total: 45 workload definitions

### Structure

```yaml
k8s:
  enabled: true
  clusters:
  - name: platform-prod
    custom_namespaces:
    - name: insightapi-prod
      workloads:
      - name: api-server
        labels:
          app: api-server
          team: platform-engineering
          feature: InsightAPI
        anomaly_profile:          # optional — per-workload anomaly
          type: daily_spike
          target_anomaly_cost: 90000
```

### Label patterns

K8s labels follow the same messiness pattern as tags (see
`docs/provider-patterns.md`). Use consistent labels within a cluster
but vary across clusters to demonstrate label normalization.

## Line Item Estimation

Before submitting, validate that line items stay under limits.

### Formula

```
peak_month_line_items = ref_matches × resource_count × 744 hours
```

K8s multiplier: `× 4.0` (K8s generates ~4x more line items than non-K8s).

### Limits

| Scope | Limit | Action |
|-------|-------|--------|
| Per provider (aggregate) | 10,000,000 | BLOCK — reduce resource_count or narrow filters |
| Per non-K8s job | 1,000,000 | WARN — may be slow |

### Validation

```bash
czbg analyze config <path> --json    # structured output for programmatic check
czbg analyze config <path>           # human-readable table
```

Check `job_analyses[].provider_analysis.<provider>.peak_month_line_items`
and sum across all jobs per provider.
