docs
/
AI skills

Training and readiness requirements

A training course in Business Made › HR › Training (a link, a Content Studio course, or one the AI drafts from the business's own SOPs) and a readiness requirement in HR › Readiness that says who needs it, by when, how often it renews, and where it is enforced (scheduling, clock-in, the till, a work station, payroll, role access); people assigned, completions recorded, a person's readiness checked at a gate, and overrides and waivers recorded on a yes.

Readiness is how a business says "nobody works the grill without a food handler card" and has the platform hold to it. A requirement names what a person must have, who it applies to and by when. It also says what happens at each gate while it is missing: when someone is scheduled, clocks in, uses the till, moves a ticket at a work station, is paid, or gets a role's permissions. Training holds the courses that satisfy course requirements, with a roster per course.

Both are Business Made screens: HR › Training (/hr/learning) and HR › Readiness (/hr/readiness). The API is appengine business-made/training/… (training/course.service.ts) and business-made/readiness/… (readiness/). Use those routes with call_appengine. They are the screens' own actions. The older business-made/learning/… routes and the LearningService AI methods skip the course rules (see What it can't do yet).

For a person reading this, the screen-by-screen guide is in the Business Made docs: Readiness, training and overrides and HR › Training. What staff see is Do your training.

Ask first

  • What must they have? A course, a certification, a signed policy, a document (I-9, W-4, a permit…), a form, an observed sign-off at a station, or a task.
  • Who does it apply to? Job titles, locations, departments, employment types, roles, stations. Empty means everyone.
  • By when, and how often? Days from their start date (or before their first shift), and whether it renews every N months.
  • Where is it enforced, and how hard? For each gate: nothing, a warning, allowed only with an override, or blocked. Grace days, if any.
  • The course: a link where it's taken, a Content Studio course that already exists, or a course built from the business's documents and policies.
  • Anyone to assign now who the requirement doesn't already cover.

Steps

1. HR › Training: the course

Skip this step if the course exists. List them with GET business-made/training/courses (?search=).

POST business-made/training/courses/link (saveLinkCourse, 174). Edit with PUT …/training/courses/link/<id>. On screen: Add course.

FieldOn screenValues
titleTitleRequired.
launchUrlLinkWhere staff go when they press Start. Must start with http:// or https://.
postIdContent Studio courseThe sk of a Content Studio post with contentType: "course" (Course with a quiz). Staff take it in the site's course player. Give this or launchUrl.
durationMinutesDuration (minutes)0 or more.
renewalMonthsRenews every (months)Empty means it never expires. A requirement's own renewal wins.
categoryCategorycompliance (default), technical, soft-skills, leadership, safety, product, onboarding, professional-development, other.
providerName, descriptionProvider, DescriptionFree text.

A refusal is 400 COURSE_INVALID with every problem listed. Tested: "launchUrl must be an http(s) link; durationMinutes must be 0 or more; category must be one of compliance, technical, …". Without either source: "launchUrl or a Content Studio course is required".

A course with a postId switches on the website's course player feature, so it can be taken there. If the business has no site: "This business has no website yet, and the course player runs on it."

Hands on: the course id. Check: open /hr/learning?openId=<id>. The course's row shows its source (Link or Content Studio), duration and renewal.

Or: build the course from the business's SOPs (AI)

POST business-made/training/ai/course-from-sop (readiness-ai/course-generator.service.ts). On screen: Build from SOP.

FieldWhat it is
documentIdsStored files (fileinfo sk or storage path): PDF, Word, PowerPoint, text, Markdown. Must be a list.
policyIdsbm_policy sks. Their text, sections and attached file are read.
titleOptional. Empty lets the AI name it. A title that is already a course gives 409 COURSE_TITLE_EXISTS.
quiz, quizQuestionsDefault true and 8. Questions 1 to 30, else "quizQuestions must be between 1 and 30." (tested).
languagesOptional language codes, e.g. ["es"]. Each one is another AI call.
guidanceWhat to stress (Anything to emphasize).

With no document or policy: "Choose at least one document or policy to build the course from." (tested). It writes a draft bm_course (version 1) and a Content Studio course post with one page per module and a server-marked quiz. The pass mark is the AI's, else 80. Nothing reaches staff until it is published.

