Files
codegraph/__tests__/fixtures/payroll-go/internal/domain/payroll/payslip.go
T
Colby McHenryandClaude Opus 5 bd86ad2061 test(explore): #1500 regression fixtures for budget allocation (CG-6)
Two permanent fixtures pinning the failure mode from issue #1500 — explore
spending its byte envelope on files that merely name-collide with the query.
BOTH FAIL TODAY, by design: they document the bug and become the pass gate
for CG-10 (scoring) + CG-12 (proportional allocation).

__tests__/fixtures/payroll-go/ — a synthetic Go service mirroring the
reporter's shape: generated FKIT CRUD beside a hand-written payroll use-case,
entered from an HTTP route. Half the generated tree carries ORDINARY names
detectable only by their `// Code generated ... DO NOT EDIT.` header (the
#1500 case, and end-to-end cover for CG-5); `payrollpb/*.pb.go` covers the
path-detectable channel. BuildPayslip, Upsert and Store each exist twice,
generated and hand-written. cycle.go sits above the whole-file window so it
clips; the generated files sit below it so they ship whole.

Asking "how does payroll cycle create and calculate payslips?" — naming none
of the answering symbols — the generated CRUD delivers 57.4% of the envelope
against the hand-written layer's 25.6%, all of the latter domain types.
cycle.go is allocated the single largest slice (30.6%) and delivers ZERO: the
hard ceiling drops its whole section. runPayrollCycleAll, the hand-written
BuildPayslip and the real Upsert never reach the agent.

The second fixture is this repo, "how does explore allocate its output budget
across files", where scripts/agent-eval/*.mjs take 71.8% against tools.ts's
18.5% despite scoring 4.6x lower. It reads the live index, so its assertions
are relative rather than fixed percentages.

- scripts/agent-eval/probe-allocation.mjs — per-file budget-share probe,
  driving the CG-4 diagnostic through a JSONL sidecar so it measures the
  shipping allocator. Fixture entries are hermetic (copy + re-index per run,
  verified byte-identical across runs); exits 1 while any assertion fails.
- scripts/agent-eval/allocation-fixtures.json — both fixtures declared, with
  the 2026-08-03 baselines.
- __tests__/explore-allocation-1500.test.ts — fixture-shape assertions green
  today; the allocation assertions held as `it.fails` so the suite stays green
  while the bug is open and goes RED the moment it is fixed.

Also documented and deliberately left unfixed: runPayrollCycleAll's
`s.store.Upsert` edge resolves to the GENERATED Store.Upsert, not the
hand-written one — same-name method resolution across two packages picks the
wrong receiver. It is upstream of the allocation bug, so it belongs with
CG-10's scoring work.

Refs #1500

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 23:30:17 -05:00

160 lines
3.5 KiB
Go

package payroll
import "time"
// CycleStatus is the lifecycle state of a payroll cycle.
type CycleStatus string
const (
CycleOpen CycleStatus = "open"
CycleClosed CycleStatus = "closed"
)
// ContractKind distinguishes the two pay models this service supports.
type ContractKind string
const (
ContractSalaried ContractKind = "salaried"
ContractHourly ContractKind = "hourly"
)
// LineKind separates the two halves of a payslip.
type LineKind string
const (
LineEarning LineKind = "earning"
LineDeduction LineKind = "deduction"
)
// Cycle is one payroll period.
type Cycle struct {
ID string
Start time.Time
End time.Time
Status CycleStatus
ClosedAt time.Time
ReopenReason string
}
// Line is a single earning or deduction on a payslip.
type Line struct {
Code string
Kind LineKind
AmountCents int64
}
// Payslip is what a cycle produces for one employee.
type Payslip struct {
CycleID string
EmployeeID string
Currency string
PeriodFrom time.Time
PeriodTo time.Time
Lines []Line
GrossCents int64
DeductionCents int64
NetCents int64
Underwater bool
RunAt time.Time
RunReason string
}
// Timesheet is the approved unit count backing an hourly payslip.
type Timesheet struct {
CycleID string
EmployeeID string
Approved bool
Units int
}
// Allowance is a recurring earning attached to a contract.
type Allowance struct {
Code string
AmountCents int64
Prorated bool
}
// Contract holds the pay terms for one employee.
type Contract struct {
Kind ContractKind
Currency string
RateCents int64
PeriodRateCents int64
OvertimeThresholdUnits int
OvertimeMultiplier float64
Allowances []Allowance
StartsOn time.Time
EndsOn time.Time
}
// OverlapsWindow reports whether the contract is live at any point in the window.
func (c Contract) OverlapsWindow(from, to time.Time) bool {
if !c.StartsOn.IsZero() && c.StartsOn.After(to) {
return false
}
if !c.EndsOn.IsZero() && c.EndsOn.Before(from) {
return false
}
return true
}
// PeriodUnits is the contractual unit count for a window, used when a salaried
// employee has no approved timesheet.
func (c Contract) PeriodUnits(from, to time.Time) int {
if to.Before(from) {
return 0
}
days := int(to.Sub(from).Hours()/24) + 1
return days * 8
}
// Deduction is a fixed or proportional subtraction from gross.
type Deduction struct {
Code string
FixedCents int64
RateBasisPoints int
}
// AmountFor resolves a deduction against a gross amount.
func (d Deduction) AmountFor(grossCents int64) int64 {
if d.FixedCents > 0 {
return d.FixedCents
}
return grossCents * int64(d.RateBasisPoints) / 10000
}
// TaxBand is one slice of a progressive tax schedule.
type TaxBand struct {
UpToCents int64
RateBasisPoints int
}
// Leave is an absence window.
type Leave struct {
From time.Time
To time.Time
Unpaid bool
}
// Employee is the payroll view of a person.
type Employee struct {
ID string
Contract Contract
Deductions []Deduction
TaxBands []TaxBand
Leave []Leave
}
// UnpaidLeaveCoversWindow reports whether unpaid leave swallows the whole window.
func (e Employee) UnpaidLeaveCoversWindow(from, to time.Time) bool {
for _, l := range e.Leave {
if !l.Unpaid {
continue
}
if !l.From.After(from) && !l.To.Before(to) {
return true
}
}
return false
}