first commit

This commit is contained in:
2026-08-24 13:06:29 +05:30
commit 7d12ebef3d
86 changed files with 39996 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
package seeder
import (
"fmt"
"math"
"sort"
"time"
)
// A port of src/api/attendanceSeed.js.
//
// Ported rather than snapshotted because this is the one collection whose dates
// are anchored to *now* rather than to a fixed calendar. `dataResolver.inPeriod`
// windows every collection on created_date, so "attendance last week" has to
// mean last week on the day the seeder runs. A frozen JSON snapshot would read
// as permanently empty a fortnight later.
//
// Nothing here is random. The distribution is deterministic given the date the
// seeder runs, so the same day always produces the same figures and the
// regression tests can assert against them:
//
// Marco — the control: reliable, weekend event overtime
// Marcus — attendance degrading over the last fortnight (the anomaly)
// Antoine — present throughout, overtime climbing week on week (the trend)
const windowDays = 56
type rosterEntry struct {
staffID, workerName, workerEmail string
jobPostingID, role, roleCategory string
weekdays []time.Weekday
startHour int
scheduledHours float64
}
var roster = []rosterEntry{
{
staffID: "staff_marco", workerName: "Marco Rivera", workerEmail: "marco.rivera@email.com",
jobPostingID: "job_bartender_corp", role: "Experienced Bartender – Corporate Events",
roleCategory: "Bartender",
// Wed–Sat: corporate events run late in the week.
weekdays: []time.Weekday{time.Wednesday, time.Thursday, time.Friday, time.Saturday},
startHour: 16, scheduledHours: 8,
},
{
staffID: "staff_marcus", workerName: "Marcus Williams", workerEmail: "marcus.w@email.com",
jobPostingID: "job_security", role: "Event Security Officer", roleCategory: "Security",
// Mon–Fri: a fixed rota, which is what makes the recent absences stand
// out rather than read as an irregular schedule.
weekdays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday},
startHour: 14, scheduledHours: 8,
},
{
staffID: "staff_antoine", workerName: "Chef Antoine Dubois", workerEmail: "antoine.dubois@email.com",
jobPostingID: "job_chef", role: "Executive Chef – Catering", roleCategory: "Chef",
// Tue–Sat: kitchen service.
weekdays: []time.Weekday{time.Tuesday, time.Wednesday, time.Thursday, time.Friday, time.Saturday},
startHour: 12, scheduledHours: 9,
},
}
type behaviour struct {
status string
minutesLate int
overtime float64
notes string
}
// behaviourFor mirrors the BEHAVIOUR map. `i` counts back from the most recent
// shift, so "the last fortnight" stays a range of small indices as the window
// rolls forward.
func behaviourFor(staffID string, i int, weekday time.Weekday) behaviour {
switch staffID {
case "staff_marco":
b := behaviour{status: "present"}
if i == 14 {
b.status, b.minutesLate = "late", 9
}
// Friday and Saturday events overrun; midweek ones do not.
if weekday == time.Friday || weekday == time.Saturday {
b.overtime = 1
}
return b
case "staff_marcus":
switch i {
case 2, 7:
return behaviour{status: "absent", notes: "Called in sick"}
case 4:
return behaviour{status: "no_show", notes: "No contact"}
case 1:
return behaviour{status: "late", minutesLate: 24}
case 5:
return behaviour{status: "late", minutesLate: 16}
case 9:
return behaviour{status: "late", minutesLate: 12}
case 26:
return behaviour{status: "late", minutesLate: 7}
}
return behaviour{status: "present"}
case "staff_antoine":
weekIndex := i / 5
busy := weekday == time.Thursday || weekday == time.Friday || weekday == time.Saturday
b := behaviour{status: "present"}
if busy {
b.overtime = math.Max(0.5, round1(3.5-float64(weekIndex)*0.45))
}
return b
}
return behaviour{status: "present"}
}
// round1 and round2 reproduce JavaScript's Math.round, which rounds halves away
// from zero — the same rule as Go's math.Round.
func round1(n float64) float64 { return math.Round(n*10) / 10 }
func round2(n float64) float64 { return math.Round(n*100) / 100 }
// daysAgo is `n` days back at a given local hour.
//
// Local rather than UTC because a shift belongs to the day it was worked in the
// place it was worked, and periodRange windows on local day boundaries too.
func daysAgo(now time.Time, n, hour int) time.Time {
d := now.AddDate(0, 0, -n)
return time.Date(d.Year(), d.Month(), d.Day(), hour, 0, 0, 0, now.Location())
}
func containsWeekday(set []time.Weekday, w time.Weekday) bool {
for _, x := range set {
if x == w {
return true
}
}
return false
}
// shiftOffsets is every day offset in the window on which this worker is rostered.
func shiftOffsets(now time.Time, weekdays []time.Weekday) []int {
var offsets []int
for offset := 0; offset <= windowDays; offset++ {
if containsWeekday(weekdays, daysAgo(now, offset, 0).Weekday()) {
offsets = append(offsets, offset)
}
}
return offsets
}
const isoMillis = "2006-01-02T15:04:05.000Z"
func iso(t time.Time) string { return t.UTC().Format(isoMillis) }
// BuildShifts generates the shift records for a given instant, most recent first.
func BuildShifts(now time.Time) []map[string]any {
records := make([]map[string]any, 0, 128)
for _, w := range roster {
offsets := shiftOffsets(now, w.weekdays)
for i, offset := range offsets {
scheduledStart := daysAgo(now, offset, w.startHour)
weekday := scheduledStart.Weekday()
scheduledEnd := scheduledStart.Add(time.Duration(w.scheduledHours * float64(time.Hour)))
b := behaviourFor(w.staffID, i, weekday)
worked := b.status != "absent" && b.status != "no_show"
var actualStart, actualEnd any
endForUpdated := scheduledEnd
if worked {
actualStart = iso(scheduledStart.Add(time.Duration(b.minutesLate) * time.Minute))
ae := scheduledEnd.Add(time.Duration(b.overtime * float64(time.Hour)))
actualEnd, endForUpdated = iso(ae), ae
}
actualHours, overtimeHours, minutesLate := 0.0, 0.0, 0
if worked {
actualHours = round2(w.scheduledHours - float64(b.minutesLate)/60 + b.overtime)
overtimeHours = round1(b.overtime)
minutesLate = b.minutesLate
}
records = append(records, map[string]any{
"id": fmt.Sprintf("shift_%s_%02d", w.staffID[len("staff_"):], len(offsets)-i),
"staff_id": w.staffID,
"worker_name": w.workerName,
"worker_email": w.workerEmail,
"job_posting_id": w.jobPostingID,
"role": w.role,
"role_category": w.roleCategory,
"shift_date": scheduledStart.Format("2006-01-02"),
"scheduled_start": iso(scheduledStart),
"scheduled_end": iso(scheduledEnd),
"scheduled_hours": w.scheduledHours,
"actual_start": actualStart,
"actual_end": actualEnd,
"actual_hours": actualHours,
"overtime_hours": overtimeHours,
"minutes_late": minutesLate,
"status": b.status,
"notes": b.notes,
// Load-bearing: dataResolver windows every collection on
// created_date, so a shift's created date IS the instant it
// was worked.
"created_date": iso(scheduledStart),
"updated_date": iso(endForUpdated),
})
}
}
// Most recent first, matching the -created_date order every other
// collection is listed in.
sort.SliceStable(records, func(a, b int) bool {
return records[a]["created_date"].(string) > records[b]["created_date"].(string)
})
return records
}