servicereport

package
v0.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package servicereport measures how the catalogue's delivery promises were kept (ADR-0515).

It is pure: orders, the holds the inventory recorded and a clock go in, positions with a verdict and per-product summaries come out. Loading those inputs — off the run loop, scoped to what the reader may see — is the caller's job, so that the one place every rule of measurement lives can be tested without a store.

Nothing here is persisted and nothing here is an engine fact. Every number is computed from what was already durable: the order's creation, the line's approval and the start of the right the inventory records (ADR-0312).

Index

Constants

This section is empty.

Variables

View Source
var PeopleCSVHeader = "person;item;name;variant;origin;price;since;until"

PeopleCSVHeader is the first line of WriteHoldingsCSV, exposed for callers that check a download without parsing it.

Functions

func WorkingDays

func WorkingDays(from, to time.Time, loc *time.Location) float64

WorkingDays answers how many working days lie between from and to: the time elapsed on Mondays to Fridays in loc, in days of 24 hours. Weekends do not count; public holidays do, until the ADR's open question is settled. Zero when to is not after from.

func WriteHoldingsCSV

func WriteHoldingsCSV(w io.Writer, people []PersonRow, loc *time.Location) error

WriteHoldingsCSV writes one line per right a person holds, by name.

func WritePositionsCSV

func WritePositionsCSV(w io.Writer, ps []Position, nameOf func(principal string) string,
	items map[string]string, loc *time.Location) error

WritePositionsCSV writes one line per position, with the people it concerns by name. Only a report permitted to show names calls it (ADR-0515 §3).

func WriteProductsCSV

func WriteProductsCSV(w io.Writer, rows []ProductRow) error

WriteProductsCSV writes the product rows, one per line, with a header.

Types

type Held

type Held struct {
	Principal string
	ItemID    string
	VariantID string
	OrderID   string
	// Origin is how the right came to be held: ordered, adopted, granted.
	Origin string
	// Since and Until are unix nanoseconds; Until is zero for a right with no end.
	Since int64
	Until int64
}

Held is one right a person holds now, as the inventory records it.

type Hold

type Hold struct {
	Principal string
	ItemID    string
	VariantID string
	OrderID   string
	// Since is when the right began, in unix nanoseconds.
	Since int64
}

Hold is the part of an inventory record measurement needs: who holds what, which order produced it and when it began. It is filled from current holdings and, for a position given back since, from the holding's history.

type HoldingRow

type HoldingRow struct {
	ItemID    string `json:"itemId"`
	Name      string `json:"name"`
	VariantID string `json:"variantId,omitempty"`
	Origin    string `json:"origin,omitempty"`
	// Price is the figure the order line carried when the right was ordered, or
	// empty for a right no order of ours produced.
	Price string `json:"price,omitempty"`
	Since int64  `json:"since"`
	Until int64  `json:"until,omitempty"`
}

HoldingRow is one right in a person's row.

type PersonRow

type PersonRow struct {
	ID               string       `json:"id"`
	Name             string       `json:"name"`
	Holdings         []HoldingRow `json:"holdings"`
	Ordered          int          `json:"ordered"`
	Open             int          `json:"open"`
	Overdue          int          `json:"overdue"`
	AwaitingApproval int          `json:"awaitingApproval"`
}

PersonRow is one person in a report that may show names: what they hold now and how their positions in the period stand.

func People

func People(ids []string, nameOf func(string) string, held []Held, items map[string]string,
	priceOf func(orderID, itemID, variantID string) string, ps []Position) []PersonRow

People answers one row per person in ids, sorted by name, with the rights each holds and the counts of their positions. nameOf resolves a principal to the name shown; items names a product; priceOf answers the price an order line carried.

type Position

type Position struct {
	OrderID   string `json:"orderId"`
	ReleaseID string `json:"releaseId,omitempty"`
	ItemID    string `json:"itemId"`
	VariantID string `json:"variantId,omitempty"`
	// Recipient and Orderer are principal ids. A report that may show names resolves
	// them when it is rendered (ADR-0314); the product manager's report drops them.
	Recipient string `json:"recipient,omitempty"`
	Orderer   string `json:"orderer,omitempty"`
	Status    string `json:"status"`
	Price     string `json:"price,omitempty"`
	// ServiceLevel and DeliveryDays are the promise the line was ordered under.
	ServiceLevel string `json:"serviceLevel,omitempty"`
	DeliveryDays int    `json:"deliveryDays,omitempty"`
	// OrderedAt, ClockFrom and ProvisionedAt are unix nanoseconds; ProvisionedAt is
	// zero for a position not (measurably) provisioned.
	OrderedAt     int64 `json:"orderedAt"`
	ClockFrom     int64 `json:"clockFrom"`
	ProvisionedAt int64 `json:"provisionedAt,omitempty"`
	// LeadDays is the working days from ClockFrom to ProvisionedAt, or, for a position
	// still open, to now.
	LeadDays float64 `json:"leadDays"`
	// ApprovalWaitDays is the working days the approval took, reported apart from the
	// lead time it is not part of.
	ApprovalWaitDays float64 `json:"approvalWaitDays,omitempty"`
	Verdict          Verdict `json:"verdict"`
}

Position is one measured order line.

func Measure

func Measure(orders []order.Order, holds []Hold, now time.Time, loc *time.Location) []Position

Measure answers one position per order line that delivered, or was meant to deliver, something. A line that was rejected, cancelled, abandoned or skipped is left out: nothing was delivered and nothing needed to be.

type ProductRow

