Skip to content

Explain cost variance and root cause from the CLI - #64

Merged
hudsonaikins merged 18 commits into
mainfrom
autonomous/dc767eb795e6125a/integration-0
Oct 4, 2026
Merged

hudsonaikins merged 18 commits into
mainfrom
autonomous/dc767eb795e6125a/integration-0

Conversation

@hudsonaikins

@hudsonaikins hudsonaikins commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Compare forecast and actual costs, preserve measured totals and residuals, and rank evidenced driver contributions without asserting delivery causation. Add local diff and explain commands plus versioned variance contracts.

Integrates current main and fixes a reproduced round-trip defect: medium-confidence synthetic inputs were valid, but their diff contributions were rejected by explain. Synthetic contribution confidence is now capped at low. The new regression failed before the fix and passes afterward.

Validation on 8fa8ab43472238019657bba4af631970e5ef2c86:

  • Focused variance/diff/explain tests pass.
  • Saved exact-commit Tabellio result validation-61b1836c-8938-4681-b273-cdcb815ed62c is passed: all six validators passed, including full Go vet/tests, schema, economics, CLI workflows, cost standards, and security boundaries, with zero provider cost.
  • All six native hosted checks completed successfully: dead-code, verify-go, validate, verify-install-smoke, verify-website, and security-scan.
  • Codex review addressed the synthetic confidence defect. No GitHub review threads or requested changes remain.

Tracking: PCTL-17. Original Pi run dc767eb795e6125a and candidates remain preserved in history. Owner authorized merge. Use a merge commit to preserve stacked PR #66's dependency. No external release or website deployment.

@hudsonaikins
hudsonaikins marked this pull request as ready for review October 4, 2026 00:40
@makeplane

makeplane Bot commented Oct 4, 2026

Copy link
Copy Markdown

Linked to Plane Work Item(s)

References

This comment was auto-generated by Plane

@hudsonaikins
hudsonaikins merged commit d597e4d into main Oct 4, 2026
6 checks passed
@hudsonaikins
hudsonaikins deleted the autonomous/dc767eb795e6125a/integration-0 branch October 4, 2026 00:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fa8ab4347

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}, nil
}

var timeUnits = map[string]float64{"second": 1, "minute": 60, "hour": 3600, "day": 86400, "week": 604800}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize all contract-supported time units

The cost contract permits month and year for cadence denominators and uptime quantities (pkg/contracts/cost/v1/validate.go:417-424), but neither is in this conversion map. Consequently, a valid paired uptime driver expressed as months in the forecast and years in the actual is rejected as having incompatible units at the check above, whereas the supported hour/minute case is normalized. Add the remaining canonical time units (with the repository's intended calendar basis) so equivalent valid inputs can be compared.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread cmd/cost_explain.go
if err := ctx.Err(); err != nil {
return nil, err
}
if report == nil || report.SchemaVersion != variancev1.SchemaVersion || (report.Status != "available" && report.Status != "unavailable") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the complete diff result before explaining it

When profitctl explain reads a hand-edited or corrupt diff file, this gate accepts any report with the right version and status if its arithmetic reconciles, then copies unchecked fields into the output. For example, blank observation IDs, an empty window, and empty money currencies can pass with a zero forecast and reconciled actual/variance/residual, but the emitted explanation is rejected by schemas/cost-variance/v1/schema.json (which requires non-empty IDs, RFC3339 window timestamps, and three-letter currencies). Validate the complete Result contract before constructing the report.

AGENTS.md reference: AGENTS.md:L27-L27

Useful? React with 👍 / 👎.

Comment on lines +266 to +270
q := d.Quantity.Value
per := d.UnitPrice.Per.Value
if scale := timeUnits[d.Quantity.Unit]; scale > 0 {
q *= scale
per *= scale

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor cadence scale denominators in comparisons

CostDriver.Per is the required cadence time denominator, but factors never reads it. Therefore the same cadence entered as 10 command/minute for the forecast and 600 command/hour for the actual is treated as a 60-fold quantity change, producing a false quantity contribution (and offsetting residual) even when both describe the same workload over the same window. Normalize the driver scale basis, or reject differing bases, before applying the decomposition.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment on lines +287 to +288
if d.Distribution.Floor != nil {
dist = math.Max(dist, *d.Distribution.Floor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Calculate floored distribution expectations

math.Max only clamps an unconstrained distribution mean; it does not calculate E[max(X, floor)]. For example, a valid normal variable driver with mean 1, standard deviation 10, and floor 0—used to mirror the simulator's per-sample non-negative clamp—gets a factor of 1 here even though the simulator's expected factor is about 4.51. This understates modeled variance for such workloads and incorrectly moves it into residual; use the truncated-distribution expectation (and analogous floor calculations for the other distributions).

AGENTS.md reference: AGENTS.md:L27-L27

Useful? React with 👍 / 👎.

Comment thread cmd/cost_explain.go
return nil, errors.New("missing_evidence must identify actual cost and name unavailable_reason when actual is unavailable")
}
}
out := &ExplainReport{SchemaVersion: report.SchemaVersion, Status: report.Status, Window: report.Window, ForecastObservationID: report.ForecastObservationID, ActualObservationID: report.ActualObservationID, UnavailableReason: report.UnavailableReason, Forecast: report.Forecast, Actual: report.Actual, VarianceAbsolute: report.AbsoluteVariance, VariancePercent: report.PercentageVariance, Residual: report.Residual, DriverContributions: []DriverContribution{}, MissingEvidence: report.MissingEvidence, Sources: report.Sources, Confidence: costv1.ConfidenceLow, ConfidenceScore: 0}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Give explain reports a distinct schema version

The diff result and explain report both emit profitctl.cost-variance/v1, but their required shapes are incompatible: Result uses absolute_variance, percentage_variance, and contributions, while ExplainReport uses variance_absolute, variance_percent, and driver_contributions. Because the explanation copies the result's version here, downstream consumers cannot route or validate a document by its declared schema version—each output is rejected by the other cost-variance/v1 schema. Give the explanation its own schema version/type, or retain a compatible envelope.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant