engine

package
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package engine provides the core cost calculation logic for FinFocus.

The engine orchestrates between plugins and local pricing specifications to calculate both projected and actual infrastructure costs.

Projected Cost Calculation

For projected costs, the engine:

  1. Validates resource descriptors
  2. Queries registered plugins for cost estimates
  3. Falls back to local YAML specs when plugins don't provide pricing
  4. Aggregates results into a unified cost breakdown

Actual Cost Pipeline

For actual costs, the engine supports:

  • Time range queries with start/end dates
  • Tag-based filtering using "tag:key=value" syntax
  • Grouping by resource, type, provider, or date
  • Cross-provider cost aggregation with currency validation

Output Formats

Results can be rendered in three formats:

  • table: Human-readable tabular output
  • json: Structured JSON for programmatic use
  • ndjson: Newline-delimited JSON for streaming

Timeouts

Operations are protected by context timeouts:

  • 60s overall query timeout
  • 30s per-plugin call timeout
  • 5s per-resource calculation timeout

Index

Constants

View Source
const (
	// HealthThresholdWarning is the utilization percentage at which a budget transitions to WARNING.
	// Budgets at or above 80% utilization but below 90% are considered WARNING.
	HealthThresholdWarning = 80.0

	// HealthThresholdCritical is the utilization percentage at which a budget transitions to CRITICAL.
	// Budgets at or above 90% utilization but below 100% are considered CRITICAL.
	HealthThresholdCritical = 90.0

	// HealthThresholdExceeded is the utilization percentage at which a budget transitions to EXCEEDED.
	// Budgets at or above 100% utilization are considered EXCEEDED.
	HealthThresholdExceeded = 100.0
)

Health threshold constants define the boundaries for budget health status. These thresholds match the proto enum semantics for BudgetHealthStatus.

View Source
const (
	DefaultThreshold50  = 50.0
	DefaultThreshold80  = 80.0
	DefaultThreshold100 = 100.0
)

Default threshold percentages.

View Source
const (
	// PropertyPulumiCreated is the property key for resource creation timestamp.
	PropertyPulumiCreated = "pulumi:created"
	// PropertyPulumiModified is the property key for resource modification timestamp.
	PropertyPulumiModified = "pulumi:modified"
	// PropertyPulumiExternal indicates the resource was imported (not created by Pulumi).
	PropertyPulumiExternal = "pulumi:external"
)

Property keys for Pulumi metadata in ResourceDescriptor.Properties.

View Source
const (
	ErrCodePluginError     = "PLUGIN_ERROR"
	ErrCodeValidationError = "VALIDATION_ERROR"
	ErrCodeTimeoutError    = "TIMEOUT_ERROR"
	ErrCodeNoCostData      = "NO_COST_DATA"
)

Error code constants for StructuredError. These are stable, additive-only identifiers per FR-005 — existing codes will never be removed or renamed.

View Source
const ApproachingThresholdBuffer = 5.0

ApproachingThresholdBuffer is the percentage buffer for "approaching" status. If current spend is within this percentage of a threshold, it's "approaching".

View Source
const CurrencyCodeLength = 3

CurrencyCodeLength is the required length for valid ISO 4217 currency codes.

View Source
const (
	// ExitCodeBudgetEvaluationError is returned when budget evaluation fails (FR-009).
	// This is distinct from the configured exit code for threshold violations.
	ExitCodeBudgetEvaluationError = 1
)

Exit code constants for CLI integration.

View Source
const HoursPerMonth = 730

HoursPerMonth is the canonical number of hours in a standard business month used for all monthly cost projections.

View Source
const PercentageMultiplier = 100.0

PercentageMultiplier is used to convert ratios to percentages.

View Source
const UptimeAssumptionNote = "Note: Estimate assumes 100% uptime. Stopped/restarted resources are not tracked."

UptimeAssumptionNote is the standard note documenting the 100% uptime assumption. Per spec T023a, this should be included in all state-based cost estimates.

Variables

View Source
var (
	// ErrCurrencyMismatch is returned when spend currency doesn't match budget currency.
	ErrCurrencyMismatch = errors.New("currency mismatch between budget and actual spend")
	// ErrInvalidSpend is returned when the spend value is invalid.
	ErrInvalidSpend = errors.New("spend value is invalid")
	// ErrBudgetDisabled is returned when trying to evaluate a disabled budget.
	ErrBudgetDisabled = errors.New("budget is disabled (amount is 0)")
)

Budget evaluation error types.

View Source
var (
	// ErrNoCostData is returned when no cost data is available for a resource.
	ErrNoCostData = errors.New("no cost data available")
	// ErrMixedCurrencies is returned when cross-provider aggregation encounters multiple currencies.
	ErrMixedCurrencies = errors.New("mixed currencies not supported in cross-provider aggregation")
	// ErrInvalidGroupBy is returned when a non-time-based grouping is used for cross-provider aggregation.
	ErrInvalidGroupBy = errors.New("invalid groupBy type for cross-provider aggregation")
	// ErrEmptyResults is returned when aggregation is attempted on empty results.
	ErrEmptyResults = errors.New("empty results provided for aggregation")
	// ErrInvalidDateRange is returned when the end date is before the start date.
	ErrInvalidDateRange = errors.New("invalid date range: end date must be after start date")
)
View Source
var ErrInvalidBudget = errors.New("invalid budget")

ErrInvalidBudget is returned when a budget fails validation.

View Source
var ErrInvalidCurrency = errors.New("invalid currency code")

ErrInvalidCurrency is returned when a currency code fails validation.

View Source
var (
	// ErrNoTimestampedResources is returned when no resources have timestamps.
	ErrNoTimestampedResources = errors.New("no resources have created timestamps")
)

Errors for state cost calculation.

View Source
var ErrOverviewValidation = errors.New("overview validation failed")

ErrOverviewValidation is returned when overview type validation fails.

View Source
var ErrResourceValidation = errors.New("resource validation failed")

ErrResourceValidation is returned when resource validation fails.

Functions

func AggregateHealth added in v0.2.5

func AggregateHealth(budgets []*pbc.Budget) pbc.BudgetHealthStatus

AggregateHealth returns the worst-case health status across all budgets. If budgets is empty, returns UNSPECIFIED.

The aggregation uses "worst wins" logic:

  • EXCEEDED > CRITICAL > WARNING > OK > UNSPECIFIED

func AggregateHealthStatuses added in v0.2.6

func AggregateHealthStatuses(statuses []pbc.BudgetHealthStatus) pbc.BudgetHealthStatus

AggregateHealthStatuses returns the worst-case health status from a list of statuses. If statuses is empty, returns UNSPECIFIED.

func ApplyChangesToRows added in v0.3.2

func ApplyChangesToRows(rows []OverviewRow, statusByURN map[string]ResourceStatus)

ApplyChangesToRows updates the Status field of existing OverviewRows in-place using a map of URN → ResourceStatus derived from plan steps. Rows whose URN is not in statusByURN retain their current Status.

This is used for Phase 2 when preview completes after initial TUI display.

Thread safety: The Bubble Tea Update() method is single-threaded by design, so this function is safe to call from OverviewChangesReadyMsg handling in the TUI. Callers in other contexts must ensure no concurrent reads on rows during this call.

No-op if rows is nil.

func ApplyDefaultThresholds added in v0.2.5

func ApplyDefaultThresholds(budget *pbc.Budget) *pbc.Budget

ApplyDefaultThresholds adds default thresholds to a budget if none exist.

This function MUTATES the input budget in place by setting budget.Thresholds to DefaultThresholds() when the budget has no thresholds defined. The same pointer is returned for convenience (allows chaining).

Returns nil if budget is nil. Returns the same budget pointer (unchanged) if thresholds already exist. Returns the same budget pointer (modified) if defaults were applied.

func ApplyDismissalDeltaToRow added in v0.3.2

func ApplyDismissalDeltaToRow(row *OverviewRow, records map[string]*config.DismissalRecord)

ApplyDismissalDeltaToRow appends dismissed and snoozed recommendations to the row's Recommendations slice by looking up each non-active DismissalRecord whose LastKnown.ResourceID matches the row's ResourceID (or URN as fallback).

This enables the count badge to show the total including dismissed recs, e.g. "3(-1)" when 3 total and 1 is dismissed, without listing the dismissed ones in full in the detail view. It is a no-op when records is empty or nil.

func ApplyProjectedPropertiesToRows added in v0.3.3

func ApplyProjectedPropertiesToRows(rows []OverviewRow, projectedPropsByURN map[string]map[string]interface{})

ApplyProjectedPropertiesToRows updates the ProjectedProperties field of existing OverviewRows in-place using a map of URN → projected properties derived from plan steps. Rows whose URN is not in projectedPropsByURN retain their current ProjectedProperties.

No-op if rows is nil.

func ApplyPropertyDiffsToRows added in v0.3.3

func ApplyPropertyDiffsToRows(rows []OverviewRow, diffsByURN map[string][]PropertyDiff)

ApplyPropertyDiffsToRows updates the PropertyDiffs field of existing OverviewRows in-place using a map of URN → []PropertyDiff derived from plan steps. Rows whose URN is not in diffsByURN retain their current PropertyDiffs.

This is used alongside ApplyChangesToRows for Phase 2 when preview completes after initial TUI display.

No-op if rows is nil.

func BuildProjectedPropertiesByURN added in v0.3.3

func BuildProjectedPropertiesByURN(planSteps []PlanStep) map[string]map[string]interface{}

BuildProjectedPropertiesByURN converts a slice of PlanSteps to a map of URN → projected properties. For replace flows where multiple operations may appear for the same URN, the first non-empty projected properties are kept.

func BuildPropertyDiffsByURN added in v0.3.3

func BuildPropertyDiffsByURN(planSteps []PlanStep) map[string][]PropertyDiff

BuildPropertyDiffsByURN converts a slice of PlanSteps to a map of URN → []PropertyDiff. Unlike BuildStatusByURN, this uses diff-specific precedence: for a replace flow (create-replacement + delete-replaced), the create-replacement step carries the PropertyDiffs while delete-replaced has none. Using the status precedence (which favors delete-replaced) would silently drop diffs. Instead, we keep the first non-empty PropertyDiffs found for each URN.

func BuildStatusByURN added in v0.3.2

func BuildStatusByURN(planSteps []PlanStep) map[string]ResourceStatus

BuildStatusByURN converts a slice of PlanSteps to a map of URN → ResourceStatus using the same precedence rules as MergeResourcesForOverview. When the same URN appears with multiple operations (e.g., create-replacement + delete-replaced for a replace), the highest-precedence operation wins.

func CalculateBudgetHealth added in v0.2.5

func CalculateBudgetHealth(budget *pbc.Budget) pbc.BudgetHealthStatus

CalculateBudgetHealth determines the health status of a single budget based on its current utilization percentage.

Thresholds:

  • OK: 0-79%
  • WARNING: 80-89%
  • CRITICAL: 90-99%
  • EXCEEDED: 100%+

Returns UNSPECIFIED if budget has no status or invalid data.

func CalculateBudgetHealthFromPercentage added in v0.2.5

func CalculateBudgetHealthFromPercentage(percentageUsed float64) pbc.BudgetHealthStatus

CalculateBudgetHealthFromPercentage calculates health status from a raw utilization percentage.

Thresholds:

  • OK: 0-79%
  • WARNING: 80-89%
  • CRITICAL: 90-99%
  • EXCEEDED: 100%+

Negative percentages are treated as 0% (OK status).

func CalculateBudgetSummary added in v0.2.3

func CalculateBudgetSummary(ctx context.Context, budgets []*pbc.Budget) *pbc.BudgetSummary

CalculateBudgetSummary computes a BudgetSummary for the given budgets. It sets TotalBudgets to the number of provided budgets and increments the appropriate health counters (BudgetsOk, BudgetsWarning, BudgetsCritical, BudgetsExceeded) based on each budget's status.Health. Budgets with a nil status or a health of UNSPECIFIED are logged and excluded from the health counts. It returns a pointer to the populated BudgetSummary.

func CalculateForecastedPercentage added in v0.2.5

func CalculateForecastedPercentage(forecastedSpend float64, budgetLimit float64) float64

CalculateForecastedPercentage calculates forecasted utilization percentage. Returns 0 if budgetLimit <= 0.

func CalculateForecastedSpend added in v0.2.5

func CalculateForecastedSpend(currentSpend float64, periodStart time.Time, periodEnd time.Time) float64

CalculateForecastedSpend predicts end-of-period spending using linear extrapolation. Uses current time for calculation.

func CalculateForecastedSpendAt added in v0.2.5

func CalculateForecastedSpendAt(
	currentSpend float64,
	periodStart time.Time,
	periodEnd time.Time,
	now time.Time,
) float64

CalculateForecastedSpendAt predicts end-of-period spending relative to a specific time. Exposed for testing.

Formula: forecastedSpend = (currentSpend / elapsedDuration) * totalDuration

