56db37a463
web/handlers/bloom.go: GET /bloom/{stashID} renders BloomRecord
(AccruedGrain, RateBasisPoints as %, LastAccrualBlock) + the 4.5%
target rate read from x/bloom/types.TargetBloomRateBasisPoints (D-073
code-constant source-of-truth, NOT hardcoded) + the 4.0%-5.0% band
(Min/MaxBloomRateBasisPoints) + Mission Lock note. Store extensions:
GetBloomRecord/ListBloomRecords. Seed: holder-alia at target rate (450
bps), holder-bryn at 420 bps (within band). bloom_test.go: D-073
target-rate source-of-truth regression guard (asserts TargetBloomRate-
BasisPoints=450, Min=400, Max=500 from code constants; would fail if
hardcoded), G-026 error lexicon check, highest-risk screen for banned-
term drift (uses "Bloom"/"real production"/"accrual" labels only).
Coverage: store 98.1%, handlers 89.2%.
---ci---
project: oy
phase: 5
milestone: v0.6
status: execute
---/ci---
138 lines
4.3 KiB
Go
138 lines
4.3 KiB
Go
package store
|
|
|
|
import (
|
|
"time"
|
|
|
|
bloomtypes "github.com/oy/openyield/x/bloom/types"
|
|
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)
|
|
seedBloom(s, now)
|
|
}
|
|
|
|
// seedBloom seeds mock BloomRecords per Stash (P5). holder-alia gets a record
|
|
// at the target rate (450 bps = 4.5%); holder-bryn gets a record at 420 bps
|
|
// (4.2%, within the 4.0%-5.0% band). AccruedGrain is a mock value.
|
|
func seedBloom(s *Store, now int64) {
|
|
s.bloomRecords["stash-holder-alia"] = bloomtypes.BloomRecord{
|
|
StashID: "stash-holder-alia",
|
|
AccruedGrain: 45000,
|
|
LastAccrualBlock: 1000,
|
|
RateBasisPoints: bloomtypes.TargetBloomRateBasisPoints, // 450 (4.5%, D-073 code constant)
|
|
}
|
|
s.bloomRecords["stash-holder-bryn"] = bloomtypes.BloomRecord{
|
|
StashID: "stash-holder-bryn",
|
|
AccruedGrain: 18000,
|
|
LastAccrualBlock: 1000,
|
|
RateBasisPoints: 420, // 4.2% (within the 400-500 band)
|
|
}
|
|
}
|
|
|
|
// 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:])
|
|
}
|