From d910d8fef453d82344dc8b8424e2485962a10e0b Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sat, 17 Oct 2026 17:25:15 +0200 Subject: [PATCH 1/4] feat(archera): build the recommendation comparison response with exact decimals and null currency Co-Authored-By: Claude Opus 5.5 --- internal/archera/recommendation_contract.go | 16 +- internal/archera/recommendation_dto.go | 138 +++++++++++ internal/archera/recommendation_dto_test.go | 247 ++++++++++++++++++++ 3 files changed, 397 insertions(+), 4 deletions(-) create mode 100644 internal/archera/recommendation_dto.go create mode 100644 internal/archera/recommendation_dto_test.go diff --git a/internal/archera/recommendation_contract.go b/internal/archera/recommendation_contract.go index 0549f7cc..87d8bdb7 100644 --- a/internal/archera/recommendation_contract.go +++ b/internal/archera/recommendation_contract.go @@ -27,17 +27,25 @@ func ProjectContract(c *insurance.Comparison, items []insurance.LineItem) ([]Com return rows, identities, nil } -// vendorAlternatives drops the entry the vendor flags as the current offer, which is never an alternative. -func vendorAlternatives(entries []insurance.OfferEntry) []CandidateIdentity { - var out []CandidateIdentity +// vendorEntries drops the entry the vendor flags as the current offer, which is never an alternative. +func vendorEntries(entries []insurance.OfferEntry) []*insurance.OfferEntry { + var out []*insurance.OfferEntry for i := range entries { if !entries[i].IsCurrent { - out = append(out, contractCandidate(&entries[i])) + out = append(out, &entries[i]) } } return out } +func vendorAlternatives(entries []insurance.OfferEntry) []CandidateIdentity { + var out []CandidateIdentity + for _, e := range vendorEntries(entries) { + out = append(out, contractCandidate(e)) + } + return out +} + func contractLineItem(li *insurance.LineItem) LineItemIdentity { offer := &li.Offer kind := offer.Type diff --git a/internal/archera/recommendation_dto.go b/internal/archera/recommendation_dto.go new file mode 100644 index 00000000..b5bc99e6 --- /dev/null +++ b/internal/archera/recommendation_dto.go @@ -0,0 +1,138 @@ +package archera + +import ( + "errors" + "strings" + "time" + + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" +) + +// errRecommendationRowMissing is returned when an associated match has no vendor row to render. +var errRecommendationRowMissing = errors.New("archera recommendation row is unavailable") + +// RecommendationComparisonDTO is the recommendation-only response: one current offer and its +// vendor-linked candidates. It carries no plan totals, deltas, guarantees, source account or org ID. +type RecommendationComparisonDTO struct { + RecommendationID string `json:"recommendation_id"` + Status MatchStatus `json:"status"` + Reason Reason `json:"reason"` + AmountProvenance Reason `json:"amount_provenance"` + EconomicStatus Reason `json:"economic_status"` + FetchedAt string `json:"fetched_at"` + Currency *string `json:"currency"` + CurrencyNote string `json:"currency_note"` + ComparableDelta *string `json:"comparable_delta"` + PremiumIncluded bool `json:"premium_included"` + Current *RecommendationOfferDTO `json:"current"` + Candidates []RecommendationAlternativeDTO `json:"candidates"` + NonGatingDisclosure string `json:"non_gating_disclosure"` + SponsorshipDisclosure string `json:"sponsorship_disclosure"` +} + +// RecommendationOfferDTO names the vendor's units explicitly. The native_* fields come from the +// line item and stay null on candidates, which the vendor does not give a native identity. +type RecommendationOfferDTO struct { + OfferID string `json:"offer_id"` + Provider string `json:"provider"` + CommitmentType string `json:"commitment_type"` + HourlySelectedCommitment *string `json:"hourly_selected_commitment"` + NativeDurationSeconds *string `json:"native_duration_seconds"` + NativePlanType *string `json:"native_plan_type"` + NativeInstanceFamily *string `json:"native_instance_family"` + EffectiveContractTerm *string `json:"effective_contract_term"` + PaymentOption *string `json:"payment_option"` + LeaseAttached bool `json:"lease_attached"` + Monthly FinancialsDTO `json:"monthly"` + UpfrontCost *string `json:"upfront_one_time_cost"` +} + +type RecommendationAlternativeDTO struct { + OfferID string `json:"offer_id"` + Status CandidateStatus `json:"status"` + Reason Reason `json:"reason"` + NativeIdentityStatus CandidateStatus `json:"native_identity_status"` + Figures *RecommendationOfferDTO `json:"figures"` +} + +// BuildRecommendationComparison matches the stored recommendation against the vendor line items +// and renders only the matched row. An unmatched result carries no figures. It returns +// ErrUnrepresentable when a rendered number has no exact decimal form. +func BuildRecommendationComparison(id, planID string, rec *RecommendationIdentity, c *insurance.Comparison, items []insurance.LineItem) (*RecommendationComparisonDTO, error) { + rows, identities, err := ProjectContract(c, items) + if err != nil { + return nil, err + } + match, err := MatchRecommendation(planID, rec, rows, identities) + if err != nil { + return nil, err + } + out := &RecommendationComparisonDTO{ + RecommendationID: id, Status: match.Status, Reason: match.Reason, + AmountProvenance: match.AmountProvenance, EconomicStatus: ReasonIdentityUnproven, + FetchedAt: c.FetchedAt.UTC().Format(time.RFC3339), CurrencyNote: currencyNote, + PremiumIncluded: true, Candidates: []RecommendationAlternativeDTO{}, + NonGatingDisclosure: common.ArcheraNonGatingDisclosure, SponsorshipDisclosure: common.ArcheraSponsorshipDisclosure, + } + if match.Status != ResourceAssociated { + return out, nil + } + out.EconomicStatus = match.EconomicStatus + m := &mapper{} + if err := m.fillMatched(out, &match, c, items); err != nil { + return nil, err + } + return out, nil +} + +func (m *mapper) fillMatched(out *RecommendationComparisonDTO, match *RecommendationMatch, c *insurance.Comparison, items []insurance.LineItem) error { + row := findRow(c.Rows, match.LineItemID) + if row == nil { + return errRecommendationRowMissing + } + out.Current = m.recommendationOffer(&row.Current) + out.Candidates = m.recommendationAlternatives(vendorEntries(row.Candidates), match.Candidates) + for i := range items { + if strings.EqualFold(items[i].ID, match.LineItemID) { + offer := &items[i].Offer + out.Current.NativeDurationSeconds = m.dec(offer.DurationSeconds) + out.Current.NativePlanType = cleanPtr(offer.PlanType) + out.Current.NativeInstanceFamily = cleanPtr(offer.InstanceFamily) + } + } + return m.err +} + +func findRow(rows []insurance.ComparisonRow, lineItemID string) *insurance.ComparisonRow { + for i := range rows { + if strings.EqualFold(rows[i].LineItemID, lineItemID) { + return &rows[i] + } + } + return nil +} + +func (m *mapper) recommendationOffer(e *insurance.OfferEntry) *RecommendationOfferDTO { + return &RecommendationOfferDTO{ + OfferID: CleanString(e.OfferID), Provider: CleanString(string(e.Provider)), CommitmentType: CleanString(e.CommitmentType), + HourlySelectedCommitment: m.dec(e.SelectedAmount), EffectiveContractTerm: cleanPtr(e.ContractTerm), + PaymentOption: payment(e.PaymentOption), LeaseAttached: e.LeaseBacked(), + Monthly: m.financials(e.Monthly), UpfrontCost: m.dec(e.UpfrontCost), + } +} + +// recommendationAlternatives pairs the vendor alternatives with their associations by index. +func (m *mapper) recommendationAlternatives(entries []*insurance.OfferEntry, associations []CandidateAssociation) []RecommendationAlternativeDTO { + out := make([]RecommendationAlternativeDTO, 0, len(associations)) + for i := range associations { + a := &associations[i] + alt := RecommendationAlternativeDTO{OfferID: CleanString(a.OfferID), Status: a.Status, + Reason: a.Reason, NativeIdentityStatus: a.NativeIdentityStatus} + if a.Status == CandidateVendorAlternative { + alt.Figures = m.recommendationOffer(entries[i]) + } + out = append(out, alt) + } + return out +} diff --git a/internal/archera/recommendation_dto_test.go b/internal/archera/recommendation_dto_test.go new file mode 100644 index 00000000..5576493e --- /dev/null +++ b/internal/archera/recommendation_dto_test.go @@ -0,0 +1,247 @@ +package archera + +import ( + "encoding/json" + "math/big" + "strings" + "testing" + + "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" + "github.com/stretchr/testify/require" +) + +func buildFixture(t *testing.T, c *insurance.Comparison, items []insurance.LineItem) (*RecommendationComparisonDTO, error) { + t.Helper() + record := storedMatchFixture() + rec, err := ProjectRecommendation(&record) + require.NoError(t, err) + return BuildRecommendationComparison("requested-row", matchPlan, rec, c, items) +} + +func mustMarshal(t *testing.T, v any) []byte { + t.Helper() + b, err := json.Marshal(v) + require.NoError(t, err) + return b +} + +func TestRecommendationDTOMapsEveryFieldFromDistinctValues(t *testing.T) { + c, items := contractMatchFixture(t) + cur := &c.Rows[0].Current + cur.SelectedAmount = mustRat(t, "0.50000000000000000001") + cur.Monthly = insurance.Financials{CommitmentCostTotal: big.NewRat(101, 1), CloudProviderCost: big.NewRat(102, 1), + Premium: big.NewRat(103, 1), GrossSavings: big.NewRat(104, 1), NetSavings: big.NewRat(105, 1), CoveredOnDemandCost: big.NewRat(106, 1)} + cur.UpfrontCost = big.NewRat(107, 1) + cand := &c.Rows[0].Candidates[0] + cand.Monthly = insurance.Financials{CommitmentCostTotal: big.NewRat(201, 1), CloudProviderCost: big.NewRat(202, 1), + Premium: big.NewRat(203, 1), GrossSavings: big.NewRat(204, 1), NetSavings: big.NewRat(205, 1), CoveredOnDemandCost: big.NewRat(206, 1)} + cand.UpfrontCost = big.NewRat(207, 1) + lease := "synthetic-lease" + cand.LeaseMenuItemID = &lease + c.Current.UpfrontCost = big.NewRat(987654321, 1) + beforeC, beforeItems := mustMarshal(t, c), mustMarshal(t, items) + + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Equal(t, "requested-row", got.RecommendationID) + require.Equal(t, ResourceAssociated, got.Status) + require.Empty(t, got.Reason) + require.Equal(t, ReasonStoredDecimalOnly, got.AmountProvenance) + require.Equal(t, ReasonUnavailableSourcePrecision, got.EconomicStatus) + require.Equal(t, "1970-01-01T00:00:01Z", got.FetchedAt) + require.Nil(t, got.Currency) + require.Nil(t, got.ComparableDelta) + require.Equal(t, currencyNote, got.CurrencyNote) + require.True(t, got.PremiumIncluded) + require.NotEmpty(t, got.NonGatingDisclosure) + require.NotEmpty(t, got.SponsorshipDisclosure) + + want := RecommendationOfferDTO{OfferID: matchOffer, Provider: "aws", CommitmentType: "savings_plan", + HourlySelectedCommitment: ptr("0.50000000000000000001"), NativeDurationSeconds: ptr("31536000"), + NativePlanType: ptr("EC2Instance"), NativeInstanceFamily: ptr("m5"), EffectiveContractTerm: ptr("one_year_gris"), + PaymentOption: ptr("no_upfront"), LeaseAttached: false, + Monthly: FinancialsDTO{CommitmentCostTotal: ptr("101"), CloudProviderCost: ptr("102"), Premium: ptr("103"), + GrossSavings: ptr("104"), NetSavings: ptr("105"), CoveredOnDemandCost: ptr("106")}, + UpfrontCost: ptr("107")} + require.Equal(t, want, *got.Current) + + require.Len(t, got.Candidates, 2) + alt := got.Candidates[0] + require.Equal(t, CandidateVendorAlternative, alt.Status) + require.Equal(t, CandidateUnavailable, alt.NativeIdentityStatus) + wantAlt := RecommendationOfferDTO{OfferID: "faadf00d-ffff-4aba-8ada-abcdefabcdef", Provider: "aws", CommitmentType: "savings_plan", + HourlySelectedCommitment: ptr("0.50000000000000000001"), EffectiveContractTerm: ptr("one_year_gris"), + PaymentOption: ptr("all_upfront"), LeaseAttached: true, + Monthly: FinancialsDTO{CommitmentCostTotal: ptr("201"), CloudProviderCost: ptr("202"), Premium: ptr("203"), + GrossSavings: ptr("204"), NetSavings: ptr("205"), CoveredOnDemandCost: ptr("206")}, + UpfrontCost: ptr("207")} + require.Equal(t, wantAlt, *alt.Figures) + require.Nil(t, alt.Figures.NativeDurationSeconds) + require.Nil(t, alt.Figures.NativePlanType) + require.Nil(t, alt.Figures.NativeInstanceFamily) + + wire := string(mustMarshal(t, got)) + for _, leak := range []string{"Source-account-a", "fixture-org", "987654321", "hypotheticals", "delta_vs_current", "provider-offer-a", "guarantee"} { + require.NotContains(t, wire, leak) + } + require.Equal(t, beforeC, mustMarshal(t, c)) + require.Equal(t, beforeItems, mustMarshal(t, items)) +} + +func TestRecommendationDTOKeepsZeroDistinctFromNull(t *testing.T) { + c, items := contractMatchFixture(t) + c.Rows[0].Current.UpfrontCost = new(big.Rat) + c.Rows[0].Current.Monthly = insurance.Financials{Premium: new(big.Rat)} + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Equal(t, "0", *got.Current.UpfrontCost) + require.Equal(t, "0", *got.Current.Monthly.Premium) + require.Nil(t, got.Current.Monthly.NetSavings) +} + +func TestRecommendationDTOKeepsVeryPreciseDecimalsExact(t *testing.T) { + c, items := contractMatchFixture(t) + precise := mustRat(t, "123456789012345678.123456789012345678901234567890") + c.Rows[0].Current.UpfrontCost = precise + c.Rows[0].Current.Monthly.NetSavings = mustRat(t, "-0.000000000000000000000001") + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Equal(t, "123456789012345678.12345678901234567890123456789", *got.Current.UpfrontCost) + require.Equal(t, "-0.000000000000000000000001", *got.Current.Monthly.NetSavings) +} + +func TestRecommendationDTOUnmatchedCarriesNoFigures(t *testing.T) { + for name, change := range map[string]func(*RecommendationIdentity, *insurance.Comparison, *[]insurance.LineItem){ + "source": func(r *RecommendationIdentity, _ *insurance.Comparison, _ *[]insurance.LineItem) { + r.SourceAccountID = "other" + }, + "ambiguous": func(_ *RecommendationIdentity, c *insurance.Comparison, i *[]insurance.LineItem) { + dup := (*i)[0] + dup.ID = "22222222-2222-4222-8222-222222222222" + *i = append(*i, dup) + row := c.Rows[0] + row.LineItemID = dup.ID + c.Rows = append(c.Rows, row) + }, + } { + t.Run(name, func(t *testing.T) { + c, items := contractMatchFixture(t) + c.Rows[0].Current.UpfrontCost = big.NewRat(1, 3) + record := storedMatchFixture() + rec, err := ProjectRecommendation(&record) + require.NoError(t, err) + change(rec, c, &items) + got, err := BuildRecommendationComparison("requested-row", matchPlan, rec, c, items) + require.NoError(t, err) + require.Equal(t, MatchUnavailable, got.Status) + require.NotEmpty(t, got.Reason) + require.Empty(t, got.AmountProvenance) + require.Equal(t, ReasonIdentityUnproven, got.EconomicStatus) + require.Nil(t, got.Current) + require.Empty(t, got.Candidates) + require.Nil(t, got.Currency) + require.Nil(t, got.ComparableDelta) + require.NotContains(t, string(mustMarshal(t, got)), "faadf00d") + }) + } +} + +func TestRecommendationDTOCandidateMismatchAndUnrepresentable(t *testing.T) { + c, items := contractMatchFixture(t) + c.Rows[0].Candidates[0].SelectedAmount = big.NewRat(1, 2) + c.Rows[0].Candidates[0].UpfrontCost = big.NewRat(1, 3) + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Equal(t, ResourceAssociated, got.Status) + require.Nil(t, got.Candidates[0].Figures) + require.Equal(t, CandidateUnavailable, got.Candidates[0].Status) + require.Equal(t, ReasonCandidateBasisUnproven, got.Candidates[0].Reason) + require.NotNil(t, got.Candidates[1].Figures) + + for name, fn := range map[string]func(*insurance.Comparison){ + "current": func(c *insurance.Comparison) { c.Rows[0].Current.UpfrontCost = big.NewRat(1, 3) }, + "candidate": func(c *insurance.Comparison) { c.Rows[0].Candidates[1].Monthly.Premium = big.NewRat(1, 3) }, + } { + t.Run(name, func(t *testing.T) { + c, items := contractMatchFixture(t) + fn(c) + got, err := buildFixture(t, c, items) + require.ErrorIs(t, err, ErrUnrepresentable) + require.Nil(t, got) + }) + } + _, err = buildFixture(t, nil, items) + require.Error(t, err) +} + +func TestRecommendationDTOMissingRowIsAnError(t *testing.T) { + c, items := contractMatchFixture(t) + record := storedMatchFixture() + rec, err := ProjectRecommendation(&record) + require.NoError(t, err) + rows, ids, err := ProjectContract(c, items) + require.NoError(t, err) + match, err := MatchRecommendation(matchPlan, rec, rows, ids) + require.NoError(t, err) + c.Rows = nil + m := &mapper{} + require.ErrorIs(t, m.fillMatched(&RecommendationComparisonDTO{}, &match, c, items), errRecommendationRowMissing) +} + +func TestRecommendationDTONeverListsCurrentOfferAsAlternative(t *testing.T) { + c, items := contractMatchFixture(t) + row := &c.Rows[0] + currentCopy := row.Current + currentCopy.UpfrontCost = big.NewRat(99, 1) + row.Candidates[0].UpfrontCost = big.NewRat(31, 1) + row.Candidates[1].SelectedAmount = big.NewRat(1, 2) + row.Candidates = append([]insurance.OfferEntry{currentCopy}, row.Candidates...) + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Len(t, got.Candidates, 2) + require.Equal(t, "31", *got.Candidates[0].Figures.UpfrontCost) + require.Nil(t, got.Candidates[1].Figures) + require.Equal(t, ReasonCandidateBasisUnproven, got.Candidates[1].Reason) +} + +func TestRecommendationDTOIgnoresUnrelatedRows(t *testing.T) { + c, items := contractMatchFixture(t) + unrelated := c.Rows[0] + unrelated.LineItemID = "22222222-2222-4222-8222-222222222222" + unrelated.Current.UpfrontCost = big.NewRat(1, 3) + c.Rows = append(c.Rows, unrelated) + other := items[0] + other.ID = unrelated.LineItemID + src := "another-source" + other.AccountID = &src + items = append(items, other) + got, err := buildFixture(t, c, items) + require.NoError(t, err) + require.Equal(t, ResourceAssociated, got.Status) + require.Len(t, got.Candidates, 2) + require.NotContains(t, string(mustMarshal(t, got)), "another-source") +} + +func TestRecommendationDTOSanitizesVendorStrings(t *testing.T) { + c, items := contractMatchFixture(t) + hostile := "one_year‮\n