QRG Generation Deep Dive
This page is a self-contained developer reference for integrating with the QRG (Quick Reference Guide) generation API. It covers the full lifecycle from kickoff to result retrieval, including the 4-step AI processing pipeline, form processing rules, error handling, and cancellation.
Overview
The QRG generation system transforms Emergency Operation Plan (EOP) annex forms into role-specific Quick Reference Guides. Each QRG contains three phases of instructions -- BEFORE (preparation), DURING (response), and AFTER (recovery) -- tailored to a specific role such as Teacher, Administrator, or Support Staff.
A single EOP can produce dozens of QRGs. For example, an EOP with 5 annexes and 4 roles generates up to 20 QRGs (one per role per annex).
The workflow is asynchronous. A kickoff request returns immediately with tracking identifiers. Consumers poll for status and retrieve the result when processing completes.
Generation Lifecycle
Authentication
See Authentication and Setup for full details on obtaining credentials, token acquisition, and environment base URLs.
Every request requires three headers:
| Header | Value |
|---|---|
Authorization | Bearer {access_token} (OAuth 2.0 client credentials) |
Ocp-Apim-Subscription-Key | {subscription_key} |
Content-Type | application/json |
All examples on this page use the Development base URL (https://dev-k12-aitools-t27p-apim.azure-api.net). Replace it with the appropriate URL for your target environment.
Kickoff
Start QRG generation by submitting an EOP identifier.
Endpoint: POST /ai/api/tools/k12-qrg-generation-kickoff/execute
Request Body
{
"data": {
"emergencyOperationPlanId": "d4e5f6a7-b8c9-0123-d456-e78901234567",
"generalUserTagIds": ["a1b2c3d4-e5f6-7890-abcd-100000000001", "a1b2c3d4-e5f6-7890-abcd-100000000002"],
"sessionId": null,
"waitForResult": false
}
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
emergencyOperationPlanId | string (UUID) | Yes | The EOP to generate QRGs from |
generalUserTagIds | array of string (UUID) | No | Filter generation to specific role tags. When omitted, all roles assigned to the EOP are used. |
sessionId | string | No | Caller-provided session identifier for correlation. A new one is generated if null. |
waitForResult | boolean | No | When true, the response blocks until processing completes. Default: false. |
Accepted Response
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sessionId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "accepted"
}
curl Example
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-qrg-generation-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"emergencyOperationPlanId": "d4e5f6a7-b8c9-0123-d456-e78901234567",
"generalUserTagIds": ["a1b2c3d4-e5f6-7890-abcd-100000000001"],
"sessionId": null,
"waitForResult": false
}
}'
The AI Processing Pipeline
For each annex form in the EOP, the system runs four sequential AI steps. Each step feeds its output into the next.
Step 1: Category Classification
The system analyzes the annex text and assigns a single emergency or functional category.
Input: Cleaned annex form content
Output:
category-- The specific category (e.g.,FIRE,LOCKDOWN)type-- EitherEMERGENCY(hazard-based) orFUNCTIONAL(procedure-based)confidence--high,medium, orlowreasoning-- Brief explanation of the classification decision
See QRG Types and Categories for the full list of valid categories.
Step 2: Group Extraction
The system reads the annex content and identifies all audience groups or roles referenced in the procedures.
Input: Full annex content plus the classification from Step 1
Output: An array of group names found in the text
Example:
Given annex text containing:
"Teachers should lead students to the assembly point. Administrators coordinate with emergency services. Bus Drivers secure their vehicles."
The system extracts: ["Teachers", "Administrators", "Bus Drivers"]
Step 3: Role Mapping
The system maps the informal group names extracted in Step 2 to the organization's official role tag identifiers.
Input:
- Extracted group names from Step 2
- The list of available role tags assigned to the EOP
Output: A mapping from each group name to one or more role tag UUIDs
Example:
| Extracted Group | Mapped Role Tag ID |
|---|---|
| Teachers | a1b2c3d4-e5f6-7890-abcd-100000000001 |
| Administrators | a1b2c3d4-e5f6-7890-abcd-100000000002 |
| Bus Drivers | a1b2c3d4-e5f6-7890-abcd-100000000003 |
Role tag scoping: The system only considers role tags assigned to the specific EOP, not all roles in the organization. This prevents QRGs from being assigned to irrelevant roles. After AI mapping completes, all returned role IDs are validated against the EOP's tag list. Any IDs not matching a valid EOP tag are silently removed.
Role tag priority order:
- Tags passed in the kickoff request (
generalUserTagIdsparameter) - Tags from the EOP snapshot
- Organization-level tags (rare fallback when the EOP snapshot contains none)
Automatic batching for large role lists: When the EOP has more than 12 role tags, the system automatically splits them into batches of 12 and runs the AI mapping step for each batch in parallel. Results are merged with deduplication, so the same group can map to roles discovered in different batches. This prevents LLM refusal on oversized prompts while maintaining mapping accuracy. The batching is transparent to the caller -- the final output is a single unified mapping regardless of how many batches were processed.
Step 4: Content Generation
The system generates one QRG for each role mapped in Step 3. Each QRG contains three structured content blocks.
Input:
- Full cleaned annex content
- Category classification from Step 1
- Group extraction from Step 2
- Validated role mappings from Step 3
Output per QRG:
- QRG display name (e.g., "Fire Safety - Teacher")
- QRG type (
EMERGENCYorFUNCTIONAL) - Category identifier (e.g.,
FIRE) - Assigned role tag IDs
- Three content blocks:
BEFORE,DURING,AFTER
Content blocks contain HTML-formatted lists of role-specific instructions.
Form Processing Rules
The system applies the following rules when processing annex forms:
| Rule | Behavior |
|---|---|
Forms with no state.data object | Skipped with a validation error |
Forms with an empty data object (state.data has zero fields) | Skipped with a validation error |
| Forms where all fields contain only HTML tags with no text content | Skipped after cleaning yields no usable content |
| Individual fields shorter than 10 characters after HTML stripping | Excluded from the cleaned content |
| Forms where all fields are excluded after cleaning | Skipped with a validation error |
Forms with status IN_PROGRESS or NOT_STARTED | Processed normally if they contain content |
Forms with status COMPLETED but empty data | Valid -- the user accepted default values |
All form fields are considered for QRG generation. The system does not prioritize or filter by field name. It is template-agnostic and works with any form structure.
Content Cleaning
Before any AI processing, form content is cleaned:
- HTML tags and style blocks are removed
- HTML entities are decoded or removed
- Whitespace is normalized
- Fields with fewer than 10 characters of text are discarded
This cleaning typically reduces payload size by approximately 70%, improving AI processing speed and accuracy.
Result Structure
Polling for Status
Endpoint: POST /ai/api/tools/k12-qrg-generation-status/execute
{
"data": {
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
curl Example (Status)
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-qrg-generation-status/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}'
Fetching the Result
Endpoint: POST /ai/api/tools/k12-qrg-generation-result/execute
{
"data": {
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
curl Example (Result)
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-qrg-generation-result/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}'
Successful Result Response
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sessionId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "SUCCESS",
"data": {
"root": [
{
"name": "Fire Safety - Teacher",
"organizationId": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"type": "EMERGENCY",
"functionality": null,
"hazard": "FIRE",
"generalUserTagManagementType": "ASSIGN_SELECTED",
"generalUserTagIds": ["a1b2c3d4-e5f6-7890-abcd-100000000001"],
"contentBlocks": [
{
"type": "BEFORE",
"content": "<ul><li>Know your primary and secondary evacuation routes</li><li>Verify fire extinguisher locations in your area</li><li>Review student roster for accountability purposes</li></ul>"
},
{
"type": "DURING",
"content": "<ul><li>Alert students calmly and begin evacuation immediately</li><li>Lead students to the designated assembly point via the nearest safe exit</li><li>Bring your class roster for student accounting</li></ul>"
},
{
"type": "AFTER",
"content": "<ul><li>Account for all students using your roster</li><li>Report any missing students to the Incident Commander immediately</li><li>Await further instructions before allowing re-entry to the building</li></ul>"
}
]
},
{
"name": "Fire Safety - Administrator",
"organizationId": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"type": "EMERGENCY",
"functionality": null,
"hazard": "FIRE",
"generalUserTagManagementType": "ASSIGN_SELECTED",
"generalUserTagIds": ["a1b2c3d4-e5f6-7890-abcd-100000000002"],
"contentBlocks": [
{
"type": "BEFORE",
"content": "<ul><li>Ensure fire alarm systems are tested and operational</li><li>Coordinate fire drill schedule with local fire department</li><li>Maintain updated emergency contact lists for all staff</li></ul>"
},
{
"type": "DURING",
"content": "<ul><li>Activate the building fire alarm if not already triggered</li><li>Call 911 and provide building address and situation details</li><li>Coordinate with emergency responders upon arrival</li></ul>"
},
{
"type": "AFTER",
"content": "<ul><li>Collect accountability reports from all teachers</li><li>Communicate status updates to district office and parents</li><li>Coordinate building re-entry clearance with fire department</li></ul>"
}
]
}
]
},
"aiStats": {
"totalAnnexes": 5,
"aiClassifySuccess": 5,
"aiClassifyError": 0,
"aiGroupsSuccess": 5,
"aiGroupsError": 0,
"aiRolesSuccess": 5,
"aiRolesError": 0,
"aiGenerateSuccess": 18,
"aiGenerateError": 0
}
}
Result Fields -- QRG Object
| Field | Type | Description |
|---|---|---|
name | string | Display name of the generated QRG (e.g., "Fire Safety - Teacher") |
organizationId | string (UUID) | The facility organization the QRG belongs to |
type | string | EMERGENCY (hazard-based) or FUNCTIONAL (procedure-based) |
functionality | string or null | The functional category when type is FUNCTIONAL (e.g., EVACUATION). Null for emergency types. |
hazard | string or null | The hazard category when type is EMERGENCY (e.g., FIRE). Null for functional types. |
generalUserTagManagementType | string | Always ASSIGN_SELECTED for generated QRGs |
generalUserTagIds | array of string (UUID) | Role tag IDs this QRG is assigned to |
contentBlocks | array of object | The three content phases (see below) |
Result Fields -- Content Block
| Field | Type | Description |
|---|---|---|
type | string | Phase identifier: BEFORE, DURING, or AFTER |
content | string | HTML-formatted instructions for this phase |
Result Fields -- AI Statistics
| Field | Type | Description |
|---|---|---|
totalAnnexes | number | Total annex forms processed |
aiClassifySuccess | number | Annexes successfully classified (Step 1) |
aiClassifyError | number | Annexes that failed classification |
aiGroupsSuccess | number | Annexes with successful group extraction (Step 2) |
aiGroupsError | number | Annexes that failed group extraction |
aiRolesSuccess | number | Annexes with successful role mapping (Step 3) |
aiRolesError | number | Annexes that failed role mapping |
aiGenerateSuccess | number | Individual QRGs successfully generated (Step 4) |
aiGenerateError | number | Individual QRGs that failed generation |
The aiGenerateSuccess count is typically higher than totalAnnexes because each annex produces multiple QRGs (one per matched role).
Error Result Response
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sessionId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "ERROR",
"message": "AI processing failed: 6/6 annexes failed AI classification",
"errors": [
"[classify:Fire Safety Annex] AI classification failed: PermissionDenied",
"[classify:Evacuation Annex] AI classification failed: PermissionDenied",
"[classify:Lockdown Annex] AI classification failed: PermissionDenied",
"[classify:Shelter in Place Annex] AI classification failed: PermissionDenied",
"[classify:Active Assailant Annex] AI classification failed: PermissionDenied",
"[classify:Severe Weather Annex] AI classification failed: PermissionDenied"
],
"aiStats": {
"totalAnnexes": 6,
"aiClassifySuccess": 0,
"aiClassifyError": 6,
"aiGroupsSuccess": 0,
"aiGroupsError": 0,
"aiRolesSuccess": 0,
"aiRolesError": 0,
"aiGenerateSuccess": 0,
"aiGenerateError": 0
}
}
Status Lifecycle
| Status | Meaning | Terminal |
|---|---|---|
accepted | Request received and queued for processing | No |
processing | AI pipeline is actively working on annexes | No |
SUCCESS | All annexes processed; QRGs generated and saved | Yes |
ERROR | Processing failed; see errors array for details | Yes |
CANCELLED | Job was cancelled via the cancel endpoint | Yes |
The kickoff response uses lowercase "accepted". All subsequent statuses (from the status and result endpoints) use UPPERCASE values (SUCCESS, ERROR, PROCESSING, CANCELLED).
Error Handling
Per-Annex Isolation
Each annex is processed independently. A failure in one annex does not stop processing of the remaining annexes. The final status reflects the aggregate outcome:
- All annexes succeed:
SUCCESS - Some annexes succeed, some fail:
SUCCESS(with partial AI stats showing errors) - All annexes fail:
ERROR
No Fallback Content
When an AI step fails for any annex, no placeholder or generic content is generated. The failure is recorded with a specific error message, and the annex is excluded from the result.
AI Statistics for Diagnostics
The aiStats object in every result response provides per-step success and error counts. This allows integrators to determine exactly which pipeline step encountered problems:
| Failure Pattern | Likely Cause |
|---|---|
aiClassifyError is high | AI service authorization issue or model unavailability |
aiGroupsError is high | Annex content too short or ambiguous for group identification |
aiRolesError is high | No role tags configured on the EOP, or group names too dissimilar to tag names |
aiGenerateError is high | AI service timeout under heavy load |
Error Messages
The errors array in an ERROR response contains specific messages prefixed with the step name and annex title:
[classify:Annex Name] ...-- Classification failure[groups:Annex Name] ...-- Group extraction failure[roles:Annex Name] ...-- Role mapping failure[generate:Annex Name] ...-- Content generation failure
Common Error Scenarios
| Scenario | Behavior |
|---|---|
| Annex form has no content | Skipped with validation error; other annexes continue |
| No groups extracted from annex text | No QRGs generated for that annex; other annexes continue |
| Groups extracted but no roles matched | QRG still generates but with empty generalUserTagIds |
| AI service timeout | Step fails for that annex; other annexes continue |
| EOP belongs to a DISTRICT organization | Request fails immediately (QRGs can only be saved to FACILITY organizations) |
Cancellation
Cancel an in-progress generation job using the cancel endpoint.
Endpoint: POST /ai/api/tools/k12-qrg-cancel/execute
Request Body
{
"data": {
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"jobType": "generation"
}
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
requestId | string | Yes | The request identifier returned by kickoff |
jobType | string | No | generation (default) or editing |
Response
{
"status": "success"
}
Or if the job has already completed or does not exist:
{
"status": "error"
}
curl Example (Cancel)
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-qrg-cancel/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"jobType": "generation"
}
}'
Cancellation is best-effort. If the AI pipeline is mid-step when cancellation is received, the current step may complete before the job terminates. Always verify the final status via the status endpoint after issuing a cancel.
Expected Timelines
| EOP Size | Annex Count | Approximate Duration |
|---|---|---|
| Small | 1-3 | 30 seconds - 1 minute |
| Medium | 4-8 | 1-3 minutes |
| Large | 10+ | 3-5 minutes |
Processing time depends on:
- Number of annexes in the EOP
- Volume of content in each annex form
- Number of roles to generate QRGs for (more roles = more QRGs per annex)
- Current AI service load
Integrators should implement polling with a reasonable interval (e.g., every 5 seconds) and set a maximum timeout appropriate for the expected EOP size.
QRG Types and Categories
Every generated QRG is classified as either FUNCTIONAL (procedure-based) or EMERGENCY (hazard-based). The classification determines which category field is populated in the result.
Functional Categories
Functional QRGs describe standard safety procedures that apply across multiple hazard types.
| Category | Description |
|---|---|
EVACUATION | Building evacuation procedures |
REVERSE_EVACUATION | Procedures for moving people back into a building |
LOCKDOWN | Facility lockdown procedures |
SHELTER_IN_PLACE | Sheltering procedures for various threats |
ACCOUNTING_FOR_ALL_PERSONS | Accounting for all persons after an incident |
COMMUNICATIONS_AND_WARNING | Emergency communications and warning procedures |
FAMILY_REUNIFICATION | Parent/guardian reunification procedures |
RECOVERY | Post-incident recovery procedures |
CONTINUITY_OF_OPERATIONS_COOP | Continuity of operations planning |
PUBLIC_HEALTH_MEDICAL_AND_MENTAL_HEALTH | Public health, medical, and mental health response |
SECURITY | Security procedures |
OTHER | Other functional procedures |
Emergency / Hazard Categories
Emergency QRGs address specific hazard scenarios with targeted response instructions.
| Category | Description |
|---|---|
ACTIVE_SHOOTER | Active assailant response |
BOMB_THREAT | Bomb threat procedures |
TORNADO_SEVERE_WEATHER | Severe weather response |
EARTHQUAKE | Earthquake response |
FIRE | Fire emergency response |
HAZARDOUS_MATERIALS | Hazmat incident response |
PANDEMIC | Pandemic/infectious disease response |
CYBER_ATTACK | Cyber attack response |
FLOOD | Flooding emergency response |
WINTER_STORM_BLIZZARD | Winter storm or blizzard response |
GANG_VIOLENCE | Gang violence response |
RIOT_CIVIL_DISORDER_VIOLENT_PROTEST | Riot, civil disorder, or violent protest response |
MISSING_STUDENT_KIDNAPPING | Missing or abducted student procedures |
NUCLEAR_RADIATION_RELEASE | Nuclear or radiation release response |
SUICIDE_OR_DEATH | Suicide or death response |
INTRUDER_UNKNOWN_INTENTION | Unauthorized intruder response |
CRIMINAL_THREATS_OR_ACTIONS | Criminal threats or actions response |
DOMESTIC_VIOLENCE | Domestic violence response |
BUS_ACCIDENT | Bus or vehicle emergency response |
DAM_FAILURE | Dam failure response |
GAS_LEAK | Gas leak response |
POWER_FAILURE | Power failure response |
SINK_HOLES | Sinkhole response |
DANGEROUS_ANIMALS | Dangerous animals response |
CONTAMINATED_FOOD_WATER_SUPPLY | Contaminated food or water supply response |
EXPLOSION_NON_CHEMICAL | Non-chemical explosion response |
OTHER | Other hazard scenarios |
Type-to-Field Mapping
QRG type Value | Populated Field | Null Field |
|---|---|---|
FUNCTIONAL | functionality (e.g., EVACUATION) | hazard is null |
EMERGENCY | hazard (e.g., FIRE) | functionality is null |
Integration Best Practices
-
Always poll asynchronously. Use the kickoff endpoint to start generation, then poll the status endpoint at regular intervals. Do not set
waitForResult: truefor production integrations with large EOPs. -
Persist tracking identifiers. Store both
requestIdandsessionIdreturned by the kickoff response. UserequestIdfor all subsequent status and result calls. -
Handle partial results. Check
aiStatsto determine whether all annexes processed successfully. ASUCCESSstatus means the workflow completed, but individual annexes may have been skipped due to empty content. -
Expect variable QRG counts. The number of QRGs generated depends on how many roles the AI identifies in each annex. Two annexes from the same EOP may produce different numbers of QRGs.
-
Do not assume fixed form field names. The system is template-agnostic. It processes all fields in the form's data object regardless of field naming conventions.
-
Verify organization type. QRGs can only be saved to FACILITY-type organizations. Requests for EOPs belonging to DISTRICT organizations will fail immediately.
-
Implement cancellation handling. For user-facing integrations, provide a way to cancel long-running generation jobs. Always verify the final status after issuing a cancel request.