Files
openyield/web/store/fixtures.go
T
cloudinit-bot 451ea08414 feat(P4): Standing + Freeholder signals progress (REQ-043)
web/handlers/standing.go: GET /standing/{reachID} renders standing score
+ bucket + 4-signal progress + Freeholder-eligible badge. Store extensions:
ListRatings/ListVouches/ListSlashes/ComputeStandingScore (simplified, from
locked x/standing/types constants PriorMean/PriorWeight/ComputeDiversityBonus/
GetVoucherWeight/GetStandingBucket — NOT hardcoded)/ComputeFreeholderSignals
(real FreeholderSignals struct + IsFreeholderEligible). Seed: holder-alia
(12 ratings, 4 cats, 1 Vouch, mature Stash -> eligible) vs holder-bryn
(3 ratings, 1 cat, no Vouch, immature -> not eligible). standing_test.go:
computed-from-locked-constants regression guard (PriorMean=4.0, PriorWeight=
10, DiversityBonus4Cats=0.10), Freeholder-eligible badge reflects
IsFreeholderEligible() (the real method), G-026 error lexicon check.
Coverage: store 98.0%, handlers 88.7%.

---ci---
project: oy
phase: 4
milestone: v0.6
status: execute
---/ci---
2026-08-18 19:02:44 +00:00

118 lines
3.5 KiB
Go

package store
import (
"time"
identitytypes "github.com/oy/openyield/x/identity/types"
standingtypes "github.com/oy/openyield/x/standing/types"
stashtypes "github.com/oy/openyield/x/stash/types"
)
// seed populates the store with a few pre-existing Reach/Stash pairs for the
// list view. All strings lexicon-clean ("Holder"/"Reach"/"Stash"; NOT the
// banned financial terms). Two fixtures: one mature (90+ active days),
// one immature (45 active days) so the Stash dashboard (P2) can show both
// states. P4 seeds Ratings/Vouches so the Standing screen can show a
// Freeholder-eligible Reach (holder-alia) vs a non-eligible one (holder-bryn).
func (s *Store) seed() {
now := time.Now().Unix()
seedOne(s, "holder-alia", "pk-alia-001", now, 920000, 92, 10)
seedOne(s, "holder-bryn", "pk-bryn-002", now, 410000, 45, 5)
seedStanding(s, now)
}
// seedStanding seeds mock Ratings + Vouches. holder-alia gets 12 ratings
// across 4 categories at 4.6-4.9 (Freeholder-eligible: score >= 4.5 in >= 3
// cats) + 1 Vouch (CommunityEndorsement). holder-bryn gets 3 ratings in 1
// category (not eligible: < 3 categories, no Vouch).
func seedStanding(s *Store, now int64) {
// holder-alia: 12 ratings, 4 categories, scores 4.6-4.9.
aliaCats := []string{"care", "sim", "vault", "mail"}
for i := 0; i < 12; i++ {
cat := aliaCats[i%4]
score := 4.6 + float64(i%4)*0.1 // 4.6, 4.7, 4.8, 4.9 repeating
s.ratings["holder-alia"] = append(s.ratings["holder-alia"], standingtypes.Rating{
RaterID: "rater-" + itoa(i),
RateeID: "holder-alia",
Category: cat,
Score: score,
Weight: 1.0,
TxRef: "tx-r-" + itoa(i),
Timestamp: now - int64(i)*86400,
DecayBucket: 0, // 6mo bucket (1.0)
})
}
// 1 Vouch for holder-alia (CommunityEndorsement signal).
s.vouches["holder-alia"] = []standingtypes.Vouch{{
VoucherID: "voucher-freeholder-1",
VoucheeID: "holder-alia",
Category: "care",
BondAmount: 100000,
Timestamp: now,
}}
// holder-bryn: 3 ratings, 1 category, scores 4.0-4.2 (not eligible: < 3 cats).
for i := 0; i < 3; i++ {
s.ratings["holder-bryn"] = append(s.ratings["holder-bryn"], standingtypes.Rating{
RaterID: "rater-b-" + itoa(i),
RateeID: "holder-bryn",
Category: "care",
Score: 4.0 + float64(i)*0.1,
Weight: 1.0,
TxRef: "tx-b-" + itoa(i),
Timestamp: now - int64(i)*86400,
DecayBucket: 0,
})
}
// No Vouches for holder-bryn (CommunityEndorsement signal false).
}
func seedOne(s *Store, holderID, pubKey string, now int64, balanceGrain int64, activeDays, maxGap uint32) {
reachID := "reach-" + holderID
stashID := "stash-" + holderID
s.reaches[holderID] = identitytypes.Reach{
ReachID: reachID,
HolderID: holderID,
CreatedAt: now - int64(activeDays)*86400,
PublicKey: pubKey,
IsNomad: true,
}
s.stashes[holderID] = stashtypes.Stash{
HolderID: holderID,
StashID: stashID,
CreatedAt: now - int64(activeDays)*86400,
LastActive: now,
BalanceGrain: balanceGrain,
}
s.stashActivities[stashID] = stashtypes.StashActivity{
StashID: stashID,
ActiveDays: activeDays,
MaxGapDays: maxGap,
LastActivityDay: now,
}
}
// itoa is a tiny int->string helper to avoid importing strconv (keeps the
// fixtures file import-light; the mock data uses small integers only).
func itoa(n int) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
var buf [12]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
if neg {
i--
buf[i] = '-'
}
return string(buf[i:])
}