Files
openyield/web/handlers/standing.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

60 lines
1.7 KiB
Go

package handlers
import (
"net/http"
standingtypes "github.com/oy/openyield/x/standing/types"
)
// registerStanding wires the Standing + Freeholder signals route (REQ-043).
func (s *Server) registerStanding(mux *http.ServeMux) {
mux.HandleFunc("GET /standing/{reachID}", s.handleStanding)
}
// standingViewData is the template data for the Standing screen.
type standingViewData struct {
ReachID string
Found bool
Score float64
Bucket standingtypes.StandingBucket
Ratings []standingtypes.Rating
Vouches []standingtypes.Vouch
Slashes []standingtypes.Slash
Signals standingtypes.FreeholderSignals
Eligible bool
MinScore float64
MinCats int
}
// handleStanding renders the Standing + Freeholder signals progress (REQ-043).
// Computed from mock Ratings/Vouches/Slashes using the locked x/standing/types
// constants + GetStandingBucket/ComputeDiversityBonus/GetVoucherWeight; the
// 4-signal progress via FreeholderSignals.IsFreeholderEligible().
func (s *Server) handleStanding(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("reachID")
_, ok := s.Store.GetReach(id)
if !ok {
http.NotFound(w, r)
return
}
score, bucket := s.Store.ComputeStandingScore(id)
ratings := s.Store.ListRatings(id)
vouches := s.Store.ListVouches(id)
slashes := s.Store.ListSlashes(id)
signals := s.Store.ComputeFreeholderSignals(id)
s.render(w, "standing.html", standingViewData{
ReachID: id,
Found: true,
Score: score,
Bucket: bucket,
Ratings: ratings,
Vouches: vouches,
Slashes: slashes,
Signals: signals,
Eligible: signals.IsFreeholderEligible(),
MinScore: standingtypes.FreeholderMinStandingScore,
MinCats: standingtypes.FreeholderMinCategories,
})
}