Edge cases:

  • Period not started (now < periodStart): returns currentSpend
  • No elapsed time: returns currentSpend (avoids division by zero)
  • Zero current spend: returns 0

func CalculateHealthFromPercentage added in v0.2.6

func CalculateHealthFromPercentage(percentage float64) pbc.BudgetHealthStatus

CalculateHealthFromPercentage calculates health status from a raw utilization percentage. This is a convenience wrapper around CalculateBudgetHealthFromPercentage for scoped budgets.

func CalculateOverallHealth added in v0.2.6

func CalculateOverallHealth(result *ScopedBudgetResult) pbc.BudgetHealthStatus

CalculateOverallHealth calculates the worst-case health status across all scopes. Uses "worst wins" aggregation: EXCEEDED > CRITICAL > WARNING > OK > UNSPECIFIED.

func CalculateProjectedDelta added in v0.3.0

func CalculateProjectedDelta(rows []OverviewRow, currentDayOfMonth int) (float64, string)

CalculateProjectedDelta computes the aggregate change-impact delta for a set of overview rows by examining pending changes.

For each row with a pending operation:

  • Updating/Replacing: delta += projected(after) - projected(current) when baseline projected data is available; otherwise falls back to projected(after) - extrapolated_actual.
  • Creating: delta += projected
  • Deleting: delta -= extrapolated_actual

The currency is taken from the first non-empty cost currency in rows that contributed a delta. Mixed currencies cannot reach this function because upstream returns ErrMixedCurrencies before aggregation.

To match per-row delta behavior, extrapolation always uses ForceExtrapolateActual, including day 1-2.

func CalculateRowDelta added in v0.3.3

func CalculateRowDelta(row OverviewRow, dayOfMonth int) (float64, bool)

CalculateRowDelta computes a per-row cost delta for display purposes.

For resources with pending changes (updating, replacing, creating, deleting), the delta represents the cost impact of the change. For updating/replacing rows, this prefers projected(after) - projected(current) when baseline projected data is available; otherwise it falls back to projected(after) - extrapolated actual.

For active resources, CostDrift.Delta is used if available.

Returns (delta, true) when a meaningful delta can be computed, or (0, false) when no delta is available (e.g., active resource without drift data).

func ConfigBudgetToProto added in v0.3.2

func ConfigBudgetToProto(status *BudgetStatus, name, id string) *pbc.Budget

ConfigBudgetToProto converts a config-evaluated BudgetStatus into a pbc.Budget proto suitable for TUI rendering. This bridges the config-based budget evaluation (DefaultBudgetEngine.Evaluate) with the plugin-based proto representation used by the TUI budget footer.

func ConvertToProto added in v0.2.5

func ConvertToProto(properties map[string]interface{}) map[string]string

ConvertToProto converts a map[string]interface{} to map[string]string for gRPC. It handles nested structures from protobuf Struct conversions by extracting meaningful values from common patterns.

func ConvertValueToString added in v0.2.5

func ConvertValueToString(v interface{}) string

ConvertValueToString converts an interface{} value to a string representation. It handles nested maps and slices that may come from protobuf Struct conversions.

func CountRecommendations added in v0.3.0

func CountRecommendations(results []CostResult) int

CountRecommendations returns the total number of recommendations across all results.

func CountRecsActiveAndDismissed added in v0.3.2

func CountRecsActiveAndDismissed(recs []Recommendation) (int, int)

CountRecsActiveAndDismissed returns the count of active and dismissed/snoozed recommendations in a slice. Active means Status is empty or "Active"; dismissed means Status is "Dismissed" or "Snoozed".

func DefaultThresholds added in v0.2.5

func DefaultThresholds() []*pbc.BudgetThreshold

DefaultThresholds returns the standard thresholds to apply when none configured. Returns: 50% ACTUAL, 80% ACTUAL, 100% ACTUAL.

func DetectPendingChanges added in v0.3.0

func DetectPendingChanges(ctx context.Context, planSteps []PlanStep) (bool, int)

DetectPendingChanges inspects a set of plan steps and reports whether any mutating operations are pending, along with the count.

func EnrichOverviewRow added in v0.3.0

func EnrichOverviewRow(ctx context.Context, row *OverviewRow, eng *Engine, dateRange DateRange)

EnrichOverviewRow enriches a single OverviewRow by fetching actual costs, projected costs, and recommendations from the engine concurrently (up to 4 goroutines per call for updating/replacing rows). When used with EnrichOverviewRows' worker pool, the maximum concurrent goroutines is approximately overviewConcurrencyLimit * 4. Partial failures from actual/projected cost are captured in row.Error with actual cost errors taking precedence; recommendation failures are logged but do not set row.Error.

func ExtractCreatedTimestamp

func ExtractCreatedTimestamp(resource ResourceDescriptor) (time.Time, error)

ExtractCreatedTimestamp extracts the pulumi:created timestamp from resource properties. Returns zero time if the property is missing. Returns an error if the timestamp is present but cannot be parsed.

func ExtractProvider added in v0.2.6

func ExtractProvider(resourceType string) string

ExtractProvider extracts the provider name from a resource type string. Examples:

  • "aws:ec2/instance" -> "aws"
  • "gcp:compute/instance" -> "gcp"
  • "azure:compute/virtualMachine" -> "azure"
  • "unknown" -> "unknown"
  • ":ec2/instance" -> "" (colon at start, no provider)

func ExtractProviderFromResourceType added in v0.3.0

func ExtractProviderFromResourceType(resourceType string) string

ExtractProviderFromResourceType returns the provider name extracted from resourceType, or an empty string if no provider can be determined.

func FilterBudgets added in v0.2.3

func FilterBudgets(budgets []*pbc.Budget, filter *pbc.BudgetFilter) []*pbc.Budget

FilterBudgets returns the subset of budgets that satisfy the given BudgetFilter. If filter is nil, the original budgets slice is returned unchanged.

func FilterBudgetsByProvider added in v0.2.5

func FilterBudgetsByProvider(ctx context.Context, budgets []*pbc.Budget, providers []string) []*pbc.Budget

FilterBudgetsByProvider filters budgets by provider name(s).

Behavior:

  • Case-insensitive matching ("aws" matches "AWS", "Aws", etc.)
  • OR logic: budget matches if it matches ANY provider in the list
  • Empty providers list returns all budgets (no filtering)
  • Returns empty slice if no budgets match

func FilterBudgetsByTags added in v0.3.0

func FilterBudgetsByTags(ctx context.Context, budgets []*pbc.Budget, tags map[string]string) []*pbc.Budget

FilterBudgetsByTags filters budgets by metadata tags.

Behavior:

  • Case-sensitive matching for both keys and values
  • AND logic: budget must match ALL specified tags
  • Supports glob patterns in values (e.g., "prod-*" matches "prod-us")
  • Empty tags map returns all budgets (no filtering)
  • Returns empty slice if no budgets match

func FindEarliestCreatedTimestamp

func FindEarliestCreatedTimestamp(resources []ResourceDescriptor) (time.Time, error)

FindEarliestCreatedTimestamp finds the earliest Created timestamp from a set of resources. This is used to auto-detect the --from date when using --pulumi-state. Returns ErrNoTimestampedResources if no resources have timestamps.

func ForceExtrapolateActual added in v0.3.3

func ForceExtrapolateActual(row OverviewRow, dayOfMonth int) float64

ForceExtrapolateActual extrapolates the MTD actual cost to a full month, even on early-month days (day 1-2). Used for resources with PropertyDiffs where the config change is a known signal and an approximate delta is more useful than suppressing it entirely.

func FormatOverviewCurrency added in v0.3.0

func FormatOverviewCurrency(amount float64) string

FormatOverviewCurrency formats an amount as "$X,XXX.XX". Negative values are formatted as "-$X,XXX.XX".

func FormatOverviewDelta added in v0.3.0

func FormatOverviewDelta(amount float64) string

FormatOverviewDelta formats a delta amount with a +/- prefix. Positive values get "+$", negative get "-$", zero gets "$0.00".

func FormatPeriod

func FormatPeriod(from, to time.Time) string

FormatPeriod formats a time duration into a human-readable period string.

func FormatRecommendationCount added in v0.3.0

func FormatRecommendationCount(count int) string

FormatRecommendationCount returns the recommendation count as a string for table display. FormatRecommendationCount returns "-" when count is zero; otherwise it returns the decimal string representation of count.

func GetBaselineProjectedMonthlyCost added in v0.3.3

func GetBaselineProjectedMonthlyCost(row OverviewRow) (float64, bool)

GetBaselineProjectedMonthlyCost extracts current-state projected monthly cost.

func GetExtrapolatedActual added in v0.3.3

func GetExtrapolatedActual(row OverviewRow, dayOfMonth int) float64

GetExtrapolatedActual extrapolates the MTD actual cost to a full month. If the day of month is too early for reliable extrapolation, returns the raw MTD cost.

Note: This function uses defaultDaysPerMonth (30) for extrapolation, which differs from CalculateCostDrift that uses the actual daysInMonth (28-31). This is intentional: delta calculations use a standardised month length for consistent comparisons, while drift calculations use calendar-accurate month length for precision.

func GetProjectedMonthlyCost added in v0.3.3

func GetProjectedMonthlyCost(row OverviewRow) float64

GetProjectedMonthlyCost safely extracts the projected monthly cost from a row.

func HealthStatusLabel added in v0.3.2

func HealthStatusLabel(h pbc.BudgetHealthStatus) string

HealthStatusLabel converts a BudgetHealthStatus enum to its short label. Labels produced are "OK", "WARNING", "CRITICAL", "EXCEEDED", and "UNKNOWN" for unspecified or unrecognized values.

func IdentifyCriticalScopes added in v0.2.6

func IdentifyCriticalScopes(result *ScopedBudgetResult) []string

IdentifyCriticalScopes returns the scope identifiers that have CRITICAL or EXCEEDED status. Results are sorted for deterministic output.

func IsExternalResource

func IsExternalResource(resource ResourceDescriptor) bool

IsExternalResource checks if a resource was imported (not created by Pulumi). Returns true if the pulumi:external property is set to "true" or true.

func MatchesProvider added in v0.2.5

func MatchesProvider(budget *pbc.Budget, providers []string) bool

MatchesProvider checks if a budget matches any of the given providers. Returns true if providers is empty (no filtering). Returns false if budget is nil.

func MatchesTags

func MatchesTags(resource ResourceDescriptor, tags map[string]string) bool

MatchesTags checks if resource properties match the specified tag filters.

func PopulateComputedDeltas added in v0.3.3

func PopulateComputedDeltas(rows []OverviewRow, dayOfMonth int)

PopulateComputedDeltas computes and stores per-row delta values. Must be called after enrichment, before any rendering. All renderers (table, JSON, NDJSON, TUI) then read row.ComputedDelta instead of independently recomputing deltas — guaranteeing consistency.

func RenderActualCostJSON

func RenderActualCostJSON(writer io.Writer, results []CostResult, showConfidence bool) error

RenderActualCostJSON renders actual cost results as a JSON array with optional confidence field.

func RenderActualCostNDJSON

func RenderActualCostNDJSON(writer io.Writer, results []CostResult, showConfidence bool) error

RenderActualCostNDJSON renders actual cost results as NDJSON with optional confidence field.

func RenderActualCostResults

func RenderActualCostResults(writer io.Writer, format OutputFormat, results []CostResult, showConfidence bool) error

RenderActualCostResults renders actual cost results using the specified output format. It dispatches to the table, pretty JSON, or NDJSON renderer based on the provided format. Supported formats are OutputTable, OutputJSON, and OutputNDJSON; an error is returned for unsupported formats. Parameters:

  • format: the desired OutputFormat (e.g., OutputTable, OutputJSON, OutputNDJSON).
  • results: the slice of CostResult values to render.

RenderActualCostResults dispatches actual cost results to the renderer that corresponds to the provided output format.

The format parameter selects the output format and must be one of OutputTable, OutputJSON, or OutputNDJSON. The results parameter is the slice of actual cost results to be rendered.

It returns an error if the selected renderer fails or if the format is unsupported.

func RenderCrossProviderAggregation

func RenderCrossProviderAggregation(
	writer io.Writer,
	format OutputFormat,
	aggregations []CrossProviderAggregation,
	groupBy GroupBy,
) error

RenderCrossProviderAggregation renders cross-provider aggregation data using the specified output format. It supports table, JSON, and NDJSON formats and dispatches to the corresponding renderer.

Parameters:

  • format: desired output format (e.g., OutputTable, OutputJSON, OutputNDJSON).
  • aggregations: slice of cross-provider aggregation records to render.
  • groupBy: grouping granularity used for table output (e.g., daily or monthly).

RenderCrossProviderAggregation dispatches cross-provider aggregation data to the renderer corresponding to the given OutputFormat.

The `format` parameter selects the output renderer: table, JSON, or NDJSON. `aggregations` is the slice of cross-provider aggregation records to render. `groupBy` controls the temporal grouping used by table renderers (for example, daily vs monthly).

