refactor(ts-migration): type src/api and the first of src/lib for noImplicitAny
Phase 12 step 1, in progress. The flag is not enabled yet — these are the
annotations it will require, landed first so the switch itself is a
one-line commit with a number that is already zero.
`noImplicitAny` projects 3496 errors across 242 files. This clears 179 of
them in 7 files: all of `src/api` (107 -> 0) and `workforce`,
`hiringRecords` in `src/lib`.
The leverage is real and worth recording, because it shapes the rest of
the step. Eight annotations on `aiEngine`'s prompt readers cleared 27
errors: where a value is `any`, every callback beneath it — `.map((c) =>
…)`, `.filter((r) => …)` — has no contextual type and errors on its own.
Typing the source fixes the callbacks for free, so this works bottom-up,
sources first.
Types are taken from what already exists wherever possible. The generated
entity types fit `workforce` and `hiringRecords` without a single
cascade: `JobPosting`, `JobApplication`, `Staff`, `AIInterview`,
`WorkerProfile`, `Assignment`, `Course`. `PreferencesUpdateResult` in
`src/types/user.ts` already described `updatePreferences`'s return.
`buildInsights` takes the other builders' outputs, so its parameters are
`ReturnType<typeof byDepartment>` and friends rather than a restatement
that could drift.
Two things are recorded rather than fixed:
`buildHires` probes four fields that are not `staff` columns —
`timeToHire`, `score`, `company` and `department` are absent from
`information_schema` and from the generated `Staff`. They are read as
fallbacks, so at run time they are always `undefined` and the other
branch always wins. `HireSourceRow` writes them down as optional so the
dead fallbacks are visible; removing the reads would be a behaviour
change. `Hire` likewise widens `profile_tier`, because the builder's
default `'skilled'` is lower-case where the column's check constraint
spells it `'Skilled'`.
`aiEngine`'s talent pool stays `any[]`. It is `JSON.parse` output from a
block embedded in a prompt, carrying computed fields like `match_score`
that no entity declares — typing it `WorkerProfile[]` would assert a
shape nothing validates.
One mistake worth keeping. I replaced an inline lookup with a hoisted
`const URGENCY = {…}`, and per-file esbuild said the output was
unchanged: with `--minify-syntax` it inlines a single-use const straight
back. The production bundle disagreed — `43e7f268` against `74d17e2d`.
Reverted to a type assertion, which erases. Hoisting reads as a tidy-up
and is a real change to the emitted code; the two checks disagreeing is
exactly why both are run.
typecheck 0 under the committed config; 3317 under the probe, from 3496
lint exit 0
npm test 1684/1691, the same 7 failures
build exit 0, bundle back to 74d17e2d…
emitted JS 7/7 identical
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
@@ -39,15 +39,15 @@ const WINDOW_DAYS = 56;
|
||||
* 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.
|
||||
*/
|
||||
function daysAgo(n, hour = 9, minute = 0, anchor = new Date()) {
|
||||
function daysAgo(n: number, hour = 9, minute = 0, anchor = new Date()) {
|
||||
const d = new Date(anchor.getTime());
|
||||
d.setDate(d.getDate() - n);
|
||||
d.setHours(hour, minute, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
const round1 = (n) => Math.round(n * 10) / 10;
|
||||
const round2 = (n) => Math.round(n * 100) / 100;
|
||||
const round1 = (n: number) => Math.round(n * 10) / 10;
|
||||
const round2 = (n: number) => Math.round(n * 100) / 100;
|
||||
const HOUR = 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
@@ -105,10 +105,21 @@ const ROSTER = [
|
||||
* Returning a plain record keeps every rule visible in one place instead of
|
||||
* spread across the generator.
|
||||
*/
|
||||
const BEHAVIOUR = {
|
||||
/**
|
||||
* One worker's shift outcome, as the rules below return it.
|
||||
*
|
||||
* `i` is the index of the shift counting back from the anchor, `weekday` the
|
||||
* day it falls on. Rules that ignore the weekday take one argument, which is
|
||||
* why the second is optional here.
|
||||
*/
|
||||
type ShiftBehaviour = (i: number, weekday?: number) => {
|
||||
status: string; minutesLate: number; overtime: number; notes: string;
|
||||
};
|
||||
|
||||
const BEHAVIOUR: Record<string, ShiftBehaviour> = {
|
||||
/* Reliable. One late arrival every couple of months, and overtime only on
|
||||
the nights events actually overrun. */
|
||||
staff_marco: (i, weekday) => ({
|
||||
staff_marco: (i: number, weekday?: number) => ({
|
||||
status: i === 14 ? 'late' : 'present',
|
||||
minutesLate: i === 14 ? 9 : 0,
|
||||
/* Friday and Saturday events overrun; midweek ones do not. */
|
||||
@@ -124,7 +135,7 @@ const BEHAVIOUR = {
|
||||
* the six weeks before it — a change big enough to be worth surfacing and
|
||||
* specific enough to act on.
|
||||
*/
|
||||
staff_marcus: (i) => {
|
||||
staff_marcus: (i: number) => {
|
||||
if (i === 2 || i === 7) {
|
||||
return { status: 'absent', minutesLate: 0, overtime: 0, notes: 'Called in sick' };
|
||||
}
|
||||
@@ -145,7 +156,7 @@ const BEHAVIOUR = {
|
||||
* covers, on the three busiest shifts of each week. A steady climb rather
|
||||
* than a spike, which is exactly the shape that hides in a table of totals.
|
||||
*/
|
||||
staff_antoine: (i, weekday) => {
|
||||
staff_antoine: (i: number, weekday?: number) => {
|
||||
const weekIndex = Math.floor(i / 5);
|
||||
const busy = weekday === 4 || weekday === 5 || weekday === 6;
|
||||
const overtime = busy ? Math.max(0.5, round1(3.5 - weekIndex * 0.45)) : 0;
|
||||
@@ -154,8 +165,8 @@ const BEHAVIOUR = {
|
||||
};
|
||||
|
||||
/** Every shift date for one worker, most recent first. */
|
||||
function shiftOffsets(weekdays, anchor) {
|
||||
const offsets = [];
|
||||
function shiftOffsets(weekdays: number[], anchor: Date) {
|
||||
const offsets: number[] = [];
|
||||
for (let offset = 0; offset <= WINDOW_DAYS; offset += 1) {
|
||||
const day = daysAgo(offset, 9, 0, anchor).getDay();
|
||||
if (weekdays.includes(day)) offsets.push(offset);
|
||||
@@ -163,16 +174,16 @@ function shiftOffsets(weekdays, anchor) {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const localDate = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
const localDate = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
|
||||
/**
|
||||
* @param {Date} [anchor] the day to count back from. Defaults to now, which is
|
||||
* the point of this collection; a caller passes one only to hold the window
|
||||
* still — see buildShiftsAt.
|
||||
*/
|
||||
function buildShifts(anchor = new Date()) {
|
||||
const records = [];
|
||||
function buildShifts(anchor: Date = new Date()) {
|
||||
const records: any[] = [];
|
||||
|
||||
for (const worker of ROSTER) {
|
||||
const offsets = shiftOffsets(worker.weekdays, anchor);
|
||||
@@ -251,6 +262,6 @@ export const SHIFT_RECORDS = buildShifts();
|
||||
* shifts built around one day and bucketed around another describe two
|
||||
* different windows.
|
||||
*/
|
||||
export function buildShiftsAt(anchor) {
|
||||
export function buildShiftsAt(anchor: Date) {
|
||||
return buildShifts(anchor);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user