Stop: this calls the AI. It uses the business's own AI integration, else Appmint's, which is billed to the business by tokens. With neither, it fails with "No AI integration is set up for this organization." Say so and wait for yes.

Then, from the course's version (/hr/learning?sopCourse=<id>):

CallOn screenWhat it does
POST …/training/ai/courses/<id>/content-studioEdit in Content StudioA draft opens its own post. A live version makes a new draft from a copy.
POST …/training/ai/courses/<id>/regenerate { replaceEdits? }Rebuild from sourcesA draft changed in Content Studio is refused (409 DRAFT_EDITED) unless replaceEdits: true. Calls the AI.
POST …/training/ai/courses/<id>/translate { languages }TranslateCalls the AI once per language.
POST …/training/ai/courses/<id>/publish { dueWithinDays? }Publish… → PublishMakes it the live course (see the Stop below).
POST …/training/courses/<id>/deleteDelete draftOnly a never-published draft.
POST …/training/ai/check-sources { courseId? }Check sourcesDrafts the next version of any course whose sources changed.

Stop before publishing. Publishing puts the course on the lists of everyone its requirements cover, moves requirements and direct assignments onto the new version, and asks people who finished the old version to redo it within dueWithinDays (else the requirement's own window, else 14 days). Publish from here, not from Content Studio, which refuses with 409 SOP_COURSE_PUBLISH.

The full version flow (drafts, sources, translations) is in the Business Made docs: Courses built from your SOPs.

2. HR › Readiness: the requirement

POST business-made/readiness/rules (readiness-rules.service.ts). Edit with PUT …/rules/<id>. On screen: HR › Readiness, New requirement, Save requirement.

FieldValues
titleRequired, up to 200 characters.
codeUpper-case letters, digits, _ and -. Made from the title when empty (tested: Food handler became …-FOOD-HANDLER). Must be unique.
kindcourse, certification, policy, document, form, signoff, task.
statusdraft (default), active, retired. Only active requirements apply.
targetWhat satisfies it, by kind: courseIds (passing any one), certificationId, policyId, documentType (e.g. i9, w4, certificate, minor-work-permit), formKey, signoff { stations, checklist, observerRoles }, task { title, instructions, assigneeRelation }.
appliesTopositions (job titles), locations, departments, roles, jobLevels, employmentTypes (full-time, part-time, contract, temporary, intern), stations, jurisdictions, minDaysEmployed, ageUnder. Different fields must all match; values within one field are alternatives. Empty means everyone.
duedueFrom: start_date (default), assignment, role_change, promotion, before_first_shift, threshold_met. dueWithinDays. renewalMonths (empty = never). renewBeforeDays (default 60).
evidence.minScoreThe pass mark for a course. It wins over the course's own.
enforcementOne entry per gate, { effect, graceDays } (below). A gate left out has no effect.

The gates (enforcement keys) and when each is checked:

KeyOn screenChecked when
schedulerSchedulerSomeone is put on a shift or takes an open shift. Trainee and shadow shifts are never checked.
clockInClock inSomeone clocks in.
posPOS / order entrySomeone signs in to or rings up on the till. permissionScope (e.g. alcohol) narrows it.
kitchenStationPrep line (Work station)Someone moves a ticket at a station on a work line.
payrollPayrollThe pay run checks readiness exceptions.
accessAccessThe roles and groups in gatedRoles / gatedGroups stay off until it is met.

effect is none, warn, override (allowed only with an override, step 5), or block. Only overdue and expired items have any effect. For graceDays after the due date (or expiry), any effect is softened to warn.

A refusal is 400 RULE_INVALID with each field's problem. Tested: an empty title, kind skill and effect stop gave "Required", "One of: course, certification, policy, document, form, signoff, task" and "One of: none, warn, override, block". An active policy, document, form or task requirement must name its target ("Choose the policy to acknowledge before activating").

PUT replaces each top-level object you send. Sending enforcement with one gate removes the others. Read the requirement (GET …/rules/<id>) and send each object whole.

Preview who it hits: POST …/rules/<id>/preview (an empty body, or unsaved edits; new as the id for an unsaved rule). Tested: it listed the one person whose job title matched.

Stop before saving it active with override or block on any gate. From then on it stops real people being scheduled, clocking in, selling or being paid. Show the preview's people and the gates, and wait for yes. A draft is safe to save first.

Ready-made requirements for some US states and cities (harassment prevention, food handler and food manager, minor work permits) come as packs: GET …/readiness/packs, then POST …/readiness/packs/<packId>/apply {}. That makes draft requirements with no course or policy attached. Attach the business's own, then set them active.

Hands on: the requirement's id. Check: open /hr/readiness?rule=<id>. Who this applies to now lists the people from the preview.

3. HR › Training: who has it

People a requirement covers are put on its course by themselves. To give the course to someone else: POST business-made/training/courses/<id>/assign { employeeIds, dueDate? }. Someone already on it keeps their enrollment and gets the new due date (tested). Assigning sends nothing.

employeeIds are employee codes (EMP-…), up to 500.

The roster: GET business-made/training/courses/<id>/roster (?state=, ?search=, ?location=). Each person is assigned, in_progress, due_soon (within 14 days), overdue, completed, expiring (within 30 days), expired, covered (by another course) or waived. Tested: the covered person showed due_soon with a due date, source: "requirement".

To take someone off: POST …/courses/<id>/withdraw { employeeIds, reason? }. Someone a requirement covers can't be taken off. Tested: "A requirement puts this course on 's list (…). Waive the requirement for them instead."

Check: open the course's Roster on /hr/learning.

4. HR › Readiness: is this person ready?

GET business-made/readiness/employee/<employee code> gives every requirement for the person with status, dueDate, completedAt, expiresAt, the proof, the gates it enforces, and any waiver and overrides. On screen: /hr/readiness?employee=<code>.

Statuses: compliant, due_soon, overdue, expired, waived, not_required.

To ask what a gate would answer: GET business-made/readiness/check/<employee code>?gate=<key>, optionally with at=<ISO time>, station, shiftId or permissionScope. It returns { effect, blocking[], warnings[], overrideActive? }. Tested on one person with a 7-day course requirement (clock-in block, scheduler warn):

CheckAnswer
gate=clockIn todayeffect: "none". The item is due_soon.
gate=clockIn&at= after the due dateeffect: "block", the item overdue in blocking.
gate=scheduler&at= after the due dateeffect: "warn", the item in warnings.
no gate400 "Pick where the check applies".

A requirement added after someone started is due from the day it reached them, not from their start date (tested: dueBasis: "rule_added"). Turning on a requirement doesn't make everyone overdue at once.

When a gate refuses someone, the refusal says what is missing: " can't : ." (code READINESS_BLOCK).

5. Record a completion, an override or a waiver

Completions depend on the course:

CourseHow it completes
Link, no course appA person records it: POST business-made/training/courses/<id>/complete { employeeIds, completedAt?, score?, note?, certificate? }. On screen: the roster's Mark completed.
Link with a course appThe course app reports it.
Content Studio, including SOP coursesOnly the course itself, when the person finishes every page in the course player and reaches the pass mark on the server-marked quiz. Mark completed is refused: "This is a Content Studio course: completion and score come only from the course itself."

Tested on a link course with a 12-month renewal: completing made the person compliant, expiring 12 months on, and clock-in answered none. completedAt can't be in the future ("completedAt cannot be in the future", tested). A score below the pass mark is refused for that person. To undo: POST …/courses/<id>/not-completed { employeeIds, note? }. Tested: the person went back to due_soon.

When staff press I've finished on a course with no quiz, the roster shows Says finished … — confirm with Mark completed. It doesn't count until someone marks it.

Nobody can mark their own training ("Nobody can mark their own training completed"). If the AI Agent is asked to mark completions, do it only on the person's say-so, for the people they name.

Override (let someone work while a requirement is missing, until a date): POST business-made/readiness/overrides with:

{ "employeeId": "EMP-…", "requirementId": "<id>", "gate": "clockIn",
  "reasonCode": "training_scheduled", "reason": "Booked on the course for Friday",
  "expiresAt": "2026-11-30T23:59:00Z" }

gate is one key or all. reasonCode: renewal_booked, awaiting_document, training_scheduled, business_need, not_applicable, system_error, other. Tested: a bad code, an empty reason and a past expiry gave "The override is incomplete" with each problem. A good one made the clock-in check answer warn with overrideActive (the approver's name and expiry), and after the expiry block again. An override covers that one requirement at that gate. On screen: Override in the person's drawer, or Override… on a refused schedule, clock-in or station move.

Waiver (the requirement doesn't apply to them, every gate): POST business-made/readiness/waivers { employeeId, requirementId, reason, reasonCode?, expiresAt? }. No expiresAt means no end. Tested: the item read waived. End it with POST …/waivers/<id>/revoke (End waiver). Tested: back to due_soon.

The approver is whoever is signed in and making the call, and the call is refused for some people. The refusals the AI will meet: "An AI employee cannot approve an override" and "Nobody can approve an override for themselves". Stop before every override and waiver. Say who, which requirement, which gate, why and until when, and record it only on the approver's yes.

Check: GET …/readiness/employee/<code> shows the waiver or the overrides on the item. HR › Readiness › Overrides (?view=overrides) lists them.

Emails and notices

Creating a requirement, assigning a course, completing, overriding, waiving and publishing a new course version send nothing.

Reminders come only from the daily readiness run (readiness-daily, 04:00 by default; HR › Readiness › Schedules), or Run now. Each is an email plus an in-app notice, sent once per person, requirement, cycle and stage (readiness/readiness-jobs.service.ts):

TemplateToWhen
readiness.dueThe personDays before the due date: reminderDays, default 60, 30, 14, 7
readiness.overdueThe personPast due
readiness.overdue_managerTheir supervisor, else a manager at their location14 days before due (notifyManagerAtDays)
readiness.overdue_hrOwners, admins and the HR people in leave settings7 days before due (notifyHrAtDays)
readiness.blockedThe person and their managerA gate is past grace with block

So activating a requirement means everyone in scope starts getting these from the next run. Say so at the Stop in step 2. Don't press Run now, or call POST …/readiness/sync with escalate: true, without a yes.

When it's done

Tell the person: the course and where it is taken, the requirement (who, by when, renewal), each gate's effect, who is covered now (the preview), and any completions, overrides or waivers recorded. Open /hr/readiness?rule=<id> and the course's roster on /hr/learning.

What it can't do yet

  • A new SOP course version doesn't make people redo it. Publishing shortens the old completions on paper, but readiness reads Content Studio completions from their own proof, which keeps its expiry. People who finished the old version stay compliant (code, not tested: course-versioning.service.ts 549, readiness.evaluator.ts 495–498 and 598–606). Assign the new version to them by hand.
  • An ended waiver still shows as active in Overrides (?active=true). Tested: after End waiver, the person read due_soon, but the list still had the waiver active: true (readiness-reports.service.ts 329). Read the person, not the list.
  • The Enrollments tab's Mark complete (business-made/learning/enrollments/<id>/complete) also marks Content Studio courses, and readiness never counts those. It sets no expiry either. Use the roster's Mark completed (step 5).
  • The older learning routes and AI methods (LearningService.deleteCourse, archiveCourse, assignCourse) skip the checks in step 1 and step 3. Delete removes a course that people took or a requirement names. Archive withdraws nobody. Assign skips anyone with any past enrollment.
  • An active course requirement needs no course. Applying a pack with activate: true makes active requirements that nothing can satisfy.
  • course-from-sop with documentIds as a string fails with a 500 instead of "documentIds must be a list." (tested).
  • An HR role held only through a group doesn't count for overrides and waivers, though it does for marking completions (readiness.controller.ts 12–16).

Don'ts

  • Don't set a requirement active with override or block without showing who it hits and getting a yes.
  • Don't send a partial PUT. It drops the gates you left out.
  • Don't record an override or waiver, or mark training completed, without the approver's yes for those people.
  • Don't mark a Content Studio course completed by any route. Its completion comes from the course.
  • Don't build, rebuild or translate an SOP course without saying it calls the AI and is billed.
  • Don't publish an SOP course without a yes. It reaches every covered person and asks past finishers to redo it.
  • Don't run the daily readiness run or a sync with escalate to "test" it. It emails staff, managers and HR.