type ProductRow struct {
	ItemID       string `json:"itemId"`
	Name         string `json:"name"`
	ServiceLevel string `json:"serviceLevel,omitempty"`
	DeliveryDays int    `json:"deliveryDays,omitempty"`
	Ordered      int    `json:"ordered"`
	// Delivered counts the positions provisioned, measurably or not.
	Delivered        int `json:"delivered"`
	Met              int `json:"met"`
	Missed           int `json:"missed"`
	Overdue          int `json:"overdue"`
	Open             int `json:"open"`
	AwaitingApproval int `json:"awaitingApproval"`
	Failed           int `json:"failed"`
	// Held is how many rights to this product are held now, whoever ordered them.
	Held int `json:"held"`
	// MetShare is met over met, missed and overdue: the positions a promise was due
	// for. Nil where none was.
	MetShare *float64 `json:"metShare,omitempty"`
	// LeadMedianDays and LeadP90Days are over the measurably delivered positions.
	LeadMedianDays *float64 `json:"leadMedianDays,omitempty"`
	LeadP90Days    *float64 `json:"leadP90Days,omitempty"`
	// ApprovalWaitMedianDays is over the positions that waited for an approval.
	ApprovalWaitMedianDays *float64 `json:"approvalWaitMedianDays,omitempty"`
}

ProductRow is one product's line in a report: what was ordered, how it was delivered against its promise and how many hold it now. It carries no person.

func Summarise

func Summarise(ps []Position, names map[string]string, held map[string]int) []ProductRow

Summarise answers one row per product that was ordered or is held, sorted by product id. names maps a product id to the name to show; held counts current rights per product.

type Service

type Service struct {
	// Now answers the server's clock in unix nanoseconds.
	Now func() int64
	// Location is the zone dates and working days are read in; nil is the
	// process's local zone.
	Location *time.Location
	// Catalogs and ReleasesOf read the catalogue store; both are sidecar reads a
	// caller may make off the run loop (ADR-0265).
	Catalogs   func() ([]catalog.Catalog, error)
	ReleasesOf func(catalogID string) ([]catalog.Release, error)
	// MayEdit reports whether a principal maintains a catalogue.
	MayEdit func(catalog.Catalog, *httpapi.Principal) bool
	// Orders reads every order, off the run loop.
	Orders func() ([]order.Order, error)
	// Inventory and Directory are the engine's side of the contract.
	Inventory shopcontract.Inventory
	Directory shopcontract.Directory
	// NameOf answers a resolver from a principal id to the name a person reads.
	NameOf func() func(string) string
	// Audit writes the audit line an answer that names people owes, before it is
	// sent.
	Audit func(r *http.Request, attrs ...slog.Attr)
}

Service answers the four report routes: the product manager's delivery report and the reports that name people (ADR-0515). It is a service of its own in the sense of ADR-0147 and, more to the point of tshopper's phase A, it reaches the engine only through the contract in api/shopcontract: the inventory as a read view, the directory as a list of accounts, and the audit as one call. Everything else it reads — catalogues, releases, orders — is the shop's own.

Every dependency is a field set once by the server, so the handlers carry no knowledge of where the stores live.

func (*Service) HandleDeliveryReport

func (s *Service) HandleDeliveryReport(w http.ResponseWriter, r *http.Request)

HandleDeliveryReport answers the delivery report over the catalogues the caller maintains, or the one ?catalog= names. ?format=csv answers it as CSV.

func (*Service) HandleMyReports

func (s *Service) HandleMyReports(w http.ResponseWriter, r *http.Request)

HandleMyReports answers which of the two reports the caller may open, so the shop draws a tab for each and none for the rest.

func (*Service) HandleOrganisationReport

func (s *Service) HandleOrganisationReport(w http.ResponseWriter, r *http.Request)

HandleOrganisationReport answers a catalogue's audience's rights and positions to one of its customer responsibles.

func (*Service) HandleTeamReport

func (s *Service) HandleTeamReport(w http.ResponseWriter, r *http.Request)

HandleTeamReport answers the caller's direct reports' rights and positions.

type Totals

type Totals struct {
	Ordered          int      `json:"ordered"`
	Delivered        int      `json:"delivered"`
	Met              int      `json:"met"`
	Missed           int      `json:"missed"`
	Overdue          int      `json:"overdue"`
	Open             int      `json:"open"`
	AwaitingApproval int      `json:"awaitingApproval"`
	Failed           int      `json:"failed"`
	Held             int      `json:"held"`
	MetShare         *float64 `json:"metShare,omitempty"`
}

Totals are the rows added up. Lead times are not: a median of medians is not a median, and the rows say them per product.

func Total

func Total(rows []ProductRow) Totals

Total adds the rows up.

type Verdict

type Verdict string

Verdict is how one position stands against its promise.

const (
	// Met: provisioned within the promised working days.
	Met Verdict = "met"
	// Missed: provisioned, later than promised.
	Missed Verdict = "missed"
	// Overdue: still open, and the promised time has passed.
	Overdue Verdict = "overdue"
	// Open: still open, within the promised time.
	Open Verdict = "open"
	// AwaitingApproval: an approval is still owed. The clock has not started,
	// because the customer's own organisation decides it.
	AwaitingApproval Verdict = "awaitingApproval"
	// Failed: provisioning failed and nothing was delivered.
	Failed Verdict = "failed"
	// NoPromise: the line was ordered from a release that promised nothing.
	NoPromise Verdict = "noPromise"
	// Unmeasured: delivered, but the inventory holds no right this order produced, so
	// when it began cannot be said. Reported rather than guessed.
	Unmeasured Verdict = "unmeasured"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL