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 ¶
- Variables
- func WorkingDays(from, to time.Time, loc *time.Location) float64
- func WriteHoldingsCSV(w io.Writer, people []PersonRow, loc *time.Location) error
- func WritePositionsCSV(w io.Writer, ps []Position, nameOf func(principal string) string, ...) error
- func WriteProductsCSV(w io.Writer, rows []ProductRow) error
- type Held
- type Hold
- type HoldingRow
- type PersonRow
- type Position
- type ProductRow
- type Service
- func (s *Service) HandleDeliveryReport(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleMyReports(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleOrganisationReport(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleTeamReport(w http.ResponseWriter, r *http.Request)
- type Totals
- type Verdict
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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.
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"`
// 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.
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"`
}
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.
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" )