It returns an error if the specified format is not supported or if the selected renderer fails.

func RenderOverviewAsJSON added in v0.3.0

func RenderOverviewAsJSON(
	ctx context.Context, w io.Writer, rows []OverviewRow,
	stackCtx StackContext, budgetResult *BudgetResult,
) error

RenderOverviewAsJSON renders the overview rows as a structured JSON object with metadata, resource array, summary, optional budgets, and errors.

Parameters:

  • ctx: context for logging and tracing; threaded to CalculateBudgetHealthResults.
  • w: destination writer for the JSON output.
  • rows: slice of OverviewRow to include as resources; may be nil (will serialize as an empty array).
  • stackCtx: stack context and metadata used in the output; GeneratedAt will be populated with the current time if zero.
  • budgetResult: optional budget data; when non-nil and non-empty, converted budgets are included in the `budgets` field. May be nil, in which case no budget entries are emitted.

Returns an error if aggregating totals fails or if encoding/writing the JSON output fails.

func RenderOverviewAsNDJSON added in v0.3.0

func RenderOverviewAsNDJSON(w io.Writer, rows []OverviewRow) error

RenderOverviewAsNDJSON renders each overview row as a separate JSON line with no metadata wrapper or summary. ComputedDelta must be populated before calling this function (via PopulateComputedDeltas).

func RenderOverviewAsTable added in v0.3.0

func RenderOverviewAsTable(w io.Writer, rows []OverviewRow, stackCtx StackContext) error

RenderOverviewAsTable writes a formatted ASCII table of the overview rows.

func RenderResults

func RenderResults(writer io.Writer, format OutputFormat, results []CostResult) error

RenderResults renders the given cost results using the specified output format. RenderResults aggregates the results for table and JSON summary outputs, emits NDJSON as individual records, and writes the output to the specified writer. The writer parameter specifies where the output should be written. The format parameter selects one of the supported OutputFormat values (OutputTable, OutputJSON, OutputNDJSON). The results parameter is the slice of CostResult to be rendered. It returns an error if rendering fails or if the provided format is unsupported.

func RenderResultsWithContext added in v0.2.6

func RenderResultsWithContext(ctx context.Context, writer io.Writer, format OutputFormat, results []CostResult) error

RenderResultsWithContext renders the given cost results using the specified output format with context. The ctx parameter enables trace ID propagation for debug logging.

func StatusIcon added in v0.3.0

func StatusIcon(status ResourceStatus) string

StatusIcon returns a single-character icon for a ResourceStatus.

func UpdateBudgetForecast added in v0.2.5

func UpdateBudgetForecast(ctx context.Context, budget *pbc.Budget, periodStart, periodEnd time.Time)

UpdateBudgetForecast updates a budget's status with calculated forecast. Modifies budget.Status in place. Creates Status if nil.

func ValidateBudget added in v0.2.5

func ValidateBudget(budget *pbc.Budget) error

ValidateBudget validates required budget fields.

func ValidateBudgetCurrency added in v0.2.5

func ValidateBudgetCurrency(budget *pbc.Budget) error

ValidateBudgetCurrency validates the currency in a budget's amount. Returns nil if valid or amount is nil, error otherwise.

func ValidateCurrency added in v0.2.5

func ValidateCurrency(code string) error

ValidateCurrency checks if a currency code is valid ISO 4217 format.

Valid format: exactly 3 uppercase letters (A-Z). Examples: "USD", "EUR", "GBP" are valid; "usd", "US", "USDD" are invalid.

Returns nil if valid, error with descriptive message if invalid.

func ValidateFilter

func ValidateFilter(filter string) error

ValidateFilter validates the syntax of a filter expression. It returns an error if the filter is malformed: missing '=' separator, or if either the key or value is empty (for example: `type=aws:ec2/instance` or `tag:env=prod`).

Types

type ActualCostData added in v0.3.0

type ActualCostData struct {
	MTDCost   float64            `json:"mtdCost"`
	Currency  string             `json:"currency"`
	Period    DateRange          `json:"period"`
	Breakdown map[string]float64 `json:"breakdown,omitempty"`
}

ActualCostData holds month-to-date actual cost information for a resource.

func (*ActualCostData) Validate added in v0.3.0

func (a *ActualCostData) Validate() error

Validate checks that the ActualCostData fields are consistent.

type ActualCostRequest

type ActualCostRequest struct {
	Resources          []ResourceDescriptor
	From               time.Time
	To                 time.Time
	Adapter            string
	GroupBy            string
	Tags               map[string]string
	EstimateConfidence bool // Show confidence level in output
	FallbackEstimate   bool // When true, include $0 placeholder results for resources with no plugin data
}

ActualCostRequest contains parameters for querying historical actual costs with filtering and grouping.

type AggregatedResults

type AggregatedResults struct {
	Summary   CostSummary  `json:"summary"`
	Resources []CostResult `json:"resources"`
}

AggregatedResults contains cost results with summary and aggregation data.

func AggregateResults

func AggregateResults(results []CostResult) *AggregatedResults

AggregateResults aggregates a slice of CostResult into an AggregatedResults summary.

AggregateResults computes total monthly and hourly costs, and builds maps of monthly costs grouped by provider, service, and adapter. The returned AggregatedResults contains the original Resources slice and a CostSummary populated with:

  • Currency: taken from the first result (or defaultCurrency when input is empty).
  • TotalMonthly and TotalHourly: summed across all results.
  • ByProvider, ByService, ByAdapter: maps of monthly totals keyed by provider, service, and adapter respectively.

If the input slice is empty, AggregateResults returns an AggregatedResults with an AggregateResults aggregates a slice of CostResult into an AggregatedResults summary.

If results is empty, it returns an AggregatedResults with zero totals, empty maps, an empty Resources slice, and Currency set to defaultCurrency. For a non-empty input, totals (TotalMonthly, TotalHourly) are summed across results, ByProvider, ByService, and ByAdapter maps accumulate monthly totals, Currency is taken from the first result, and Resources contains the original input slice.

type BudgetAllocation added in v0.2.6

type BudgetAllocation struct {
	// ResourceID is the unique identifier of the resource.
	ResourceID string `json:"resource_id,omitempty"`

	// ResourceType is the full type string (e.g., "aws:ec2/instance").
	ResourceType string `json:"resource_type"`

	// Provider is the extracted provider from the resource type.
	Provider string `json:"provider,omitempty"`

	// Cost is the resource's cost that was allocated.
	Cost float64 `json:"cost"`

	// AllocatedScopes lists all scopes that received this resource's cost.
	// Format: "global", "provider:aws", "tag:team:platform", "type:aws:ec2/instance"
	AllocatedScopes []string `json:"allocated_scopes,omitempty"`

	// MatchedTags lists all tags that matched tag budgets for this resource.
	// If multiple matched, only the highest priority receives cost.
	MatchedTags []string `json:"matched_tags,omitempty"`

	// SelectedTagBudget is the tag budget that received the cost allocation.
	// Empty if no tag budget matched or no tag budgets configured.
	SelectedTagBudget string `json:"selected_tag_budget,omitempty"`

	// Warnings contains any warnings generated during allocation.
	// e.g., "overlapping tag budgets without priority"
	Warnings []string `json:"warnings,omitempty"`
}

BudgetAllocation tracks cost allocation for a single resource.

type BudgetEngine added in v0.2.4

type BudgetEngine interface {
	// Evaluate compares current spend against the configured budget and alerts.
	// It returns a BudgetStatus or an error if evaluation fails.
	Evaluate(budget config.BudgetConfig, currentSpend float64, currency string) (*BudgetStatus, error)
}

BudgetEngine defines the interface for budget evaluation operations.

type BudgetFilterOptions added in v0.2.5

type BudgetFilterOptions struct {
	Providers []string          // Filter by provider names (case-insensitive, OR logic)
	Tags      map[string]string // Filter by metadata tags (case-sensitive, AND logic, supports glob patterns)
}

BudgetFilterOptions contains criteria for filtering budgets.

type BudgetHealthResult added in v0.2.5

type BudgetHealthResult struct {
	BudgetID     string                 `json:"budgetID"`
	BudgetName   string                 `json:"budgetName"`
	Provider     string                 `json:"provider"`
	Health       pbc.BudgetHealthStatus `json:"-"` // serialized via custom MarshalJSON
	Utilization  float64                `json:"utilization"`
	Forecasted   float64                `json:"forecasted"`
	Currency     string                 `json:"currency"`
	Limit        float64                `json:"limit"`
	CurrentSpend float64                `json:"currentSpend"`
}

BudgetHealthResult contains health assessment for a single budget.

func CalculateBudgetHealthResults added in v0.2.5

func CalculateBudgetHealthResults(ctx context.Context, budgets []*pbc.Budget) []BudgetHealthResult

CalculateBudgetHealthResults calculates health results for multiple budgets. It returns a BudgetHealthResult for each budget with all relevant health information.

func (BudgetHealthResult) MarshalJSON added in v0.3.2

func (b BudgetHealthResult) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler, converting Health to a string label.

func (*BudgetHealthResult) UnmarshalJSON added in v0.3.2

func (b *BudgetHealthResult) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler, parsing Health from a string label. Returns an error for unrecognized non-empty health labels to prevent silent data loss.

type BudgetHealthSummary added in v0.3.2

type BudgetHealthSummary struct {
	OverallHealth string `json:"overallHealth,omitempty"`
	TotalBudgets  int    `json:"totalBudgets"`
	CriticalCount int    `json:"criticalCount"`
}

BudgetHealthSummary provides a lightweight budget health summary for StackContext JSON serialization. It captures the overall health status, total number of budgets evaluated, and count of critical/exceeded budgets.

type BudgetResult added in v0.2.5

type BudgetResult struct {
	Budgets []*pbc.Budget          // Filtered budgets with health status
	Summary *ExtendedBudgetSummary // Aggregated statistics
	Errors  []error                // Any errors during processing
}

BudgetResult contains the complete budget health response.

func BuildConfigBudgetResult added in v0.3.2

func BuildConfigBudgetResult(
	ctx context.Context,
	budgetsCfg *config.BudgetsConfig,
	totalProjectedCost float64,
) *BudgetResult

BuildConfigBudgetResult evaluates the config-based global budget against a total projected cost and returns a BudgetResult suitable for TUI rendering. Returns nil when no config budget is enabled.

type BudgetStatus added in v0.2.4

type BudgetStatus struct {
	// Budget is the original budget configuration.
	Budget config.BudgetConfig
	// CurrentSpend is the actual spend amount.
	CurrentSpend float64
	// Percentage is the percentage of budget consumed (0-100+).
	Percentage float64
	// ForecastedSpend is the estimated total spend by end of period (0 if forecasting not applicable).
	ForecastedSpend float64
	// ForecastPercentage is the percentage of budget forecasted to be consumed.
	ForecastPercentage float64
	// Alerts contains the status of each configured alert threshold.
	Alerts []ThresholdStatus
	// Currency is the currency of the spend (validated to match budget).
	Currency string
}

BudgetStatus represents the result of evaluating a budget against current spend.

func (*BudgetStatus) CappedPercentage added in v0.2.4

func (s *BudgetStatus) CappedPercentage() float64

CappedPercentage returns the percentage capped at 100 for display purposes. The actual Percentage field may exceed 100 for over-budget scenarios.

func (*BudgetStatus) ExitReason added in v0.2.5

func (s *BudgetStatus) ExitReason() string

ExitReason returns a human-readable reason for the exit code. Returns an empty string if no exit should occur. A zero exit code represents a warning-only condition. Used for debug logging and user feedback.

func (*BudgetStatus) GetExceededAlerts added in v0.2.4

func (s *BudgetStatus) GetExceededAlerts() []ThresholdStatus

GetExceededAlerts returns all alerts with EXCEEDED status.

func (*BudgetStatus) GetExitCode added in v0.2.5

func (s *BudgetStatus) GetExitCode() int

GetExitCode returns the appropriate exit code based on budget evaluation. Returns 0 if no exit should occur (ShouldExit() is false). Returns the configured exit code if a threshold is exceeded and ExitOnThreshold is enabled. Note: A zero exit code representing a warning-only condition is explicitly allowed.

func (*BudgetStatus) GetHighestExceededThreshold added in v0.2.4

func (s *BudgetStatus) GetHighestExceededThreshold() float64

GetHighestExceededThreshold returns the highest threshold that has been exceeded. Returns 0 if no thresholds are exceeded.

func (*BudgetStatus) HasApproachingAlerts added in v0.2.4

func (s *BudgetStatus) HasApproachingAlerts() bool

HasApproachingAlerts returns true if any alert has APPROACHING status.

func (*BudgetStatus) HasExceededAlerts added in v0.2.4

func (s *BudgetStatus) HasExceededAlerts() bool

HasExceededAlerts returns true if any alert has EXCEEDED status.

func (*BudgetStatus) IsForecastOverBudget added in v0.2.4

