Add quota receipt aggregation and approved standup advisories
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/store"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -29,6 +30,31 @@ type StandupItem struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// QuotaReceipt is the native usage receipt produced by a harness. Receipts
|
||||
// are additive: a task that crosses rotations contributes each rotation's
|
||||
// receipt to every window containing it.
|
||||
type QuotaReceipt struct {
|
||||
HarnessID string `json:"harness_id"`
|
||||
Consumed float64 `json:"consumed"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// AggregateQuota sums native receipts in the requested window. It does not
|
||||
// de-duplicate rotations or use a cumulative session total.
|
||||
func AggregateQuota(events []domain.Event, from, to time.Time) map[string]float64 {
|
||||
out := map[string]float64{}
|
||||
for _, e := range events {
|
||||
if e.Type != "QuotaReported" || e.At.Before(from) || e.At.After(to) {
|
||||
continue
|
||||
}
|
||||
var r QuotaReceipt
|
||||
if json.Unmarshal(e.Payload, &r) == nil && r.HarnessID != "" && r.Consumed >= 0 {
|
||||
out[r.HarnessID] += r.Consumed
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func StandupItems(tasks []domain.Task) []StandupItem {
|
||||
out := make([]StandupItem, 0)
|
||||
for _, t := range tasks {
|
||||
@@ -59,15 +85,73 @@ func BuildBrief(events []domain.Event, from, to time.Time, git GitSync) Brief {
|
||||
b.NeedsAttention = append(b.NeedsAttention, e)
|
||||
case "ApprovalRequested":
|
||||
b.NeedsAttention = append(b.NeedsAttention, e)
|
||||
case "QuotaReported":
|
||||
if h, ok := p["harness_id"].(string); ok {
|
||||
if n, ok := p["consumed"].(float64); ok {
|
||||
b.Quota[h] += n
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Quota = AggregateQuota(events, from, to)
|
||||
return b
|
||||
}
|
||||
|
||||
// GenerateStandupAdvisory creates a persisted, read-only recommendation.
|
||||
// Applying it is intentionally a separate approval-gated operation.
|
||||
func GenerateStandupAdvisory(s *store.Store, at time.Time) (domain.Event, error) {
|
||||
items := StandupItems(s.Tasks())
|
||||
p, err := json.Marshal(map[string]any{"items": items, "generated_at": at.UTC()})
|
||||
if err != nil {
|
||||
return domain.Event{}, err
|
||||
}
|
||||
e := domain.Event{ID: domain.NewID(), TaskID: "system", Type: "StandupAdvisory", Payload: p, At: at}
|
||||
return e, s.Append(e)
|
||||
}
|
||||
|
||||
// ApplyAdvisory applies only approved title recommendations. Unknown or
|
||||
// malformed recommendations are ignored, while event conflicts are returned.
|
||||
func ApplyAdvisory(s *store.Store, advisoryID string) ([]domain.Event, error) {
|
||||
var advisory *domain.Event
|
||||
approved := false
|
||||
for _, e := range s.Events(0) {
|
||||
if e.ID == advisoryID && e.Type == "StandupAdvisory" {
|
||||
x := e
|
||||
advisory = &x
|
||||
}
|
||||
if e.Type == "ApprovalGranted" {
|
||||
var p struct {
|
||||
SubjectRef string `json:"subject_ref"`
|
||||
}
|
||||
_ = json.Unmarshal(e.Payload, &p)
|
||||
if p.SubjectRef == advisoryID {
|
||||
approved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return b
|
||||
if advisory == nil {
|
||||
return nil, fmt.Errorf("advisory %q not found", advisoryID)
|
||||
}
|
||||
if !approved {
|
||||
return nil, fmt.Errorf("advisory %q is not approved", advisoryID)
|
||||
}
|
||||
var p struct {
|
||||
Items []StandupItem `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(advisory.Payload, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []domain.Event
|
||||
for _, item := range p.Items {
|
||||
if item.TaskID == "" || item.Title == "" {
|
||||
continue
|
||||
}
|
||||
t, ok := s.Task(item.TaskID)
|
||||
if !ok || t.Title == item.Title {
|
||||
continue
|
||||
}
|
||||
b, _ := json.Marshal(map[string]any{"title": item.Title, "advisory_ref": advisoryID})
|
||||
e := domain.Event{ID: domain.NewID(), TaskID: item.TaskID, Type: "TaskAmended", Version: t.Version + 1, Payload: b}
|
||||
if err := s.Append(e); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GitState reports local checkout state for the brief. Git failures are visible,
|
||||
|
||||
Reference in New Issue
Block a user