clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / internal / enrich / maturity / gate.go
gate.go 3.0K
1package maturity23import (4 "fmt"56 "github.com/FelineStateMachine/atlas/format/bundle"7)89// Comparison is what the monotonicity gate found.10type Comparison struct {11 // Before and After are the two builds' totals.12 Before, After int13 // Delta is After minus Before.14 Delta int15 // Allowance is how much decrease the later build's ledger accounts for.16 Allowance int17 // Comparable is false when the two were scored under different point table18 // versions, in which case nothing is concluded.19 Comparable bool20 // Reasons are the corrections the allowance came from.21 Reasons []string22}2324// Compare measures two scores against each other without deciding anything.25func Compare(before, after *Score) Comparison {26 out := Comparison{Before: before.Total, After: after.Total, Delta: after.Total - before.Total}27 out.Comparable = before.TableVersion == after.TableVersion28 for _, line := range after.Ledger {29 for _, correction := range line.Account.Corrections {30 out.Allowance += correction.Points31 out.Reasons = append(out.Reasons, correction.Reason)32 }33 }34 return out35}3637// Gate is the build gate: an enrichment whose score declines fails.38//39// Two builds are compared only under the same point table version. A40// re-weighting is a new version of the table, not a mass failure of every build41// in the library, so a comparison across versions concludes nothing and passes.42//43// A decline is permitted up to what the later build's ledger accounts for in44// corrections. That is the one shape a decrease may take, and it has to be45// written down: the gate exists to reward good data, never to punish the46// removal of data that was wrong.47func Gate(before, after *Score) error {48 comparison := Compare(before, after)49 if !comparison.Comparable {50 return nil51 }52 if comparison.Delta >= 0 {53 return nil54 }55 if -comparison.Delta <= comparison.Allowance {56 return nil57 }58 return fmt.Errorf(59 "maturity fell from %d to %d (%d points) and the ledger accounts for %d of it: "+60 "a build that lost quality does not supersede the build it came from "+61 "(see docs/enrich.md, the monotonicity gate)",62 comparison.Before, comparison.After, -comparison.Delta, comparison.Allowance)63}6465// Serving picks the build a registry would serve out of several scores of one66// volume: the registry's own fold, restated over measurements, so a report67// lists builds in the order a reader would actually meet them.68func Serving(scores []*Score) *Score {69 var winner *Score70 for _, score := range scores {71 if winner == nil || newer(score, winner) {72 winner = score73 }74 }75 return winner76}7778// newer is bundle.Newer over scores. The descriptor is built rather than79// re-implemented, so the ordering has exactly one definition and it lives in80// the format.81func newer(a, b *Score) bool {82 return bundle.Newer(descriptorOf(a), descriptorOf(b))83}8485func descriptorOf(s *Score) bundle.Descriptor {86 return bundle.Descriptor{87 Locator: s.Path,88 Slug: s.Volume,89 Title: s.Title,90 Stamp: s.Stamp,91 CreatedAt: s.CreatedAt,92 Revision: s.Revision,93 }94}