func (s *BudgetStatus) IsForecastOverBudget() bool

IsForecastOverBudget returns true if forecasted spend exceeds the budget amount.

func (*BudgetStatus) IsOverBudget added in v0.2.4

func (s *BudgetStatus) IsOverBudget() bool

IsOverBudget returns true if current spend exceeds the budget amount.

func (*BudgetStatus) ShouldExit added in v0.2.5

func (s *BudgetStatus) ShouldExit() bool

ShouldExit returns true if the CLI should "exit" due to an exceeded budget threshold. It returns true when ExitOnThreshold is enabled in the budget config and any threshold has been exceeded, regardless of whether the configured exit code is zero (warning-only).

type Confidence

type Confidence string

Confidence represents the reliability level of a cost estimate.

Confidence levels help users understand how reliable a cost estimate is:

  • HIGH: Backed by real billing data from cloud provider APIs
  • MEDIUM: Calculated from runtime using Pulumi state timestamps
  • LOW: Estimated for imported resources where creation time is unknown
const (
	// ConfidenceHigh indicates the cost is backed by real billing data.
	// This is the most reliable estimate, coming from actual cloud billing APIs.
	ConfidenceHigh Confidence = "high"

	// ConfidenceMedium indicates the cost is calculated from runtime.
	// The estimate uses hourly_rate × runtime from Pulumi state timestamps.
	// Reliable for resources created by Pulumi.
	ConfidenceMedium Confidence = "medium"

	// ConfidenceLow indicates reduced reliability for imported resources.
	// The "Created" timestamp reflects import time, not actual resource creation,
	// so runtime calculations may significantly underestimate actual usage.
	ConfidenceLow Confidence = "low"

	// ConfidenceUnknown indicates confidence could not be determined.
	// Used when no cost data is available or for error cases.
	ConfidenceUnknown Confidence = ""
)

Confidence level constants.

func DetermineConfidence

func DetermineConfidence(hasBillingData, isExternal bool) Confidence

DetermineConfidence calculates the appropriate confidence level based on data source.

The logic is:

  • HIGH: hasBillingData=true (real billing API data overrides all other factors)
  • MEDIUM: hasBillingData=false, isExternal=false (runtime estimate, non-imported)
  • LOW: hasBillingData=false, isExternal=true (runtime estimate, imported resource)

Parameters:

  • hasBillingData: true if cost came from actual billing APIs (e.g., AWS Cost Explorer)
  • isExternal: true if the resource was imported (pulumi:external=true)

func DetermineConfidenceFromResult

func DetermineConfidenceFromResult(result CostResult, isExternal bool) Confidence

DetermineConfidenceFromResult calculates confidence level from a CostResult.

Uses TotalCost > 0 as indicator of billing data (from GetActualCost API calls). The isExternal parameter indicates if the resource was imported.

Parameters:

  • result: CostResult containing cost data
  • isExternal: true if the resource was imported (pulumi:external=true)

func (Confidence) DisplayLabel

func (c Confidence) DisplayLabel() string

DisplayLabel returns an uppercase label for UI display. Returns "-" for unknown confidence to indicate missing data.

func (Confidence) IsValid

func (c Confidence) IsValid() bool

IsValid returns true if the Confidence value is valid.

func (Confidence) String

func (c Confidence) String() string

String returns the string representation of the Confidence level.

type ContextKey

type ContextKey string

ContextKey is a custom type for context keys to avoid collisions.

const (
	// ContextKeyUtilization is the context key for passing utilization rate.
	ContextKeyUtilization ContextKey = "utilization"
)

type CostDelta added in v0.3.0

type CostDelta struct {
	// Property is the name of the property that was changed
	Property string `json:"property"`

	// OriginalValue is the value before the change
	OriginalValue string `json:"originalValue"`

	// NewValue is the value after the change
	NewValue string `json:"newValue"`

	// CostChange is the monthly cost difference
	// Positive = increase, negative = savings
	CostChange float64 `json:"costChange"`
}

CostDelta represents the cost impact of changing a single property.

Usage Examples:

delta := CostDelta{
	Property:      "instanceType",
	OriginalValue: "t3.micro",
	NewValue:      "m5.large",
	CostChange:    65.70, // Positive means cost increase
}

type CostDriftData added in v0.3.0

type CostDriftData struct {
	ExtrapolatedMonthly float64 `json:"extrapolatedMonthly"`
	Projected           float64 `json:"projected"`
	Delta               float64 `json:"delta"`
	PercentDrift        float64 `json:"percentDrift"`
	IsWarning           bool    `json:"isWarning"`
}

CostDriftData captures the difference between extrapolated actual spend and the projected monthly cost. It is only populated when the absolute drift exceeds the warning threshold (10%).

func CalculateCostDrift added in v0.3.0

func CalculateCostDrift(actualMTD, projected float64, dayOfMonth, daysInMonth int) (*CostDriftData, error)

CalculateCostDrift computes the cost drift between an extrapolated month-to-date actual spend and the projected monthly cost normalized to the current calendar month.

Parameters:

  • actualMTD: month-to-date actual cost for the resource.
  • projected: projected monthly cost expressed on the package's canonical month basis.
  • dayOfMonth: current day of the month (1-31); used to extrapolate actualMTD to a full month.
  • daysInMonth: total days in the current calendar month (28-31); used to normalize the projection.

Return values:

  • (*CostDriftData, nil) when drift exceeds the warning threshold (10%).
  • (nil, nil) when drift is not meaningful: both sides zero, one side zero (new/deleted resource), or drift within the warning threshold.
  • (nil, error) with ErrOverviewValidation when inputs are invalid: dayOfMonth < driftMinDay, daysInMonth outside 28..31, or dayOfMonth > daysInMonth.

func CalculateCostDriftWithElapsedDays added in v0.3.3

func CalculateCostDriftWithElapsedDays(
	actualMTD, projected, elapsedDays float64,
	daysInMonth int,
) (*CostDriftData, error)

CalculateCostDriftWithElapsedDays computes run-rate drift using a fractional elapsed day denominator. This avoids early-month bias from integer dayOfMonth rounding (for example, March 3 at 06:00 should use 2.25 elapsed days, not 3).

Parameters:

  • actualMTD: actual cost accumulated over the elapsed window.
  • projected: projected monthly cost expressed on the canonical 730h basis.
  • elapsedDays: elapsed runtime window in days; may be fractional.
  • daysInMonth: total days in the reference calendar month (28-31).

Return values match CalculateCostDrift.

func (*CostDriftData) Validate added in v0.3.0

func (c *CostDriftData) Validate() error

Validate checks that the CostDriftData fields are consistent.

type CostResult

type CostResult struct {
	ResourceType   string                          `json:"resourceType"`
	ResourceID     string                          `json:"resourceId"`
	Adapter        string                          `json:"adapter"`
	Currency       string                          `json:"currency"`
	Monthly        float64                         `json:"monthly"`
	Hourly         float64                         `json:"hourly"`
	Notes          string                          `json:"notes"`
	Breakdown      map[string]float64              `json:"breakdown"`
	Sustainability map[string]SustainabilityMetric `json:"sustainability,omitempty"`
	// Recommendations contains cost optimization suggestions from plugins.
	// This field is populated when plugins provide actionable recommendations
	// alongside cost estimates (e.g., right-sizing, termination suggestions).
	Recommendations []Recommendation `json:"recommendations,omitempty"`
	// Error contains a machine-readable structured error for JSON/NDJSON output.
	// When non-nil, callers should prefer this structured error for programmatic
	// handling. The Notes field may still contain ERROR: or VALIDATION: prefixes
	// for backward compatibility (see internal/proto/adapter.go).
	Error *StructuredError `json:"error,omitempty"`
	// Actual cost specific fields
	TotalCost  float64   `json:"totalCost,omitempty"`
	DailyCosts []float64 `json:"dailyCosts,omitempty"`
	CostPeriod string    `json:"costPeriod,omitempty"`
	StartDate  time.Time `json:"startDate,omitempty"`
	EndDate    time.Time `json:"endDate,omitempty"`

	// Delta represents the cost change (trend) compared to a baseline, in the same currency as the cost.
	// Positive values indicate cost increase, negative values indicate decrease.
	// The baseline depends on context (e.g., previous period for actual costs, budget for projected costs).
	Delta float64 `json:"delta,omitempty"`

	// Confidence indicates the reliability level of this cost estimate.
	// HIGH: Real billing data from cloud APIs
	// MEDIUM: Runtime-based estimate from Pulumi timestamps
	// LOW: Imported resource (timestamp may be inaccurate)
	Confidence Confidence `json:"confidence,omitempty"`

	// ExpiresAt is a caching hint from the plugin indicating when this cost data
	// becomes stale. When non-nil, the engine uses it to calculate a custom cache
	// TTL via cache.CalculatePluginTTL instead of the store default. Nil means
	// the plugin did not provide a hint and the default TTL applies.
	ExpiresAt *time.Time `json:"expiresAt,omitempty"`
}

CostResult contains the calculated cost information for a single resource.

func AggregateResultsInternal

func AggregateResultsInternal(results []CostResult, groupName string) CostResult

AggregateResultsInternal aggregates multiple CostResult entries into a single CostResult. AggregateResultsInternal sums numeric totals (Monthly, Hourly, TotalCost), merges breakdown maps by summing values for matching keys, and combines daily cost series by aligning indices and summing per-day values. The returned CostResult preserves the Currency, StartDate, EndDate and CostPeriod from the first entry in results, sets ResourceType to groupName, ResourceID to "aggregated-<N>-resources", Adapter to "aggregated", and Notes to indicate the number of AggregateResultsInternal aggregates multiple CostResult entries into a single CostResult that represents the grouped resources.

AggregateResultsInternal sums numeric totals (Monthly, Hourly, TotalCost), merges Breakdown maps by summing values for matching keys, and aligns/sums DailyCosts across results, extending the slice to match the longest daily-cost series. The returned result preserves Currency, StartDate, EndDate, and CostPeriod from the first entry, and sets ResourceType to the provided groupName, ResourceID to "aggregated-N-resources", Adapter to "aggregated", and Notes to indicate the number of aggregated resources.

Parameters:

  • results: slice of CostResult entries to aggregate. If empty, an empty CostResult is returned.
  • groupName: name to assign to the aggregated ResourceType.

Returns:

The aggregated CostResult combining all provided entries.

func MergeRecommendations

func MergeRecommendations(
	costs []CostResult,
	recommendations []Recommendation,
) []CostResult

MergeRecommendations merges recommendations into CostResults by matching ResourceID.

type CostResultWithErrors

type CostResultWithErrors struct {
	Results []CostResult
	Errors  []ErrorDetail
}

CostResultWithErrors wraps results and any errors encountered during cost calculation.

func (*CostResultWithErrors) ErrorSummary

func (c *CostResultWithErrors) ErrorSummary() string

ErrorSummary returns a human-readable summary of errors. Truncates the output after maxErrorsToDisplay errors to keep it readable.

func (*CostResultWithErrors) HasErrors

func (c *CostResultWithErrors) HasErrors() bool

HasErrors returns true if any errors were encountered during cost calculation.

type CostSummary

type CostSummary struct {
	TotalMonthly float64            `json:"totalMonthly"`
	TotalHourly  float64            `json:"totalHourly"`
	Currency     string             `json:"currency"`
	ByProvider   map[string]float64 `json:"byProvider"`
	ByService    map[string]float64 `json:"byService"`
	ByAdapter    map[string]float64 `json:"byAdapter"`
	Resources    []CostResult       `json:"resources"`
}

CostSummary provides aggregated cost totals grouped by provider, service, and adapter.

type CrossProviderAggregation

type CrossProviderAggregation struct {
	Period    string             `json:"period"`    // Date (2024-01-01) or Month (2024-01)
	Providers map[string]float64 `json:"providers"` // Provider name -> cost
	Total     float64            `json:"total"`     // Total cost for this period
	Currency  string             `json:"currency"`  // Currency for all costs
}

CrossProviderAggregation represents daily/monthly cost aggregation across providers.

This type enables comprehensive cost analysis across multiple cloud providers (AWS, Azure, GCP, etc.) with time-based aggregation for trend analysis and cost optimization insights.

Usage Examples:

// Daily aggregation showing multi-provider costs
agg := CrossProviderAggregation{
	Period: "2024-01-15",
	Providers: map[string]float64{
		"aws":   245.67,
		"azure": 156.23,
		"gcp":   89.45,
	},
	Total:    491.35,
	Currency: "USD",
}

// Monthly aggregation for cost trending
agg := CrossProviderAggregation{
	Period: "2024-01",
	Providers: map[string]float64{
		"aws":   7620.15,
		"azure": 4843.67,
	},
	Total:    12463.82,
	Currency: "USD",
}

func CreateCrossProviderAggregation

func CreateCrossProviderAggregation(
	results []CostResult,
	groupBy GroupBy,
) ([]CrossProviderAggregation, error)

