Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/recruiting/goals.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/recruiting/goals.go |
| Original SHA-256 | e491e735fdfe3f4aaa4b83538f3ab02beedf822e91d21c3eb92a7aa6a8d38631 |
| Git blob SHA | 6c3218970357c57fd5e7d6b4656193fd697ac18b |
| Reading-copy SHA-256 | e491e735fdfe3f4aaa4b83538f3ab02beedf822e91d21c3eb92a7aa6a8d38631 |
Recovered source transcript
Download complete source text ↓311 lines · 11.9 KB
package recruiting
import (
"context"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/shopspring/decimal"
)
// PHASE 4: GOALS AND THE FORECAST.
//
// Phase 1 answers "better or worse than last month". It cannot answer "is this enough", which is the
// question the business actually asks, because four placements is a good month or a bad one entirely
// depending on what was committed to. Goals record what was committed to. Attainment is then the same
// figure the manager already reads, divided by it.
//
// THE FORECAST IS WEIGHTED BY THE FIRM'S OWN HISTORY AND BY NOTHING ELSE. It would be easy and wrong to
// ship stage weights as constants, because a plausible looking number that came from nowhere is exactly
// what this product refuses everywhere else. Every weight here is measured from the organization's own
// applications, and where there is not enough history to measure one, the forecast says so instead of
// guessing.
const (
// forecastMinSample is how many applications must have reached a stage before that stage's conversion
// rate is treated as knowledge rather than noise.
//
// Twenty is not a statistical ceremony, it is the point below which one lucky placement moves the rate
// by five percentage points and a manager would be steering on an accident.
forecastMinSample = 20
// goalListLimit bounds a goals read. Same reason as everywhere else in this module.
goalListLimit = 500
)
// openStages are the stages an application can sit in and still become a placement. It is the existing
// ladder minus its last rung, derived rather than retyped, so a change to the funnel cannot leave the
// forecast weighting a stage the pipeline no longer has. stageOrd is the one already defined in
// pipeline_stage.go and mirrored from migration 0048.
var openStages = ladder[:len(ladder)-1]
// Goal is one commitment.
type Goal struct {
ID string `json:"id"`
Scope string `json:"scope"`
SubjectID string `json:"subject_id,omitempty"`
SubjectName string `json:"subject_name,omitempty"`
Metric string `json:"metric"`
PeriodStart string `json:"period_start"`
PeriodDays int `json:"period_days"`
Target decimal.Decimal `json:"target"`
Note string `json:"note,omitempty"`
// Actual and Attainment are filled by the attainment read and left empty by a plain list, so a caller
// can tell the difference between "not measured" and "measured as zero".
Actual *decimal.Decimal `json:"actual,omitempty"`
Attainment *decimal.Decimal `json:"attainment_pct,omitempty"`
OnTrack string `json:"on_track,omitempty"`
}
// StageConversion is what the firm's own history says about one stage.
type StageConversion struct {
Stage string `json:"stage"`
// Reached is how many applications ever got this far, Placed how many of those ended placed.
Reached int `json:"reached"`
Placed int `json:"placed"`
Rate decimal.Decimal `json:"rate"`
// Measured is false when the sample was too small to trust. The rate is then zero and MUST NOT be
// rendered as a forecast of nothing; the interface says the history is too thin instead.
Measured bool `json:"measured"`
}
// Forecast is the weighted pipeline.
type Forecast struct {
Scope string `json:"scope"`
ScopeLabel string `json:"scope_label"`
Open int `json:"open_applications"`
Expected decimal.Decimal `json:"expected_placements"`
Conversions []StageConversion `json:"conversions"`
// Unmeasured names the stages whose weight could not be established. Applications sitting in those
// stages are counted in Open and contribute NOTHING to Expected, so the number is a floor rather than
// a guess, and saying which stages were skipped is what makes that honest rather than quiet.
Unmeasured []string `json:"unmeasured_stages,omitempty"`
SampleFloor int `json:"sample_floor"`
}
// ListGoals returns the goals for one scope, newest period first.
func (s *Service) ListGoals(ctx context.Context, teamID, owner string) ([]Goal, error) {
if s.db == nil {
return nil, ErrNoDatabase
}
out := []Goal{}
err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
ids, _, _, err := s.resolveScope(ctx, tx, teamID, owner)
if err != nil {
return err
}
// A scoped read shows the goals that BIND this scope: the organization's own, plus any set on the
// people in it. A manager filtering to their desk should still see the company target they are
// contributing to, because a desk number without the company number above it is half a picture.
var filter any
if ids != nil {
filter = ids
}
rows, e := tx.Query(ctx, `
SELECT g.id::text, g.scope, coalesce(g.subject_id::text,''),
coalesce(u.full_name, t.name, ''),
g.metric, g.period_start::text, g.period_days, g.target, g.note
FROM recruiting_goals g
LEFT JOIN users u ON u.id = g.subject_id
LEFT JOIN teams t ON t.id = g.subject_id
WHERE g.scope = 'organization'
OR $1::uuid[] IS NULL
OR g.subject_id = ANY($1)
ORDER BY g.period_start DESC, g.scope, g.metric
LIMIT `+strconv.Itoa(goalListLimit), filter)
if e != nil {
return translatePG(e)
}
defer rows.Close()
for rows.Next() {
var g Goal
if e := rows.Scan(&g.ID, &g.Scope, &g.SubjectID, &g.SubjectName, &g.Metric,
&g.PeriodStart, &g.PeriodDays, &g.Target, &g.Note); e != nil {
return e
}
out = append(out, g)
}
return translatePG(rows.Err())
})
if err != nil {
return nil, err
}
return out, nil
}
// SetGoal records a commitment, replacing any existing one for the same subject, metric and period.
//
// UPSERT rather than refuse, because a target being revised is the normal case: a quarter gets reforecast,
// a desk gains a person. The unique index makes the replacement exact rather than leaving two rows and a
// question about which is real.
func (s *Service) SetGoal(ctx context.Context, scope, subjectID, metric, periodStart string, periodDays int,
target decimal.Decimal, note string) (Goal, error) {
if s.db == nil {
return Goal{}, ErrNoDatabase
}
scope = strings.ToLower(strings.TrimSpace(scope))
metric = strings.ToLower(strings.TrimSpace(metric))
if scope != "organization" && scope != "team" && scope != "user" {
return Goal{}, &ValidationError{Field: "scope", Message: "is organization, team or user"}
}
if !inGoalMetrics(metric) {
return Goal{}, &ValidationError{Field: "metric", Message: "is placements, submissions, fill_rate or time_to_fill_days"}
}
if target.IsNegative() {
return Goal{}, &ValidationError{Field: "target", Message: "cannot be negative"}
}
if periodDays < 1 || periodDays > 366 {
return Goal{}, &ValidationError{Field: "period_days", Message: "is between 1 and 366"}
}
start, err := time.Parse("2006-01-02", strings.TrimSpace(periodStart))
if err != nil {
return Goal{}, &ValidationError{Field: "period_start", Message: "is a date like 2026-08-01"}
}
var subject any
if scope == "organization" {
if strings.TrimSpace(subjectID) != "" {
return Goal{}, &ValidationError{Field: "subject_id", Message: "an organization goal names nobody"}
}
} else {
id, perr := uuid.Parse(strings.TrimSpace(subjectID))
if perr != nil {
return Goal{}, &ValidationError{Field: "subject_id", Message: "is required for a team or user goal"}
}
subject = id
}
var g Goal
err = s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
return tx.QueryRow(ctx, `
INSERT INTO recruiting_goals
(organization_id, scope, subject_id, metric, period_start, period_days, target, note, created_by)
VALUES (current_setting('app.current_org_id')::uuid, $1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (organization_id, scope, coalesce(subject_id,'00000000-0000-0000-0000-000000000000'::uuid), metric, period_start)
DO UPDATE SET target = EXCLUDED.target, period_days = EXCLUDED.period_days,
note = EXCLUDED.note, updated_at = now()
RETURNING id::text, scope, coalesce(subject_id::text,''), metric,
period_start::text, period_days, target, note`,
scope, subject, metric, start, periodDays, target, strings.TrimSpace(note), actorPtr(ctx)).
Scan(&g.ID, &g.Scope, &g.SubjectID, &g.Metric, &g.PeriodStart, &g.PeriodDays, &g.Target, &g.Note)
})
if err != nil {
return Goal{}, err
}
return g, nil
}
// DeleteGoal removes a commitment.
func (s *Service) DeleteGoal(ctx context.Context, id uuid.UUID) error {
if s.db == nil {
return ErrNoDatabase
}
return s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
tag, e := tx.Exec(ctx, `DELETE FROM recruiting_goals WHERE id = $1`, id)
if e != nil {
return translatePG(e)
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
})
}
// Pipeline builds the weighted forecast.
//
// THE WEIGHTS COME FROM THE ORGANIZATION'S OWN APPLICATIONS. For each stage, how many applications ever
// reached it, and how many of those ended placed. furthest_stage_ord already records the high water mark
// per application, which is what lets this be answered without a stage event log, and the stage event log
// is empty in production so a design that needed it would report nothing at all.
//
// A stage with too little history contributes NOTHING rather than a guessed rate, and is named in
// Unmeasured. That makes Expected a FLOOR: the real number is at least this, and the parts that could not
// be measured are listed rather than quietly folded in at zero.
func (s *Service) Pipeline(ctx context.Context, teamID, owner string) (Forecast, error) {
if s.db == nil {
return Forecast{}, ErrNoDatabase
}
out := Forecast{Conversions: []StageConversion{}, SampleFloor: forecastMinSample, Expected: decimal.Zero}
err := s.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
ids, scope, label, err := s.resolveScope(ctx, tx, teamID, owner)
if err != nil {
return err
}
out.Scope, out.ScopeLabel = scope, label
var filter any
if ids != nil {
filter = ids
}
// One pass for the whole conversion table. The history is deliberately NOT scope filtered: a desk
// with nine applications has no history of its own worth measuring, and the firm's conversion from
// submitted to placed is a property of how the firm works rather than of who is holding the file.
rates := map[string]StageConversion{}
for _, st := range openStages {
ord := stageOrd[st]
var reached, placed int
if e := tx.QueryRow(ctx, `
SELECT count(*)::int,
count(*) FILTER (WHERE stage = 'placed')::int
FROM applications
WHERE coalesce(furthest_stage_ord, 1) >= $1`, ord).Scan(&reached, &placed); e != nil {
return translatePG(e)
}
c := StageConversion{Stage: st, Reached: reached, Placed: placed}
if reached >= forecastMinSample {
c.Rate = decimal.NewFromInt(int64(placed)).Div(decimal.NewFromInt(int64(reached))).Round(4)
c.Measured = true
} else {
out.Unmeasured = append(out.Unmeasured, st)
}
rates[st] = c
out.Conversions = append(out.Conversions, c)
}
// The open pipeline in this scope, by stage.
rows, e := tx.Query(ctx, `
SELECT stage, count(*)::int
FROM applications
WHERE stage NOT IN ('placed','rejected','withdrawn')
AND ($1::uuid[] IS NULL OR owner_user_id = ANY($1))
GROUP BY stage`, filter)
if e != nil {
return translatePG(e)
}
defer rows.Close()
for rows.Next() {
var st string
var n int
if e := rows.Scan(&st, &n); e != nil {
return e
}
out.Open += n
if c, ok := rates[st]; ok && c.Measured {
out.Expected = out.Expected.Add(c.Rate.Mul(decimal.NewFromInt(int64(n))))
}
}
out.Expected = out.Expected.Round(1)
return translatePG(rows.Err())
})
if err != nil {
return Forecast{}, err
}
return out, nil
}
func inGoalMetrics(m string) bool {
for _, v := range []string{"placements", "submissions", "fill_rate", "time_to_fill_days"} {
if v == m {
return true
}
}
return false
}