skill issue
This commit is contained in:
@@ -241,6 +241,14 @@ const RECENT_WEEKS = 2;
|
||||
* Each finding carries the figures it was derived from, so a caller can state
|
||||
* *why* rather than asserting that something is wrong.
|
||||
*/
|
||||
/** The middle value, for asking what a typical week looks like. */
|
||||
function median(values) {
|
||||
const sorted = [...values].filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
||||
if (!sorted.length) return 0;
|
||||
const mid = Math.floor(sorted.length / 2);
|
||||
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
||||
}
|
||||
|
||||
export function attendanceAnomalies(shifts = [], { now = new Date() } = {}) {
|
||||
const findings = [];
|
||||
const trend = weeklyTrend(shifts, { weeks: 12, now });
|
||||
@@ -311,7 +319,34 @@ export function attendanceAnomalies(shifts = [], { now = new Date() } = {}) {
|
||||
* against whole ones reports a fall in overtime every Monday morning.
|
||||
*/
|
||||
const thisWeekStart = startOfWeek(now).getTime();
|
||||
const complete = trend.filter((w) => new Date(w.weekStart).getTime() < thisWeekStart);
|
||||
const withinWindow = trend.filter((w) => new Date(w.weekStart).getTime() < thisWeekStart);
|
||||
|
||||
/* And the same again at the other end.
|
||||
*
|
||||
* The rule above drops the week in progress because a part-week is not
|
||||
* comparable to a whole one. The oldest week has exactly that problem and was
|
||||
* kept: records begin somewhere, and wherever that is, it lands mid-week — so
|
||||
* the first bucket holds whatever part of a week the data reaches back to.
|
||||
*
|
||||
* Per-shift normalises for a week's size but not for its sample: two shifts
|
||||
* carrying one long night read as a very high rate, and that inflated figure
|
||||
* became the baseline every later week was measured against. The effect was
|
||||
* a blind spot with a calendar in it — the seeded overtime climb was reported
|
||||
* Sunday through Thursday and vanished on Friday and Saturday, as the oldest
|
||||
* week thinned from 8 scheduled shifts to 5 to 2.
|
||||
*
|
||||
* A week rostered well below the usual is dropped from the front rather than
|
||||
* trusted. Only from the front: a genuine collapse in scheduled work is a
|
||||
* finding, not noise, and this must not swallow one in the middle. */
|
||||
const scheduledPerWeek = withinWindow.map((w) => w.scheduled);
|
||||
const typicalWeek = median(scheduledPerWeek);
|
||||
let firstFull = 0;
|
||||
while (
|
||||
firstFull < withinWindow.length - 1
|
||||
&& typicalWeek > 0
|
||||
&& withinWindow[firstFull].scheduled < typicalWeek * 0.6
|
||||
) firstFull += 1;
|
||||
const complete = withinWindow.slice(firstFull);
|
||||
|
||||
if (complete.length >= 4) {
|
||||
const perShift = complete.map((w) => (w.scheduled ? w.overtimeHours / w.scheduled : 0));
|
||||
|
||||
Reference in New Issue
Block a user