CreateCrossProviderAggregation creates daily/monthly cross-provider aggregation from cost results.

This function aggregates costs across multiple cloud providers and time periods, enabling comprehensive cost analysis and reporting. It supports both daily and monthly aggregation with built-in currency validation and input validation.

Parameters:

  • results: Slice of CostResult objects containing cost data from various providers. Each result must have consistent currency and valid date ranges.
  • groupBy: Must be GroupByDaily or GroupByMonthly. Other grouping types will return ErrInvalidGroupBy.

Returns:

  • []CrossProviderAggregation: Sorted aggregations by time period, each containing:
  • Period: Date ("2006-01-02") for daily or month ("2006-01") for monthly
  • Providers: Map of provider names to their costs for that period
  • Total: Sum of all provider costs for the period
  • Currency: Consistent currency across all results
  • error: Various validation errors:
  • ErrEmptyResults: If results slice is empty
  • ErrInvalidGroupBy: If groupBy is not time-based (daily/monthly)
  • ErrMixedCurrencies: If results contain different currencies
  • ErrInvalidDateRange: If any result has EndDate before StartDate

Usage Examples:

// Daily aggregation across AWS and Azure costs
results := []CostResult{
	{ResourceType: "aws:ec2:Instance", TotalCost: 100.0, Currency: "USD", StartDate: jan1, EndDate: jan2},
	{ResourceType: "azure:compute:VirtualMachine", TotalCost: 150.0, Currency: "USD", StartDate: jan1, EndDate: jan2},
}
aggregations, err := CreateCrossProviderAggregation(results, GroupByDaily)
if err != nil {
	return fmt.Errorf("aggregation failed: %w", err)
}
// Result: [{Period: "2024-01-01", Providers: {"aws": 100.0, "azure": 150.0}, Total: 250.0, Currency: "USD"}]

// Monthly aggregation for cost trend analysis
aggregations, err := CreateCrossProviderAggregation(results, GroupByMonthly)
// Result: [{Period: "2024-01", Providers: {"aws": 3100.0, "azure": 4650.0}, Total: 7750.0, Currency: "USD"}]

Error Scenarios:

  • Mixed currencies: Results with different currencies (USD vs EUR) will fail validation
  • Invalid grouping: Using GroupByResource or GroupByType will return ErrInvalidGroupBy
  • Date validation: Results with EndDate before StartDate will fail
  • Empty input: Empty results slice returns ErrEmptyResults

Performance Considerations:

  • Efficient for datasets up to 10,000 cost results
  • Memory usage scales linearly with unique time periods

CreateCrossProviderAggregation groups cost results into time-based cross-provider aggregations.

CreateCrossProviderAggregation validates the input results and the provided time-based grouping, ensures all results use a single currency, then groups costs by period (daily or monthly) and provider. It produces a sorted slice of CrossProviderAggregation entries where each entry contains the period string, per-provider totals, the period total, and the common currency.

Parameters:

  • results: the list of CostResult entries to aggregate.
  • groupBy: a time-based GroupBy value (daily or monthly) that determines the period granularity.

Returns:

  • a sorted slice of CrossProviderAggregation entries ordered by period.

CreateCrossProviderAggregation groups a slice of CostResult entries by the specified time period and returns cross-provider aggregations for each period sorted by period key.

The function accepts:

  • results: the cost results to aggregate (must be non-empty).
  • groupBy: the time-based grouping to apply (daily or monthly).

It validates inputs, enforces that all results use a single currency (empty currency values are treated as the package default), and groups costs by period and provider. Each returned CrossProviderAggregation contains the period key, per-provider totals, the overall total, and the resolved currency.

Possible errors:

  • ErrEmptyResults if `results` is empty.
  • ErrInvalidGroupBy if `groupBy` is not a time-based grouping.
  • ErrInvalidDateRange if any result has an EndDate not after its StartDate.

- the results contain more than one distinct currency (ErrMixedCurrencies).

type DateRange added in v0.3.0

type DateRange struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

DateRange represents a half-open time interval [Start, End).

func (DateRange) Validate added in v0.3.0

func (d DateRange) Validate() error

Validate checks that the DateRange is well-formed.

type DefaultBudgetEngine added in v0.2.4

type DefaultBudgetEngine struct {
	// contains filtered or unexported fields
}

DefaultBudgetEngine implements BudgetEngine with standard evaluation logic.

func NewBudgetEngine added in v0.2.4

func NewBudgetEngine() *DefaultBudgetEngine

NewBudgetEngine returns a DefaultBudgetEngine configured to use time.Now as the time source.

func NewBudgetEngineWithTime added in v0.2.4

func NewBudgetEngineWithTime(nowFunc func() time.Time) *DefaultBudgetEngine

NewBudgetEngineWithTime creates a new DefaultBudgetEngine with a custom time function. NewBudgetEngineWithTime creates a DefaultBudgetEngine that uses nowFunc as the source of current time. nowFunc is called whenever the engine needs the current time and enables deterministic behavior for tests. If nowFunc is nil, time.Now is used as the default. It returns a pointer to a DefaultBudgetEngine configured to use the provided time function.

func (*DefaultBudgetEngine) Evaluate added in v0.2.4

func (e *DefaultBudgetEngine) Evaluate(
	budget config.BudgetConfig,
	currentSpend float64,
	currency string,
) (*BudgetStatus, error)

Evaluate compares current spend against the configured budget and alerts. It returns a BudgetStatus with threshold evaluations or an error if validation fails.

Errors are returned when:

  • budget.Currency != currency (currency mismatch)
  • budget.Amount is <= 0 (budget disabled or invalid)
  • currentSpend is negative (invalid spend)

type DismissRequest added in v0.3.0

type DismissRequest struct {
	// RecommendationID is the unique identifier of the recommendation to dismiss.
	RecommendationID string `json:"recommendationId"`

	// Reason is the CLI flag value (e.g., "business-constraint").
	Reason string `json:"reason"`

	// CustomReason is the free-text explanation from the --note flag.
	CustomReason string `json:"customReason,omitempty"`

	// ExpiresAt is the snooze expiry date; nil means permanent dismissal.
	ExpiresAt *time.Time `json:"expiresAt,omitempty"`

	// Recommendation is the current recommendation details for the LastKnown snapshot.
	Recommendation *Recommendation `json:"recommendation,omitempty"`
}

DismissRequest contains parameters for dismissing a recommendation.

type DismissResult added in v0.3.0

type DismissResult struct {
	// RecommendationID is the ID of the dismissed recommendation.
	RecommendationID string `json:"recommendationId"`

	// PluginDismissed is true if a plugin accepted the dismissal via RPC.
	PluginDismissed bool `json:"pluginDismissed"`

	// PluginName identifies which plugin handled the dismissal.
	PluginName string `json:"pluginName,omitempty"`

	// PluginMessage is the plugin's response message.
	PluginMessage string `json:"pluginMessage,omitempty"`

	// PluginFailed is true if a plugin dismiss RPC was attempted but failed.
	// Local persistence still proceeds, but the upstream state may be inconsistent.
	PluginFailed bool `json:"pluginFailed"`

	// LocalPersisted is true if the dismissal was saved locally.
	LocalPersisted bool `json:"localPersisted"`

	// Warning contains a non-fatal warning (e.g., plugin failed but local succeeded).
	Warning string `json:"warning,omitempty"`
}

DismissResult contains the outcome of a dismiss operation.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine orchestrates cost calculations between plugins and local pricing specifications.

func New

func New(clients []*pluginhost.Client, loader SpecLoader) *Engine

New creates a new Engine with the given plugin clients and spec loader.

func (*Engine) DismissRecommendation added in v0.3.0

func (e *Engine) DismissRecommendation(
	ctx context.Context,
	store *config.DismissalStore,
	req DismissRequest,
) (*DismissResult, error)

DismissRecommendation dismisses a recommendation by ID. When a connected plugin advertises the dismiss_recommendations capability, the DismissRecommendation RPC is called first. The dismissal is always persisted locally regardless of plugin result.

func (*Engine) EstimateCost added in v0.3.0

func (e *Engine) EstimateCost(
	ctx context.Context,
	request *EstimateRequest,
) (*EstimateResult, error)

EstimateCost performs what-if cost analysis with property overrides.

It first attempts to use the EstimateCost RPC if the plugin implements it. If the RPC is unimplemented, it falls back to calling GetProjectedCost twice: once with original properties (baseline) and once with overrides applied (modified).

Parameters:

  • ctx: Context for cancellation and tracing
  • request: The estimate request containing resource and property overrides

Returns:

  • *EstimateResult: The estimation result with baseline, modified costs, and deltas
  • error: Any error encountered during estimation

The method logs at appropriate levels:

  • DEBUG: Entry/exit, fallback decisions
  • INFO: Successful estimations
  • WARN: Fallback usage
  • ERROR: Failed estimations

func (*Engine) GetActualCost

func (e *Engine) GetActualCost(
	ctx context.Context,
	resources []ResourceDescriptor,
	from, to time.Time,
) ([]CostResult, error)

GetActualCost retrieves historical actual costs from plugins for the specified time range.

func (*Engine) GetActualCostWithOptions

func (e *Engine) GetActualCostWithOptions(
	ctx context.Context,
	request ActualCostRequest,
) ([]CostResult, error)

GetActualCostWithOptions retrieves actual costs with advanced filtering, grouping, and time range options.

func (*Engine) GetActualCostWithOptionsAndErrors

func (e *Engine) GetActualCostWithOptionsAndErrors(
	ctx context.Context,
	request ActualCostRequest,
) (*CostResultWithErrors, error)

GetActualCostWithOptionsAndErrors retrieves actual costs with comprehensive error tracking. It returns results for all resources (with placeholders for failures) and aggregated error details.

func (*Engine) GetBudgets added in v0.2.5

func (e *Engine) GetBudgets(ctx context.Context, filter *BudgetFilterOptions) (*BudgetResult, error)

GetBudgets retrieves budgets from plugins and applies health calculations. This is the main entry point for budget health functionality.

func (*Engine) GetProjectedCost

func (e *Engine) GetProjectedCost(
	ctx context.Context,
	resources []ResourceDescriptor,
) ([]CostResult, error)

GetProjectedCost calculates projected costs for the given resources using plugins or specs.

func (*Engine) GetProjectedCostWithErrors

func (e *Engine) GetProjectedCostWithErrors(
	ctx context.Context,
	resources []ResourceDescriptor,
) (*CostResultWithErrors, error)

GetProjectedCostWithErrors calculates projected costs with comprehensive error tracking.

func (*Engine) GetRecommendationHistory added in v0.3.0

func (e *Engine) GetRecommendationHistory(
	_ context.Context,
	store *config.DismissalStore,
	recommendationID string,
) ([]config.LifecycleEvent, error)

GetRecommendationHistory returns the lifecycle events for a recommendation.

func (*Engine) GetRecommendationsForResources

func (e *Engine) GetRecommendationsForResources(
	ctx context.Context,
	resources []ResourceDescriptor,
) (*RecommendationsResult, error)

GetRecommendationsForResources fetches cost optimization recommendations for the given resources. For large datasets (>100 resources), it uses batch processing to improve performance and memory usage.

func (*Engine) GroupResults

func (e *Engine) GroupResults(results []CostResult, groupBy GroupBy) []CostResult

GroupResults groups cost results by the specified grouping strategy.

func (*Engine) UndismissRecommendation added in v0.3.0

func (e *Engine) UndismissRecommendation(
	ctx context.Context,
	store *config.DismissalStore,
	recommendationID string,
) (*UndismissResult, error)

UndismissRecommendation re-enables a previously dismissed or snoozed recommendation.

func (*Engine) WithCache added in v0.2.5

func (e *Engine) WithCache(cacheStore cache.Cache) *Engine

WithCache sets the cache store for the engine. This is optional - if not set, no caching will be performed.

func (*Engine) WithDismissalStore added in v0.3.0

func (e *Engine) WithDismissalStore(store *config.DismissalStore) *Engine

WithDismissalStore sets the dismissal store for the engine. This is optional - if not set, a store is created on demand per call.

func (*Engine) WithJobs added in v0.3.0

func (e *Engine) WithJobs(jobs int) *Engine

WithJobs sets an explicit worker count override for concurrent operations. When jobs > 0, this value is used instead of the auto-calculated worker count. When jobs == 0 (default), the auto calculation based on NumCPU is used. Negative inputs are normalized to 0 (auto).

func (*Engine) WithRouter added in v0.2.6

func (e *Engine) WithRouter(router Router) *Engine

WithRouter sets the router for intelligent plugin selection. This is optional - if not set, all plugins are queried for each resource.

type ErrorDetail

type ErrorDetail struct {
	ResourceType string
	ResourceID   string
	PluginName   string
	Error        error
	Timestamp    time.Time
}

ErrorDetail captures information about a failed resource cost calculation.

type ErrorType added in v0.3.0

type ErrorType int

ErrorType categorises the kind of error encountered for a resource.

const (
	// ErrorTypeAuth indicates an authentication or authorisation failure.
	ErrorTypeAuth ErrorType = iota
	// ErrorTypeNetwork indicates a network connectivity failure.
	ErrorTypeNetwork
	// ErrorTypeRateLimit indicates a rate-limit / throttle response.
	ErrorTypeRateLimit
	// ErrorTypeUnknown indicates an unclassified error.
	ErrorTypeUnknown
)

func (ErrorType) MarshalJSON added in v0.3.0

func (e ErrorType) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler to output ErrorType as string.

func (ErrorType) String added in v0.3.0

func (e ErrorType) String() string

String returns the human-readable label for an ErrorType.

func (*ErrorType) UnmarshalJSON added in v0.3.0

func (e *ErrorType) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler to parse ErrorType from string.

type EstimateRequest added in v0.3.0

type EstimateRequest struct {
	// Resource is the base resource descriptor
	Resource *ResourceDescriptor `json:"resource,omitempty"`

	// PropertyOverrides are the changes to evaluate
	PropertyOverrides map[string]string `json:"propertyOverrides,omitempty"`

	// UsageProfile optionally provides context (dev, prod, etc.)
	UsageProfile string `json:"usageProfile,omitempty"`
}

EstimateRequest encapsulates parameters for EstimateCost.

This is the internal request structure used by the engine layer for what-if cost analysis operations.

type EstimateResult added in v0.3.0

type EstimateResult struct {
	// Resource is the resource being estimated
	Resource *ResourceDescriptor `json:"resource"`

	// Baseline is the cost with original properties
	Baseline *CostResult `json:"baseline"`

	// Modified is the cost with property overrides applied
	Modified *CostResult `json:"modified"`

	// TotalChange is the difference between modified and baseline monthly costs
	// Positive = increase, negative = savings
	TotalChange float64 `json:"totalChange"`

	// Deltas contains per-property cost impact breakdown
	Deltas []CostDelta `json:"deltas"`

	// UsedFallback indicates if EstimateCost RPC was unavailable
	// and the result was computed from two GetProjectedCost calls
	UsedFallback bool `json:"usedFallback,omitempty"`
}

EstimateResult represents the result of a what-if cost estimation. It contains baseline and modified costs along with per-property deltas.

Usage Examples:

result := EstimateResult{
	Resource: &ResourceDescriptor{Provider: "aws", Type: "ec2:Instance"},
	Baseline: &CostResult{Monthly: 8.32, Currency: "USD"},
	Modified: &CostResult{Monthly: 83.22, Currency: "USD"},
	TotalChange: 74.90,
	Deltas: []CostDelta{
		{Property: "instanceType", OriginalValue: "t3.micro", NewValue: "m5.large", CostChange: 74.90},
	},
}

type ExtendedBudgetSummary added in v0.2.5

type ExtendedBudgetSummary struct {
	*pbc.BudgetSummary // Embedded proto summary

	ByProvider      map[string]*pbc.BudgetSummary // Per-provider breakdown
	ByCurrency      map[string]*pbc.BudgetSummary // Per-currency breakdown
	OverallHealth   pbc.BudgetHealthStatus        // Worst-case health
	CriticalBudgets []string                      // IDs of critical/exceeded budgets
}

func CalculateExtendedSummary added in v0.2.5

func CalculateExtendedSummary(ctx context.Context, budgets []*pbc.Budget) *ExtendedBudgetSummary

CalculateExtendedSummary provides detailed breakdown by provider and currency. Includes list of critical/exceeded budget IDs for immediate attention.

type GroupBy

type GroupBy string

GroupBy defines the available grouping strategies for cost result aggregation.

This type enables flexible cost analysis by grouping results along different dimensions such as resources, providers, or time periods. Each grouping type provides unique insights for cost optimization and analysis.

const (
	GroupByResource GroupBy = "resource"
	GroupByType     GroupBy = "type"
	GroupByProvider GroupBy = "provider"
	GroupByDate     GroupBy = "date" // Deprecated: use GroupByDaily
	GroupByDaily    GroupBy = "daily"
	GroupByMonthly  GroupBy = "monthly"
	GroupByNone     GroupBy = ""
)

GroupBy constants define all supported aggregation strategies.

Resource Groupings:

  • GroupByResource: Groups by individual resource (ResourceType/ResourceID)
  • GroupByType: Groups by resource type (e.g., "aws:ec2:Instance")
  • GroupByProvider: Groups by cloud provider (e.g., "aws", "azure", "gcp")

Time-Based Groupings:

  • GroupByDaily: Groups by calendar date ("2006-01-02") for daily trends
  • GroupByMonthly: Groups by month ("2006-01") for monthly analysis
  • GroupByDate: Deprecated legacy date-key grouping (non time-based for cross-provider). Prefer GroupByDaily for time-based aggregations.

Special Values:

  • GroupByNone: No grouping (empty string) - returns results as-is

Usage Guidelines:

  • Use resource groupings for cost attribution and resource optimization
  • Use time-based groupings for trend analysis and forecasting
  • Time-based groupings require results with valid StartDate/EndDate fields
  • Cross-provider aggregation only supports time-based groupings

func (GroupBy) IsTimeBasedGrouping

func (g GroupBy) IsTimeBasedGrouping() bool

IsTimeBasedGrouping returns true if the GroupBy requires time-based aggregation.

Time-based groupings require cost results with valid StartDate and EndDate fields and are the only grouping types supported by cross-provider aggregation functions. This method is used internally to validate aggregation requests and determine processing strategies.

Time-Based GroupBy Values:

  • GroupByDaily: Requires daily cost data aggregation
  • GroupByMonthly: Requires monthly cost data aggregation
  • GroupByDate: Deprecated legacy date-key grouping (non time-based for cross-provider)

Non-Time-Based GroupBy Values:

  • GroupByResource: Groups by resource identity
  • GroupByType: Groups by resource type
  • GroupByProvider: Groups by cloud provider
  • GroupByNone: No grouping applied

Returns:

  • true: For GroupByDaily and GroupByMonthly only
  • false: For all other GroupBy values

Usage Examples:

// Validate for cross-provider aggregation
if !groupBy.IsTimeBasedGrouping() {
	return ErrInvalidGroupBy
}

// Route to appropriate processing
if groupBy.IsTimeBasedGrouping() {
	return CreateCrossProviderAggregation(results, groupBy)
} else {
	return engine.GroupResults(results, groupBy)
}

func (GroupBy) IsValid

func (g GroupBy) IsValid() bool

IsValid returns true if the GroupBy value is valid.

This method provides compile-time safety for GroupBy values and enables validation before processing. All defined GroupBy constants are considered valid, including the empty string (GroupByNone).

Returns:

  • true: For all defined GroupBy constants
  • false: For any other string values

Usage Examples:

// Validate user input
groupBy := GroupBy(userInput)
if !groupBy.IsValid() {
	return fmt.Errorf("invalid groupBy: %s", userInput)
}

// Safe to use in switch statements
switch groupBy {
case GroupByDaily, GroupByMonthly:
	// Time-based processing
case GroupByResource, GroupByType, GroupByProvider:
	// Resource-based processing
}

func (GroupBy) String

func (g GroupBy) String() string

String returns the string representation of the GroupBy.

This method implements the Stringer interface and provides a consistent string representation for logging, debugging, and serialization.

Returns:

  • string: The underlying string value of the GroupBy
  • "": Empty string for GroupByNone

Usage Examples:

// Logging and debugging
log.Printf("Processing with groupBy: %s", groupBy.String())

// CLI flag validation
if groupBy.String() == "" {
	groupBy = GroupByResource // Default
}

// JSON serialization (automatic via json package)
type Request struct {
	GroupBy GroupBy `json:"groupBy"`
}

type OutputFormat

type OutputFormat string

OutputFormat specifies the output format for cost results (table, JSON, NDJSON).

const (
	// OutputTable renders results in a formatted table.
	OutputTable OutputFormat = "table"
	// OutputJSON renders results as pretty-printed JSON.
	OutputJSON OutputFormat = "json"
	// OutputNDJSON renders results as newline-delimited JSON for streaming.
	OutputNDJSON OutputFormat = "ndjson"
)

type OverviewJSONOutput added in v0.3.0

type OverviewJSONOutput struct {
	Metadata  OverviewMetadata     `json:"metadata"`
	Resources []OverviewRow        `json:"resources"`
	Summary   OverviewSummary      `json:"summary"`
	Budgets   []BudgetHealthResult `json:"budgets,omitempty"`
	Errors    []OverviewRowError   `json:"errors"`
}

OverviewJSONOutput is the top-level JSON output structure.

type OverviewMetadata added in v0.3.0

type OverviewMetadata struct {
	StackContext
}

OverviewMetadata holds metadata information for the JSON output. It embeds StackContext so field promotion avoids duplication. GeneratedAt is promoted from StackContext.

type OverviewRow added in v0.3.0

type OverviewRow struct {
	URN        string                 `json:"urn"`
	Type       string                 `json:"type"`
	ResourceID string                 `json:"resourceId,omitempty"`
	Status     ResourceStatus         `json:"status"`
	Properties map[string]interface{} `json:"properties,omitempty"`
	// ProjectedProperties stores preview-merged properties used for projected
	// cost calls (deep-merge of old state and new inputs). Actual cost calls
	// continue to use Properties from current state.
	ProjectedProperties map[string]interface{} `json:"projectedProperties,omitempty"`
	ActualCost          *ActualCostData        `json:"actualCost,omitempty"`
	ProjectedCost       *ProjectedCostData     `json:"projectedCost,omitempty"`
	// BaselineProjectedCost captures projected monthly cost using current-state
	// properties (before pending changes). It is internal to delta math and is
	// intentionally omitted from serialized output.
	BaselineProjectedCost *ProjectedCostData `json:"-"`
	Recommendations       []Recommendation   `json:"recommendations,omitempty"`
	CostDrift             *CostDriftData     `json:"costDrift,omitempty"`
	Error                 *OverviewRowError  `json:"error,omitempty"`
	PropertyDiffs         []PropertyDiff     `json:"propertyDiffs,omitempty"`
	// ComputedDelta is the per-row cost delta populated by PopulateComputedDeltas
	// after enrichment, before any rendering. All renderers (table, JSON, NDJSON,
	// TUI) read this value instead of computing deltas independently.
	// Nil when no meaningful delta exists (e.g., active resource without drift).
	ComputedDelta *float64 `json:"delta,omitempty"`
	// CreatedAt tracks when the resource was first added to Pulumi state.
	// Used by enrichCostDrift to suppress drift for very new resources with
	// insufficient data points in the current billing window.
	// Nil for resources from Pulumi < v3.60.0 or plan-only resources.
	CreatedAt *time.Time `json:"createdAt,omitempty"`
}

OverviewRow represents a single resource row in the unified cost overview. Each row combines state, plan, actual costs, projected costs, drift, and recommendations for a resource.

func EnrichOverviewRows added in v0.3.0

func EnrichOverviewRows(
	ctx context.Context,
	rows []OverviewRow,
	eng *Engine,
	dateRange DateRange,
	progressChan chan<- OverviewRowUpdate,
) []OverviewRow

EnrichOverviewRows concurrently enriches each OverviewRow in rows with cost and recommendation data. It runs a fixed-size worker pool (capped by overviewConcurrencyLimit or the number of rows), respects context cancellation, and records any per-row failures in each row's Error field without returning an error. If progressChan is non-nil, the function sends OverviewRowUpdate messages as rows are processed and closes progressChan before returning.

Parameters:

  • ctx: the context to observe for cancellation and logging.
  • rows: the slice of OverviewRow to enrich; enrichment is performed in-place and the same slice is returned.
  • eng: the Engine used to fetch costs and recommendations.
  • dateRange: the date range used for cost calculations.
  • progressChan: optional channel that receives progress updates for each enriched row; may be nil.

Returns:

The input slice of OverviewRow populated with enrichment results (ActualCost, ProjectedCost, Recommendations,
CostDrift) and any per-row Error values set for partial failures.

func MergeResourcesForOverview added in v0.3.0

func MergeResourcesForOverview(
	ctx context.Context,
	stateResources []StateResource,
	planSteps []PlanStep,
) ([]OverviewRow, error)

MergeResourcesForOverview builds skeleton OverviewRow entries by combining current Pulumi state resources with pending plan steps.

The merge preserves the order of state resources (FR-011), appending any newly-created resources from the plan that are not already present in state. Only custom resources (cloud resources, not providers or components) are included.

The returned rows have URN, Type, ResourceID, and Status populated; cost fields are left nil for later enrichment.

func NewRowsFromState added in v0.3.2

func NewRowsFromState(ctx context.Context, stateResources []StateResource) []OverviewRow

NewRowsFromState creates skeleton OverviewRows from state resources only, with no plan data applied. All rows are assigned StatusActive.

This is used for Phase 1 (state-first) loading before pulumi preview runs. The rows are structurally identical to those produced by MergeResourcesForOverview and can be enriched and later updated with change status via ApplyChangesToRows.

