451ea08414
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---
118 lines
3.5 KiB
Go
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:])
|
|
}
|