Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/recruiting/placement_math.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/recruiting/placement_math.go |
| Original SHA-256 | 1ee48d6887c7f0b91a1582c03e4ea62590b6c57dc6d63fd1d7ee69736a0d0d0e |
| Git blob SHA | 73e2a03fa5d7da447d153eb6ba688157c66394c7 |
| Reading-copy SHA-256 | 1ee48d6887c7f0b91a1582c03e4ea62590b6c57dc6d63fd1d7ee69736a0d0d0e |
Recovered source transcript
Download complete source text ↓232 lines · 10.2 KB
package recruiting
import (
"time"
"github.com/shopspring/decimal"
)
// Money arithmetic for offers and placements.
//
// Every figure here is exact decimal on values that arrived from Postgres numeric as text. None of it
// touches float64. A bill rate is multiplied by hours and by a wrap rate downstream, and binary floating
// point cannot represent 0.1, so a margin computed through a float would not reconcile with an invoice.
//
// What this file deliberately does NOT compute is projected revenue. Annualising an hourly rate requires
// assuming a full time, full year assignment that nobody has agreed to, and a number like that is
// indistinguishable on a screen from one that was actually contracted. Only quantities that follow from
// recorded facts appear here.
var (
offerStatuses = map[string]bool{
"extended": true, "accepted": true, "declined": true, "rescinded": true, "expired": true,
}
placementStatuses = map[string]bool{
"pending_start": true, "active": true, "completed": true, "terminated": true, "fell_off": true,
}
interviewKinds = map[string]bool{
"phone_screen": true, "technical": true, "client": true, "panel": true, "final": true,
}
interviewOutcomes = map[string]bool{
"scheduled": true, "passed": true, "failed": true, "no_show": true, "cancelled": true, "rescheduled": true,
}
)
// offerTransitions mirrors rm_offer_transition in migration 0055. The database is the authority; this
// exists so the interface can offer only the moves that will be accepted.
var offerTransitions = map[string][]string{
"extended": {"accepted", "declined", "rescinded", "expired"},
"accepted": {"rescinded"},
// declined and expired are terminal. Re offering is a NEW offer so the history keeps both.
"declined": {},
"rescinded": {},
"expired": {},
}
func offerCanMove(from, to string) bool {
for _, t := range offerTransitions[from] {
if t == to {
return true
}
}
return false
}
// placementEnded reports whether a status closes an assignment. An ended placement must carry an end
// date, which the database enforces, and stops being counted as live.
func placementEnded(status string) bool {
return status == "completed" || status == "terminated" || status == "fell_off"
}
// Economics is the money on one placement or offer, all exact, and it carries TWO different numbers that were
// previously reported as one.
//
// THE SPREAD IS NOT THE MARGIN. The spread is bill minus pay, the gross difference between the two rates
// before the employer has paid for anything the employment itself costs: payroll taxes, workers compensation,
// unemployment insurance, health cover, paid leave, retirement, general and administrative, overhead. The
// margin is what is left after those. On a 110 pay and a 170 bill at a 35 percent wrap the spread is 60.00 an
// hour and the margin is 21.50, and this type used to publish the first under the name of the second.
//
// MIGRATION 0086 WROTE THE RULE DOWN AND NOTHING IMPLEMENTED IT: a NULL burden means the line is UNBURDENED
// and NO MARGIN MAY BE REPORTED FOR IT AT ALL. That rule is enforced here, in the one pure function every
// money bearing response goes through, rather than in each screen that renders a number.
type Economics struct {
// SpreadPerHour is bill minus pay. Present for all rate priced work, because it needs nothing but the two
// rates, and it is ALWAYS the honest name for that subtraction.
SpreadPerHour *decimal.Decimal `json:"spread_per_hour,omitempty"`
// SpreadPct is the spread as a share of bill, to four places. Absent when the bill rate is zero, because a
// share of nothing is undefined rather than zero.
SpreadPct *decimal.Decimal `json:"spread_pct,omitempty"`
// BurdenPct is the wrap actually applied, as a fraction, so 0.35 is a 35 percent wrap. Present only when
// one was known. It is reported back rather than left implicit because a margin that changed overnight
// because somebody edited an organization default is otherwise impossible to explain.
BurdenPct *decimal.Decimal `json:"burden_pct,omitempty"`
// BurdenSource says WHERE that wrap came from, either the record itself or the organization default. Two
// placements showing different margins on identical rates is correct when one carries its own wrap, and
// baffling without this field.
BurdenSource string `json:"burden_source,omitempty"`
// CostPerHour is pay plus the burden on it, which is what the hour actually costs to deliver.
CostPerHour *decimal.Decimal `json:"cost_per_hour,omitempty"`
// MarginPerHour and MarginPct are bill minus cost. ABSENT unless a burden was known, per clause A.
MarginPerHour *decimal.Decimal `json:"margin_per_hour,omitempty"`
MarginPct *decimal.Decimal `json:"margin_pct,omitempty"`
// Unburdened states WHY the margin is missing: no wrap is set for this line or for the organization. It is
// distinct from a direct hire, which has no hourly margin because it is not priced by the hour. Without
// this the two cases render identically and a recruiter concludes the number is broken rather than
// unavailable.
Unburdened bool `json:"unburdened,omitempty"`
// Fee is the direct hire fee: an explicit amount when one was agreed, otherwise salary times the
// percentage. Present only for direct hire.
Fee *decimal.Decimal `json:"fee,omitempty"`
}
// BurdenFromRecord and BurdenFromOrg name the two places a wrap can come from. They are constants rather than
// free text so a screen can branch on them and a typo cannot invent a third source.
const (
BurdenFromRecord = "record"
BurdenFromOrg = "organization"
)
// economicsOf computes the money from whichever pricing shape applies.
//
// The two shapes are never blended. A contract placement earns an hourly spread and a direct hire earns a one
// time fee, and averaging or summing them into one revenue number produces a figure that describes neither.
// Migration 0048 already requires the right columns per employment type, so this reads whichever pair is
// populated rather than guessing.
//
// recordBurden is the wrap stored on this offer or placement, orgBurden the organization default. The record
// wins when both are present, because it was set to describe this specific line and the default was set to
// describe the ordinary case.
func economicsOf(
employmentType string,
bill, pay, salary, feePct, feeAmount *decimal.Decimal,
recordBurden, orgBurden *decimal.Decimal,
) Economics {
var e Economics
if employmentType == "direct_hire" {
switch {
case feeAmount != nil:
f := *feeAmount
e.Fee = &f
case salary != nil && feePct != nil:
// fee_pct is stored as a fraction (0 to 1), so 0.20 of a 150000 salary is 30000.
f := salary.Mul(*feePct).Round(2)
e.Fee = &f
}
// NOT marked unburdened. A direct hire has no hourly margin because it is not priced by the hour, which
// is a different fact from a contract line whose wrap nobody has entered, and conflating them would put
// a "no wrap set" prompt on a record where setting one would change nothing.
return e
}
if bill == nil || pay == nil {
return e
}
s := bill.Sub(*pay)
e.SpreadPerHour = &s
if !bill.IsZero() {
sp := s.Div(*bill).Mul(decimal.NewFromInt(100)).Round(4)
e.SpreadPct = &sp
}
burden, source := recordBurden, BurdenFromRecord
if burden == nil {
burden, source = orgBurden, BurdenFromOrg
}
if burden == nil {
// CLAUSE A. No wrap anywhere, so the cost of the hour is unknown, so the margin is unknown. The spread
// above still stands, because it is arithmetic on two known rates and needs nothing else.
e.Unburdened = true
return e
}
b := *burden
e.BurdenPct = &b
e.BurdenSource = source
cost := pay.Mul(decimal.NewFromInt(1).Add(b)).Round(4)
e.CostPerHour = &cost
m := bill.Sub(cost)
// NOT floored at zero. A placement sold below its loaded cost is a real and important fact, and clamping
// it would hide the one number an owner most needs to see.
e.MarginPerHour = &m
if !bill.IsZero() {
mp := m.Div(*bill).Mul(decimal.NewFromInt(100)).Round(4)
e.MarginPct = &mp
}
return e
}
// Guarantee is the clawback position on a placement.
//
// A staffing guarantee means that if the person leaves within N days of starting, the fee is returned or
// the seat is refilled free. It is the single largest correction to reported revenue in this industry, so
// it is computed from recorded dates rather than tracked by hand.
type Guarantee struct {
Days int `json:"days"`
// EndsOn is the last day covered. Inclusive: a 90 day guarantee on a placement starting the 1st
// covers through day 90, so the boundary case is a real day and not an off by one.
EndsOn string `json:"ends_on,omitempty"`
// DaysRemaining is negative once the guarantee has run out. Absent for an ended placement, where the
// question is no longer about time remaining but about whether it fell inside.
DaysRemaining *int `json:"days_remaining,omitempty"`
// Breached is true when the assignment ended inside the guarantee window in a way that triggers it.
// A completed assignment never breaches, however short: completing early is the client's call.
Breached bool `json:"breached"`
}
// guaranteeOf computes the clawback position.
//
// Only fell_off and terminated breach. completed does not, because an assignment that ran its course is
// not a failure to deliver, and counting it as one would understate a recruiter's performance for work
// the client ended on its own terms.
func guaranteeOf(days int, start time.Time, end *time.Time, status string, today time.Time) Guarantee {
g := Guarantee{Days: days}
if days <= 0 {
return g
}
// Inclusive of the start day: a 90 day guarantee starting on the 1st ends on day 90, which is
// start + 89 days, not start + 90.
endsOn := start.AddDate(0, 0, days-1)
g.EndsOn = endsOn.Format(dateLayout)
if end != nil {
if (status == "fell_off" || status == "terminated") && !end.After(endsOn) {
g.Breached = true
}
return g
}
rem := int(endsOn.Sub(today).Hours() / 24)
g.DaysRemaining = &rem
return g
}
// interviewRoundOK bounds the round number against the CHECK in migration 0048.
func interviewRoundOK(r int) bool { return r >= 1 && r <= 20 }
// interviewConcluded reports whether an outcome requires a completed_at, mirroring interviews_completed_chk.
func interviewConcluded(outcome string) bool {
return outcome == "passed" || outcome == "failed" || outcome == "no_show"
}