Only custom resources are included — the same filter as MergeResourcesForOverview.

func (*OverviewRow) Validate added in v0.3.0

func (r *OverviewRow) Validate() error

Validate checks that the OverviewRow fields are well-formed. It validates required fields, length constraints, status range, and recursively validates nested types when present.

type OverviewRowError added in v0.3.0

type OverviewRowError struct {
	URN       string    `json:"urn"`
	ErrorType ErrorType `json:"errorType"`
	Message   string    `json:"message"`
	Retryable bool      `json:"retryable"`
}

OverviewRowError captures an error that occurred while fetching cost data for a specific resource.

func (*OverviewRowError) Validate added in v0.3.0

func (e *OverviewRowError) Validate() error

Validate checks that the OverviewRowError fields are well-formed.

type OverviewRowUpdate added in v0.3.0

type OverviewRowUpdate struct {
	Index int
	Row   OverviewRow
}

OverviewRowUpdate carries a row update on a progress channel, pairing the row index with the updated row data.

type OverviewSummary added in v0.3.0

type OverviewSummary struct {
	TotalActualMTD   float64 `json:"totalActualMTD"`
	ProjectedMonthly float64 `json:"projectedMonthly"`
	ProjectedDelta   float64 `json:"projectedDelta"`
	PotentialSavings float64 `json:"potentialSavings"`
	Currency         string  `json:"currency"`
}

OverviewSummary holds aggregated summary statistics for the JSON output.

type PlanStep added in v0.3.0

type PlanStep struct {
	URN           string         `json:"urn,omitempty"`
	Op            string         `json:"op,omitempty"`
	Type          string         `json:"type,omitempty"`
	PropertyDiffs []PropertyDiff `json:"propertyDiffs,omitempty"`
	// ProjectedProperties carries preview properties (deep-merge of old state
	// and new inputs) used to price pending changes accurately.
	ProjectedProperties map[string]interface{} `json:"projectedProperties,omitempty"`
}

PlanStep represents a step from a Pulumi plan for overview merging. This is a lightweight projection of ingest.PulumiStep to avoid an import cycle (ingest already imports engine).

type PluginMatch added in v0.2.6

type PluginMatch struct {
	Client      *pluginhost.Client
	Priority    int
	Fallback    bool
	MatchReason string
	Source      string
}

PluginMatch represents a matched plugin from the router. This mirrors router.PluginMatch to avoid circular imports.

type PricingSpec

type PricingSpec = spec.PricingSpec

PricingSpec is an alias to the PricingSpec from the spec package to ensure type consistency.

type ProjectedCostData added in v0.3.0

type ProjectedCostData struct {
	MonthlyCost float64            `json:"monthlyCost"`
	Currency    string             `json:"currency"`
	Breakdown   map[string]float64 `json:"breakdown,omitempty"`
}

ProjectedCostData holds projected monthly cost information for a resource.

func (*ProjectedCostData) Validate added in v0.3.0

func (p *ProjectedCostData) Validate() error

Validate checks that the ProjectedCostData fields are consistent.

type ProjectedCostRequest

type ProjectedCostRequest struct {
	Resources []ResourceDescriptor
	SpecDir   string
	Adapter   string
}

ProjectedCostRequest contains resources for which projected costs should be calculated.

type PropertyDiff added in v0.3.3

type PropertyDiff struct {
	Key      string `json:"key"`
	OldValue string `json:"oldValue"`
	NewValue string `json:"newValue"`
}

PropertyDiff represents a single property change from a Pulumi plan. It captures the before and after values for a property that differs between OldState.Inputs and NewState.Inputs in an update or replace operation.

type Recommendation

type Recommendation struct {
	// ResourceID identifies the resource this recommendation applies to.
	ResourceID string `json:"resourceId,omitempty"`

	// Type categorizes the recommendation (e.g., "Right-sizing", "Terminate",
	// "Purchase Commitment", "Delete Unused", "Adjust Requests")
	Type string `json:"type"`

	// Description provides actionable text explaining the recommendation
	Description string `json:"description"`

	// EstimatedSavings is the projected monthly savings if the recommendation
	// is implemented. Zero indicates savings cannot be estimated.
	EstimatedSavings float64 `json:"estimatedSavings,omitempty"`

	// Currency is the ISO 4217 code for EstimatedSavings (e.g., "USD").
	// Empty if EstimatedSavings is zero.
	Currency string `json:"currency,omitempty"`

	// Status indicates the lifecycle state of this recommendation.
	// Empty or "Active" for active recommendations, "Dismissed" or "Snoozed"
	// for dismissed/snoozed recommendations shown via --include-dismissed.
	Status RecommendationStatus `json:"status,omitempty"`

	// Reasoning carries plugin-provided warnings and caveats explaining
	// prerequisites or risks for implementing this recommendation
	// (e.g., "Ensure application compatibility with ARM64 architecture").
	Reasoning []string `json:"reasoning,omitempty"`
}

Recommendation represents a single cost optimization suggestion.

Recommendations are provided by plugins to suggest ways to reduce costs, such as right-sizing instances, terminating idle resources, or purchasing reserved capacity.

Usage Examples:

rec := Recommendation{
	Type:            "Right-sizing",
	Description:     "Switch to t3.small to reduce costs",
	EstimatedSavings: 15.00,
	Currency:        "USD",
}

type RecommendationError

type RecommendationError struct {
	PluginName string `json:"pluginName"`
	Error      string `json:"error"`
}

RecommendationError captures error information when fetching recommendations from a plugin.

type RecommendationStatus added in v0.3.0

type RecommendationStatus string

RecommendationStatus represents the lifecycle state of a recommendation.

const (
	// RecommendationStatusActive indicates an active (non-dismissed) recommendation.
	RecommendationStatusActive RecommendationStatus = "Active"
	// RecommendationStatusDismissed indicates a permanently dismissed recommendation.
	RecommendationStatusDismissed RecommendationStatus = "Dismissed"
	// RecommendationStatusSnoozed indicates a temporarily snoozed recommendation.
	RecommendationStatusSnoozed RecommendationStatus = "Snoozed"
)

func (RecommendationStatus) IsValid added in v0.3.0

func (rs RecommendationStatus) IsValid() bool

IsValid returns true if the status is a known value.

func (RecommendationStatus) String added in v0.3.0

func (rs RecommendationStatus) String() string

String returns the string representation of the status.

type RecommendationsResult

type RecommendationsResult struct {
	Recommendations []Recommendation      `json:"recommendations"`
	Errors          []RecommendationError `json:"errors"`
	TotalSavings    float64               `json:"totalSavings"`
	Currency        string                `json:"currency"`
}

RecommendationsResult contains the results of fetching recommendations from multiple plugins.

func (*RecommendationsResult) ErrorSummary

func (r *RecommendationsResult) ErrorSummary() string

ErrorSummary returns a string summary of errors.

func (*RecommendationsResult) HasErrors

func (r *RecommendationsResult) HasErrors() bool

HasErrors returns true if any errors were encountered.

type ResourceDescriptor

type ResourceDescriptor struct {
	Type       string                 `json:"type"`
	ID         string                 `json:"id"`
	Provider   string                 `json:"provider"`
	Properties map[string]interface{} `json:"properties"`
}

ResourceDescriptor represents a cloud resource with its type, provider, and properties.

func FilterResources

func FilterResources(resources []ResourceDescriptor, filter string) []ResourceDescriptor

FilterResources selects resources that match the provided filter expression. The filter is a single key=value expression (for example "provider=aws" or "tag:env=prod"). An empty filter returns the input slice unchanged. The returned slice contains only the resources that satisfy the filter.

func (*ResourceDescriptor) Validate

func (r *ResourceDescriptor) Validate() error

Validate checks that the ResourceDescriptor has valid fields and returns an error if validation fails. It validates:

  • Type is not empty and within length limits
  • ID is within length limits (can be empty for some resources)
  • Properties count does not exceed maximum
  • Property keys are valid identifiers and within length limits
  • Property values do not exceed size limits

type ResourceStatus added in v0.3.0

type ResourceStatus int

ResourceStatus represents the lifecycle state of a resource in the overview.

const (
	// StatusActive indicates the resource exists and has no pending changes.
	StatusActive ResourceStatus = iota
	// StatusCreating indicates the resource is being created.
	StatusCreating
	// StatusUpdating indicates the resource is being updated.
	StatusUpdating
	// StatusDeleting indicates the resource is being deleted.
	StatusDeleting
	// StatusReplacing indicates the resource is being replaced (delete + create).
	StatusReplacing
)

func MapOperationToStatus added in v0.3.0

func MapOperationToStatus(op string) ResourceStatus

MapOperationToStatus converts a Pulumi plan operation string to a ResourceStatus value. Unknown operations default to StatusActive.

func (ResourceStatus) MarshalJSON added in v0.3.0

func (s ResourceStatus) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler to output ResourceStatus as string.

func (ResourceStatus) String added in v0.3.0

func (s ResourceStatus) String() string

String returns the human-readable label for a ResourceStatus.

func (*ResourceStatus) UnmarshalJSON added in v0.3.0

func (s *ResourceStatus) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler to parse ResourceStatus from string.

type Router added in v0.2.6

type Router interface {
	// SelectPlugins returns plugins that match a resource for a given feature.
	// Results are ordered by priority (highest first).
	SelectPlugins(ctx context.Context, resource ResourceDescriptor, feature string) []PluginMatch

	// ShouldFallback returns true if fallback is enabled for a plugin.
	ShouldFallback(pluginName string) bool
}

Router selects appropriate plugins for resources. This interface is implemented by the router package.

type ScopeType added in v0.2.6

type ScopeType string

ScopeType identifies the category of a budget scope.

const (
	// ScopeTypeGlobal represents the global budget that all resources count toward.
	ScopeTypeGlobal ScopeType = "global"

	// ScopeTypeProvider represents a per-cloud-provider budget (aws, gcp, azure).
	ScopeTypeProvider ScopeType = "provider"

	// ScopeTypeTag represents a tag-based budget with priority ordering.
	ScopeTypeTag ScopeType = "tag"

	// ScopeTypeType represents a per-resource-type budget (e.g., aws:ec2/instance).
	ScopeTypeType ScopeType = "type"
)

Budget scope type constants.

func (ScopeType) IsValid added in v0.2.6

func (s ScopeType) IsValid() bool

IsValid returns true if the scope type is a recognized value.

func (ScopeType) String added in v0.2.6

func (s ScopeType) String() string

String returns the string representation of the scope type.

type ScopedBudgetEvaluator added in v0.2.6

type ScopedBudgetEvaluator struct {
	// contains filtered or unexported fields
}

ScopedBudgetEvaluator provides methods for evaluating scoped budgets.

func NewScopedBudgetEvaluator added in v0.2.6

func NewScopedBudgetEvaluator(cfg *config.BudgetsConfig) *ScopedBudgetEvaluator

NewScopedBudgetEvaluator creates a new evaluator for the given configuration. Note: Invalid tag selectors are logged as warnings and skipped. The configuration should be validated with BudgetsConfig.Validate() before creating an evaluator to catch syntax errors early. A future enhancement may return an error for invalid selectors to fail fast on configuration errors.

func (*ScopedBudgetEvaluator) AllocateCostToProvider added in v0.2.6

func (e *ScopedBudgetEvaluator) AllocateCostToProvider(
	ctx context.Context,
	resourceType string,
	cost float64,
) *BudgetAllocation

AllocateCostToProvider allocates a resource's cost to its provider budget. Returns a BudgetAllocation with provider scope if a matching budget exists.

func (*ScopedBudgetEvaluator) AllocateCostToTag added in v0.2.6

func (e *ScopedBudgetEvaluator) AllocateCostToTag(
	ctx context.Context,
	resourceType string,
	tags map[string]string,
	cost float64,
) *BudgetAllocation

AllocateCostToTag allocates a resource's cost to the highest-priority matching tag budget. Returns a BudgetAllocation with tag scope if a matching budget exists. If multiple tag budgets match with the same priority, warnings are emitted.

func (*ScopedBudgetEvaluator) AllocateCostToType added in v0.2.6

func (e *ScopedBudgetEvaluator) AllocateCostToType(
	ctx context.Context,
	resourceType string,
	cost float64,
) *BudgetAllocation

AllocateCostToType allocates a resource's cost to its resource type budget. Returns a BudgetAllocation with type scope if a matching budget exists.

func (*ScopedBudgetEvaluator) AllocateCosts added in v0.2.6

func (e *ScopedBudgetEvaluator) AllocateCosts(
	ctx context.Context,
	resourceType string,
	tags map[string]string,
	cost float64,
) *BudgetAllocation

AllocateCosts allocates a resource's cost to all applicable budget scopes. This is the main entry point for multi-scope cost allocation. Returns a BudgetAllocation with all scopes that received the cost. Returns an empty allocation (no scopes) if the context is cancelled.

func (*ScopedBudgetEvaluator) GetProviderBudget added in v0.2.6

func (e *ScopedBudgetEvaluator) GetProviderBudget(provider string) *config.ScopedBudget

GetProviderBudget returns the budget for a provider, or nil if not configured.

func (*ScopedBudgetEvaluator) GetTypeBudget added in v0.2.6

func (e *ScopedBudgetEvaluator) GetTypeBudget(resourceType string) *config.ScopedBudget

GetTypeBudget returns the budget for a resource type, or nil if not configured.

func (*ScopedBudgetEvaluator) MatchTagBudgets added in v0.2.6

func (e *ScopedBudgetEvaluator) MatchTagBudgets(_ context.Context, tags map[string]string) []config.TagBudget

MatchTagBudgets returns all tag budgets that match the given tags. Results are returned in priority order (highest first). Uses pre-parsed selectors for efficiency (parsed once in NewScopedBudgetEvaluator).

func (*ScopedBudgetEvaluator) SelectHighestPriorityTagBudget added in v0.2.6

func (e *ScopedBudgetEvaluator) SelectHighestPriorityTagBudget(
	ctx context.Context,
	matches []config.TagBudget,
) (*config.TagBudget, []string)

SelectHighestPriorityTagBudget selects the tag budget with highest priority from matches. Returns nil if no matches provided. Emits a warning if multiple budgets have the same highest priority.

type ScopedBudgetResult added in v0.2.6

type ScopedBudgetResult struct {
	// Global is the global budget status (always present if configured).
	Global *ScopedBudgetStatus `json:"global,omitempty"`

	// ByProvider maps provider names to their budget statuses.
	ByProvider map[string]*ScopedBudgetStatus `json:"by_provider,omitempty"`

	// ByTag contains tag budget statuses in priority order.
	ByTag []*ScopedBudgetStatus `json:"by_tag,omitempty"`

	// ByType maps resource types to their budget statuses.
	ByType map[string]*ScopedBudgetStatus `json:"by_type,omitempty"`

	// OverallHealth is the worst health status across all scopes.
	OverallHealth pbc.BudgetHealthStatus `json:"overall_health"`

	// CriticalScopes lists scope identifiers with CRITICAL or EXCEEDED status.
	CriticalScopes []string `json:"critical_scopes,omitempty"`

	// Allocations contains per-resource allocation details (debug mode only).
	Allocations []BudgetAllocation `json:"allocations,omitempty"`

	// Warnings contains all warnings generated during evaluation.
	Warnings []string `json:"warnings,omitempty"`
}

ScopedBudgetResult contains all evaluated scoped budgets and summaries.

func (*ScopedBudgetResult) AllScopes added in v0.2.6

func (r *ScopedBudgetResult) AllScopes() []*ScopedBudgetStatus

AllScopes returns all scoped budget statuses in a flat list.

WARNING: The returned slice contains pointers to internal state. Callers MUST NOT modify the returned ScopedBudgetStatus objects. This design is intentional for performance in rendering scenarios. If modification is needed, callers should create their own copies.

func (*ScopedBudgetResult) HasCriticalBudgets added in v0.2.6

func (r *ScopedBudgetResult) HasCriticalBudgets() bool

HasCriticalBudgets returns true if any budget has CRITICAL or EXCEEDED health status.

func (*ScopedBudgetResult) HasExceededBudgets added in v0.2.6

func (r *ScopedBudgetResult) HasExceededBudgets() bool

HasExceededBudgets returns true if any budget has EXCEEDED health status.

type ScopedBudgetStatus added in v0.2.6

type ScopedBudgetStatus struct {
	// ScopeType identifies the budget scope category.
	ScopeType ScopeType `json:"scope_type"`

	// ScopeKey is the identifier within the scope type.
	// For provider: "aws", "gcp", etc.
	// For tag: "team:platform", "env:prod", etc.
	// For type: "aws:ec2/instance", etc.
	// For global: empty string.
	ScopeKey string `json:"scope_key,omitempty"`

	// Budget is the configured budget for this scope.
	Budget config.ScopedBudget `json:"budget"`

	// CurrentSpend is the total cost allocated to this scope.
	CurrentSpend float64 `json:"current_spend"`

	// Percentage is CurrentSpend / Budget.Amount * 100.
	Percentage float64 `json:"percentage"`

	// ForecastedSpend is the projected end-of-period spend.
	ForecastedSpend float64 `json:"forecasted_spend,omitempty"`

	// ForecastPercentage is ForecastedSpend / Budget.Amount * 100.
	ForecastPercentage float64 `json:"forecast_percentage,omitempty"`

	// Health is the overall health status (OK, WARNING, CRITICAL, EXCEEDED).
	Health pbc.BudgetHealthStatus `json:"health"`

	// Alerts is the list of evaluated threshold statuses.
	Alerts []ThresholdStatus `json:"alerts,omitempty"`

	// MatchedResources is the count of resources allocated to this scope.
	MatchedResources int `json:"matched_resources,omitempty"`

	// Currency is the budget currency for display.
	Currency string `json:"currency,omitempty"`
}

ScopedBudgetStatus represents the evaluated state of a scoped budget.

func CalculateProviderBudgetStatus added in v0.2.6

func CalculateProviderBudgetStatus(
	provider string,
	budget *config.ScopedBudget,
	currentSpend float64,
) *ScopedBudgetStatus

CalculateProviderBudgetStatus calculates the budget status for a provider scope.

func CalculateTagBudgetStatus added in v0.2.6

func CalculateTagBudgetStatus(
	tagBudget *config.TagBudget,
	currentSpend float64,
) *ScopedBudgetStatus

CalculateTagBudgetStatus calculates the budget status for a tag scope.

func CalculateTypeBudgetStatus added in v0.2.6

func CalculateTypeBudgetStatus(
	resourceType string,
	budget *config.ScopedBudget,
	currentSpend float64,
) *ScopedBudgetStatus

CalculateTypeBudgetStatus calculates the budget status for a resource type scope.

func (*ScopedBudgetStatus) HasExceededAlerts added in v0.2.6

func (s *ScopedBudgetStatus) HasExceededAlerts() bool

HasExceededAlerts returns true if any alert has EXCEEDED status.

func (*ScopedBudgetStatus) IsOverBudget added in v0.2.6

func (s *ScopedBudgetStatus) IsOverBudget() bool

IsOverBudget returns true if current spend exceeds the budget amount.

func (*ScopedBudgetStatus) ScopeIdentifier added in v0.2.6

func (s *ScopedBudgetStatus) ScopeIdentifier() string

ScopeIdentifier returns a string identifier for this scope in format "type:key".

type SpecLoader

type SpecLoader interface {
	LoadSpec(provider, service, sku string) (interface{}, error)
}

SpecLoader is an interface for loading pricing specifications from local YAML files.

type StackContext added in v0.3.0

type StackContext struct {
	StackName      string    `json:"stackName"`
	Region         string    `json:"region,omitempty"`
	TimeWindow     DateRange `json:"timeWindow"`
	HasChanges     bool      `json:"hasChanges"`
	TotalResources int       `json:"totalResources"`
	PendingChanges int       `json:"pendingChanges"`
	GeneratedAt    time.Time `json:"generatedAt,omitempty"`
	// IsStateOnly is true when no pulumi preview was run; costs reflect the current state only.
	IsStateOnly bool `json:"isStateOnly,omitempty"`
	// BudgetHealth provides a stack-level budget health summary for JSON output.
	BudgetHealth *BudgetHealthSummary `json:"budgetHealth,omitempty"`
}

StackContext provides metadata about the Pulumi stack being analysed in the overview, including change detection information.

func (*StackContext) Validate added in v0.3.0

func (s *StackContext) Validate() error

Validate checks that the StackContext fields are well-formed.

type StateCostInput

type StateCostInput struct {
	Resource   ResourceDescriptor
	HourlyRate float64
	CreatedAt  time.Time
	IsExternal bool
}

StateCostInput contains input for state-based cost calculation.

func (*StateCostInput) Validate

func (s *StateCostInput) Validate() error

Validate checks that the StateCostInput has valid fields.

type StateCostResult

type StateCostResult struct {
	TotalCost    float64
	RuntimeHours float64
	Notes        string
}

StateCostResult contains the result of state-based cost calculation.

func CalculateStateCost

func CalculateStateCost(input StateCostInput, referenceTime time.Time) StateCostResult

CalculateStateCost calculates the estimated cost of a resource based on its runtime. The cost is calculated as: hourly_rate × runtime.Hours() where runtime = referenceTime - CreatedAt.

For imported resources (IsExternal=true), a warning note is added since the Created timestamp reflects import time, not actual creation.

Parameters:

  • input: StateCostInput with resource details, hourly rate, and creation time
  • referenceTime: The time to calculate runtime against (usually time.Now())

Returns StateCostResult with TotalCost, RuntimeHours, and any warning Notes.

type StateResource added in v0.3.0

type StateResource struct {
	URN        string                 `json:"urn,omitempty"`
	Type       string                 `json:"type,omitempty"`
	ID         string                 `json:"id,omitempty"`
	Custom     bool                   `json:"custom,omitempty"`
	Properties map[string]interface{} `json:"properties,omitempty"`
	// CreatedAt tracks when the resource was first added to Pulumi state.
	// Available since Pulumi v3.60.0; nil for older state files.
	// JSON key is "createdAt" (not "created" as in the Pulumi state format);
	// conversion happens in cli.convertStateResources.
	CreatedAt *time.Time `json:"createdAt,omitempty"`
}

StateResource represents a resource from Pulumi state for overview merging. This is a lightweight projection of ingest.StackExportResource to avoid an import cycle (ingest already imports engine).

type StructuredError added in v0.3.0

type StructuredError struct {
	Code         string `json:"code"`
	Message      string `json:"message"`
	ResourceType string `json:"resourceType"`
}

StructuredError is a machine-readable error representation included in JSON/NDJSON output so AI agents can programmatically categorize errors without parsing the human-readable Notes field.

type SustainabilityMetric

type SustainabilityMetric struct {
	Value float64 `json:"value"`
	Unit  string  `json:"unit"`
}

SustainabilityMetric represents a single sustainability impact measurement.

type ThresholdEvaluationResult added in v0.2.5

type ThresholdEvaluationResult struct {
	Threshold   *pbc.BudgetThreshold // Original threshold
	Triggered   bool                 // Whether threshold was crossed
	TriggeredAt time.Time            // When triggered (zero if not)
	SpendType   string               // "actual" or "forecasted"
}

ThresholdEvaluationResult contains evaluated threshold state.

func EvaluateThresholds added in v0.2.5

func EvaluateThresholds(
	ctx context.Context,
	budget *pbc.Budget,
	currentSpend float64,
	forecastedSpend float64,
) []ThresholdEvaluationResult

EvaluateThresholds checks which thresholds have been triggered.

Behavior:

  • Evaluates against current spend for ACTUAL thresholds
  • Evaluates against forecasted spend for FORECASTED thresholds
  • Sets Triggered=true and TriggeredAt for crossed thresholds
  • Returns all thresholds with updated triggered status
  • Preserves existing TriggeredAt timestamp if already triggered

type ThresholdStatus added in v0.2.4

type ThresholdStatus struct {
	// Threshold is the configured threshold percentage (e.g., 80.0 for 80%).
	Threshold float64
	// Type is the alert type ("actual" or "forecasted").
	Type config.AlertType
	// Status is the evaluation result: OK, APPROACHING, or EXCEEDED.
	Status ThresholdStatusValue
}

ThresholdStatus represents the status of an individual alert threshold.

type ThresholdStatusValue added in v0.2.4

type ThresholdStatusValue string

ThresholdStatusValue represents the status of a threshold evaluation.

const (
	// ThresholdStatusOK indicates the threshold has not been reached.
	ThresholdStatusOK ThresholdStatusValue = "OK"
	// ThresholdStatusApproaching indicates spending is approaching the threshold (within 5%).
	ThresholdStatusApproaching ThresholdStatusValue = "APPROACHING"
	// ThresholdStatusExceeded indicates the threshold has been exceeded.
	ThresholdStatusExceeded ThresholdStatusValue = "EXCEEDED"
)

Threshold status values for budget alert evaluation.

type UndismissResult added in v0.3.0

type UndismissResult struct {
	// RecommendationID is the ID of the undismissed recommendation.
	RecommendationID string `json:"recommendationId"`

	// WasDismissed is false if the recommendation wasn't dismissed.
	WasDismissed bool `json:"wasDismissed"`

	// Message provides information about the undismiss operation.
	Message string `json:"message,omitempty"`
}

UndismissResult contains the outcome of an undismiss operation.

Directories

Path Synopsis
Package batch provides utilities for processing large datasets in fixed-size batches.
Package batch provides utilities for processing large datasets in fixed-size batches.
Package cache provides BoltDB-backed caching with TTL expiration for cost query results.
Package cache provides BoltDB-backed caching with TTL expiration for cost query results.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL