@choiceapps/communityservices-sdk 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/browser/enrollment-workflow.d.mts +54 -4
- package/browser/enrollment-workflow.mjs +89 -33
- package/browser/entry-correction.mjs +53 -10
- package/browser/entry-row-registration.mjs +63 -0
- package/browser/household-enrollment-discovery.mjs +49 -0
- package/browser/index.d.mts +3 -1
- package/browser/playwright-session.mjs +4 -3
- package/browser/workflow-error-codes.mjs +198 -0
- package/dist/cjs/mobile-preflight.d.ts +19 -0
- package/dist/cjs/mobile-preflight.d.ts.map +1 -0
- package/dist/cjs/mobile-preflight.js +75 -0
- package/dist/cjs/mobile-preflight.js.map +1 -0
- package/dist/cjs/v2.d.ts +2 -0
- package/dist/cjs/v2.d.ts.map +1 -1
- package/dist/cjs/v2.js +2 -0
- package/dist/cjs/v2.js.map +1 -1
- package/dist/cjs/v3.d.ts +2 -0
- package/dist/cjs/v3.d.ts.map +1 -1
- package/dist/cjs/v3.js +2 -0
- package/dist/cjs/v3.js.map +1 -1
- package/dist/cjs/write-labels.d.ts +13 -0
- package/dist/cjs/write-labels.d.ts.map +1 -0
- package/dist/cjs/write-labels.js +68 -0
- package/dist/cjs/write-labels.js.map +1 -0
- package/dist/esm/mobile-preflight.d.ts +19 -0
- package/dist/esm/mobile-preflight.d.ts.map +1 -0
- package/dist/esm/mobile-preflight.js +70 -0
- package/dist/esm/mobile-preflight.js.map +1 -0
- package/dist/esm/v2.d.ts +2 -0
- package/dist/esm/v2.d.ts.map +1 -1
- package/dist/esm/v2.js +2 -0
- package/dist/esm/v2.js.map +1 -1
- package/dist/esm/v3.d.ts +2 -0
- package/dist/esm/v3.d.ts.map +1 -1
- package/dist/esm/v3.js +2 -0
- package/dist/esm/v3.js.map +1 -1
- package/dist/esm/write-labels.d.ts +13 -0
- package/dist/esm/write-labels.d.ts.map +1 -0
- package/dist/esm/write-labels.js +65 -0
- package/dist/esm/write-labels.js.map +1 -0
- package/docs/assessments.md +38 -1
- package/docs/browser-session.md +1 -1
- package/docs/capabilities.md +15 -2
- package/docs/enrollments.md +75 -3
- package/docs/reference.md +3 -2
- package/docs/troubleshooting.md +20 -0
- package/examples/README.md +2 -0
- package/examples/checked-assessment-writes.mts +14 -0
- package/examples/enrollment-discovery-and-correction.mts +19 -0
- package/examples/entry-correction-workflow.mts +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {parseRestInstant,toItems} from '../dist/esm/index.js';
|
|
3
|
+
|
|
4
|
+
const object=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
|
|
5
|
+
const canonical=v=>JSON.stringify(v,(_,x)=>object(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
|
|
6
|
+
const hash=v=>createHash('sha256').update(canonical(v)).digest('hex');
|
|
7
|
+
const positive=v=>Number.isSafeInteger(v)&&v>0;
|
|
8
|
+
|
|
9
|
+
/** Registers caller-selected existing identities locally; never writes an assessment. */
|
|
10
|
+
export function createEntryRowRegistrar({api,store,enrollmentSession,discover,scope,reservationScope,makeError}){
|
|
11
|
+
const fail=(code,reason)=>{throw makeError(reason,{code,committed:false});};
|
|
12
|
+
const key=v=>typeof v==='string'&&!!v.trim()&&v.length<=200;
|
|
13
|
+
return async raw=>{
|
|
14
|
+
if(!object(raw)||Object.keys(raw).some(k=>!['key','enrollmentKey','clientId','rows'].includes(k))||!key(raw.key)||!key(raw.enrollmentKey)||!positive(raw.clientId)||!Array.isArray(raw.rows)||!raw.rows.length)fail('ROW_REGISTRATION_INPUT_INVALID','valid registration key, entry key, client and explicit rows required');
|
|
15
|
+
if(typeof enrollmentSession?.readEnrollment!=='function')fail('ROW_REGISTRATION_READER_REQUIRED','authoritative enrollment reader required');
|
|
16
|
+
const input=structuredClone(raw);input.rows.sort((a,b)=>String(a?.key).localeCompare(String(b?.key)));
|
|
17
|
+
const keys=new Set(),ids=new Set();
|
|
18
|
+
for(const row of input.rows){
|
|
19
|
+
if(!object(row)||Object.keys(row).some(k=>!['key','parentField','recordsetId'].includes(k))||!key(row.key)||!key(row.parentField)||!positive(row.recordsetId)||keys.has(row.key)||ids.has(row.recordsetId))fail('ROW_REGISTRATION_INPUT_INVALID','unique row keys, parents and positive recordset IDs required');
|
|
20
|
+
keys.add(row.key);ids.add(row.recordsetId);
|
|
21
|
+
}
|
|
22
|
+
return store.withLock(scope(input.clientId),async()=>{
|
|
23
|
+
const reservations=await store.withLock(reservationScope(input.clientId),()=>store.read(reservationScope(input.clientId)));
|
|
24
|
+
if(!Array.isArray(reservations)||reservations.some(r=>r.stage!=='verified'))fail('WORKFLOW_PENDING','unfinished household workflow must be reconciled first');
|
|
25
|
+
const records=await store.read(scope(input.clientId));
|
|
26
|
+
if(!Array.isArray(records)||records.some(r=>r.version!==1||!object(r.steps)))fail('WORKFLOW_JOURNAL_INVALID','invalid workflow journal');
|
|
27
|
+
if(records.some(r=>r.stage!=='verified'&&!(r.kind==='correction'&&r.stage==='rejected')))fail('WORKFLOW_PENDING','unfinished workflow must be reconciled first');
|
|
28
|
+
const entries=records.filter(r=>r.key===input.enrollmentKey&&r.kind==='entry'&&r.stage==='verified');
|
|
29
|
+
if(entries.length!==1)fail('ROW_REGISTRATION_ENTRY_REQUIRED','verified enrollment workflow required');const entry=entries[0];
|
|
30
|
+
const prior=Object.hasOwn(entry.rowRegistrations??{},input.key)?entry.rowRegistrations[input.key]:undefined,intentHash=hash(input);
|
|
31
|
+
if(prior&&prior.intentHash!==intentHash)fail('ROW_REGISTRATION_INTENT_CONFLICT','registration key conflicts with previous intent');
|
|
32
|
+
const target={clientId:input.clientId,enrollmentId:entry.enrollmentId},at=entry.context.entryDate;
|
|
33
|
+
const verifyEnrollment=async()=>{
|
|
34
|
+
const s=await enrollmentSession.readEnrollment(target);
|
|
35
|
+
if(s?.source!=='gwt-rpc'||s.tenant!==api.tenant||s.clientId!==target.clientId||s.enrollmentId!==target.enrollmentId||s.providerId!==entry.context.providerId||s.enrollmentType!==entry.context.type||parseRestInstant(s.projectStartDate)!==parseRestInstant(at))fail('ROW_REGISTRATION_CONTEXT_MISMATCH','enrollment identity changed during row registration');
|
|
36
|
+
};
|
|
37
|
+
await verifyEnrollment();
|
|
38
|
+
const discovered=await discover({...entry.context}),definition=discovered.entry.definition;
|
|
39
|
+
const schemaHash=hash({providerId:discovered.provider.providerId,entry:{assessment:discovered.entry.assessment,questions:definition.questions},interim:{assessment:discovered.interim.assessment,questions:discovered.interim.definition.questions}});
|
|
40
|
+
if(schemaHash!==entry.schemaHash)fail('WORKFLOW_SCHEMA_CHANGED','program assessment configuration changed');
|
|
41
|
+
const rowMap=entry.rows??{},parents=entry.rowParents??{};
|
|
42
|
+
for(const row of input.rows){
|
|
43
|
+
if(!definition.questions.some(q=>q.fieldName===row.parentField&&q.questionType==='sub_assessment'))fail('ROW_REGISTRATION_PARENT_INVALID','subassessment parent not in entry definition');
|
|
44
|
+
if(Object.hasOwn(rowMap,row.key)&&(rowMap[row.key]!==row.recordsetId||!Object.hasOwn(parents,row.key))||Object.entries(rowMap).some(([key,id])=>key!==row.key&&id===row.recordsetId)||Object.hasOwn(parents,row.key)&&parents[row.key]!==row.parentField)fail('ROW_REGISTRATION_BINDING_CONFLICT','row key or recordset is already bound differently');
|
|
45
|
+
}
|
|
46
|
+
const verifyRows=async effectiveDate=>{
|
|
47
|
+
const response=toItems(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:entry.assessmentCode},query:{dateEffective:effectiveDate}}));
|
|
48
|
+
for(const row of input.rows){
|
|
49
|
+
const parent=response.filter(p=>p?.virt_field_name===row.parentField&&p.question_type==='sub_assessment');
|
|
50
|
+
if(parent.length!==1||!Array.isArray(parent[0].sub_assessment_answer_list))fail('ROW_REGISTRATION_READ_UNAVAILABLE','complete row registration read required');
|
|
51
|
+
const matches=parent[0].sub_assessment_answer_list.filter(r=>r?.recordset_id===row.recordsetId);
|
|
52
|
+
if(matches.length!==1||!Array.isArray(matches[0].sub_assessment_list))fail('ROW_REGISTRATION_ROW_MISSING','selected row is missing or ambiguous at the entry date or current read');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
await verifyRows(at);await verifyRows(new Date(Math.max(Date.now(),parseRestInstant(at)+1000)).toISOString());await verifyEnrollment();
|
|
56
|
+
entry.rows=Object.fromEntries([...Object.entries(rowMap),...input.rows.map(r=>[r.key,r.recordsetId])]);
|
|
57
|
+
entry.rowParents=Object.fromEntries([...Object.entries(parents),...input.rows.map(r=>[r.key,r.parentField])]);
|
|
58
|
+
entry.rowRegistrations=Object.fromEntries([...Object.entries(entry.rowRegistrations??{}),[input.key,{intentHash,rows:input.rows}]]);
|
|
59
|
+
await store.write(scope(input.clientId),structuredClone(records));
|
|
60
|
+
return {key:input.key,enrollmentKey:input.enrollmentKey,clientId:input.clientId,enrollmentId:entry.enrollmentId,outcome:prior?'already-present':'verified',recordsetIds:structuredClone(entry.rows)};
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {parseRestInstant,toItems} from '../dist/esm/index.js';
|
|
2
|
+
|
|
3
|
+
const positive=v=>Number.isSafeInteger(v)&&v>0;
|
|
4
|
+
const pairs=members=>JSON.stringify(members.map(({clientId,enrollmentId})=>({clientId,enrollmentId})).sort((a,b)=>a.clientId-b.clientId));
|
|
5
|
+
|
|
6
|
+
/** Resolves only a unique current household satisfying the adoption membership contract. */
|
|
7
|
+
export function createHouseholdEnrollmentDiscovery({api,enrollmentSession,makeError}){
|
|
8
|
+
const fail=(code,reason)=>{throw makeError(reason,{code,committed:false});};
|
|
9
|
+
return async input=>{
|
|
10
|
+
if(!input||Object.keys(input).some(k=>!['headClientId','headEnrollmentId'].includes(k))||!positive(input.headClientId)||!positive(input.headEnrollmentId))fail('HOUSEHOLD_DISCOVERY_INPUT_INVALID','head client and enrollment IDs required');
|
|
11
|
+
if(typeof enrollmentSession?.readEnrollment!=='function'||typeof api.households?.listForClient!=='function'||typeof api.households?.get!=='function')fail('HOUSEHOLD_DISCOVERY_READERS_REQUIRED','GWT detail and complete household readers required');
|
|
12
|
+
const target={clientId:input.headClientId,enrollmentId:input.headEnrollmentId};
|
|
13
|
+
const anchor=await enrollmentSession.readEnrollment(target);
|
|
14
|
+
const valid=(s,t)=>s?.source==='gwt-rpc'&&s.tenant===api.tenant&&s.clientId===t.clientId&&s.enrollmentId===t.enrollmentId&&positive(s.providerId)&&typeof s.enrollmentType==='string'&&Number.isFinite(parseRestInstant(s.projectStartDate));
|
|
15
|
+
if(!valid(anchor,target)||!Array.isArray(anchor.members)||!anchor.members.length||anchor.members.some(m=>!positive(m?.clientId)||!positive(m?.enrollmentId))||new Set(anchor.members.map(m=>m.clientId)).size!==anchor.members.length||new Set(anchor.members.map(m=>m.enrollmentId)).size!==anchor.members.length||!anchor.members.some(m=>m.clientId===target.clientId&&m.enrollmentId===target.enrollmentId))fail('HOUSEHOLD_DISCOVERY_DETAIL_INVALID','complete linked enrollment cohort required');
|
|
16
|
+
if(anchor.members.length===1&&anchor.grouped!==true)fail('ENROLLMENT_NOT_GROUPED','standalone enrollment uses adoptEnrollment');
|
|
17
|
+
if(anchor.grouped===false&&anchor.members.length>1)fail('HOUSEHOLD_DISCOVERY_DETAIL_INVALID','group flag conflicts with linked cohort');
|
|
18
|
+
const members=anchor.members.map(({clientId,enrollmentId})=>({clientId,enrollmentId})).sort((a,b)=>a.clientId-b.clientId),starts=new Map();
|
|
19
|
+
for(const member of members){
|
|
20
|
+
const s=member.clientId===target.clientId?anchor:await enrollmentSession.readEnrollment(member);
|
|
21
|
+
if(!valid(s,member)||s.providerId!==anchor.providerId||s.enrollmentType!==anchor.enrollmentType||!Array.isArray(s.members)||pairs(s.members)!==pairs(members))fail('HOUSEHOLD_DISCOVERY_COHORT_CHANGED','linked enrollment cohort changed during discovery');
|
|
22
|
+
starts.set(member.clientId,parseRestInstant(s.projectStartDate));
|
|
23
|
+
}
|
|
24
|
+
const candidates=[],seen=new Set();let total;
|
|
25
|
+
for(let startPage=1;startPage<=100;startPage++){
|
|
26
|
+
const result=await api.households.listForClient({path:{clientId:input.headClientId},query:{perPage:100,startPage,total_count:true}});
|
|
27
|
+
if(!Number.isSafeInteger(result?.count)||result.count<0||!Array.isArray(result.values)||total!==undefined&&total!==result.count)fail('HOUSEHOLD_DISCOVERY_LIST_INCOMPLETE','household list count unavailable or changed');total=result.count;
|
|
28
|
+
for(const h of result.values){
|
|
29
|
+
if(!positive(h?.householdId)||seen.has(h.householdId)||!Array.isArray(h.clients)||h.clients.some(m=>!positive(m?.clientId))||new Set(h.clients.map(m=>m.clientId)).size!==h.clients.length||!h.clients.some(m=>m.clientId===input.headClientId))fail('HOUSEHOLD_DISCOVERY_LIST_INCOMPLETE','invalid household list');
|
|
30
|
+
seen.add(h.householdId);candidates.push(h);
|
|
31
|
+
}
|
|
32
|
+
if(seen.size===total)break;
|
|
33
|
+
if(seen.size>total||!result.values.length||startPage===100)fail('HOUSEHOLD_DISCOVERY_LIST_INCOMPLETE','incomplete household list');
|
|
34
|
+
}
|
|
35
|
+
const matches=h=>{
|
|
36
|
+
if(h.isActive!==true||!Array.isArray(h.clients))return false;
|
|
37
|
+
const heads=h.clients.filter(m=>m.headofhouseholdStatus&&m.isActive!==false&&!m.dateLeft);
|
|
38
|
+
return heads.length===1&&heads[0].clientId===input.headClientId&&members.every(m=>{
|
|
39
|
+
const found=h.clients.filter(c=>c.clientId===m.clientId);
|
|
40
|
+
return found.length===1&&found[0].isActive!==false&&!found[0].dateLeft&&Number.isFinite(parseRestInstant(found[0].dateJoined))&&parseRestInstant(found[0].dateJoined)<=starts.get(m.clientId);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const found=candidates.filter(matches);
|
|
44
|
+
if(found.length!==1)fail(found.length?'HOUSEHOLD_DISCOVERY_AMBIGUOUS':'HOUSEHOLD_DISCOVERY_NOT_FOUND','a unique current household matching the full cohort, head and dates is required');
|
|
45
|
+
const fresh=toItems(await api.households.get({path:{householdId:found[0].householdId}}));
|
|
46
|
+
if(fresh.length!==1||fresh[0].householdId!==found[0].householdId||!matches(fresh[0]))fail('HOUSEHOLD_DISCOVERY_COHORT_CHANGED','household membership changed during discovery');
|
|
47
|
+
return {headClientId:input.headClientId,headEnrollmentId:input.headEnrollmentId,householdId:fresh[0].householdId,householdSource:'verified-membership',providerId:anchor.providerId,type:anchor.enrollmentType,members};
|
|
48
|
+
};
|
|
49
|
+
}
|
package/browser/index.d.mts
CHANGED
|
@@ -17,10 +17,12 @@ export class LoginError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
export interface UiSession {readonly connection:UiConnection;browser: Browser;context: BrowserContext;page: Page;passwordRecovery:'restored-original'|'not-needed';ensureAuthenticated():Promise<void>;reauthenticate():Promise<void>;close():Promise<void>;}
|
|
19
19
|
export interface PlaywrightGwtOptions {page:Page;tenant:string;moduleBaseUrl:string;}
|
|
20
|
-
export interface PlaywrightEnrollmentSnapshot extends GwtEnrollmentSnapshot {providerId:number;enrollmentType:string;exits:readonly GwtEnrollmentExit[];}
|
|
20
|
+
export interface PlaywrightEnrollmentSnapshot extends GwtEnrollmentSnapshot {providerId:number;enrollmentType:string;exits:readonly GwtEnrollmentExit[];groupId?:number|string|null;grouped?:boolean;}
|
|
21
21
|
export interface EnrollmentListRow {
|
|
22
22
|
enrollmentId:number;clientId:number;enrollmentProvider:number;type:string;
|
|
23
23
|
projectStartDate:string;projectExitDate:string|null;isActive:boolean;
|
|
24
|
+
/** An enrollment group pointer, not a household identifier or member count. */
|
|
25
|
+
groupId:number|string|null;grouped:boolean;
|
|
24
26
|
}
|
|
25
27
|
export interface EnrollmentListSnapshot {
|
|
26
28
|
source:'gwt-rpc';tenant:string;clientId:number;count:number;values:EnrollmentListRow[];
|
|
@@ -111,13 +111,14 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
|
|
|
111
111
|
page.on('request',listener);
|
|
112
112
|
try{await openClient(clientId);wire=await captured;}finally{page.off('request',listener);}
|
|
113
113
|
if(!wire?.policy||Object.values(wire.headers).some(v=>!v))throw Error('Fresh enrollment list context unavailable');
|
|
114
|
-
const rows=await mapped(`SELECT e.entryExitId AS enrollmentId, e.clientId AS clientId, e.entryDate AS projectStartDate, e.exitDate AS projectExitDate, e.providerId AS enrollmentProvider, e.typeEntryExit.name AS type FROM SpEntryExit AS e WHERE e.active = TRUE AND e.client.active = TRUE AND e.clientId = ${clientId} ORDER BY e.entryExitId`);
|
|
114
|
+
const rows=await mapped(`SELECT e.entryExitId AS enrollmentId, e.clientId AS clientId, e.entryDate AS projectStartDate, e.exitDate AS projectExitDate, e.providerId AS enrollmentProvider, e.typeEntryExit.name AS type, e.groupId AS groupId FROM SpEntryExit AS e WHERE e.active = TRUE AND e.client.active = TRUE AND e.clientId = ${clientId} ORDER BY e.entryExitId`);
|
|
115
115
|
const ids=new Set();
|
|
116
116
|
for(const row of rows){
|
|
117
117
|
if(!Number.isSafeInteger(row.enrollmentId)||row.enrollmentId<=0||row.clientId!==clientId||!Number.isSafeInteger(row.enrollmentProvider)||row.enrollmentProvider<=0||typeof row.type!=='string'||!row.type.trim()||typeof row.projectStartDate!=='string'||!Number.isFinite(Date.parse(row.projectStartDate))||row.projectExitDate!==null&&(typeof row.projectExitDate!=='string'||!Number.isFinite(Date.parse(row.projectExitDate)))||ids.has(row.enrollmentId))throw Error('Invalid enrollment list');
|
|
118
118
|
ids.add(row.enrollmentId);
|
|
119
|
+
if(row.groupId!==null){if(typeof row.groupId==='number'&&row.groupId<=0)throw Error('Invalid enrollment group');literal(row.groupId);}
|
|
119
120
|
}
|
|
120
|
-
return {source:'gwt-rpc',tenant,clientId,count:rows.length,values:rows.map(row=>({...row,isActive:true}))};
|
|
121
|
+
return {source:'gwt-rpc',tenant,clientId,count:rows.length,values:rows.map(row=>({...row,isActive:true,grouped:row.groupId!==null}))};
|
|
121
122
|
};
|
|
122
123
|
const readEnrollment=async target=>{
|
|
123
124
|
const model=await openTarget(target),householdClientIds=await householdIds(target);
|
|
@@ -149,7 +150,7 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
|
|
|
149
150
|
reviews.push({reviewId:row.reviewId,type:row.type,reviewDate:row.reviewDate,clientIds:clients.map(c=>c.clientId).sort((a,b)=>a-b)});
|
|
150
151
|
}
|
|
151
152
|
reviews.sort((a,b)=>a.reviewId-b.reviewId);
|
|
152
|
-
const data={source:'gwt-rpc',tenant,sessionId,...target,projectStartDate:entry.projectStartDate,providerId:entry.providerId,enrollmentType:entry.enrollmentType,householdClientIds,members,exits,reviews};
|
|
153
|
+
const data={source:'gwt-rpc',tenant,sessionId,...target,projectStartDate:entry.projectStartDate,providerId:entry.providerId,enrollmentType:entry.enrollmentType,groupId:entry.groupId,grouped:entry.groupId!==null,householdClientIds,members,exits,reviews};
|
|
153
154
|
return {...data,revision:digest(model+JSON.stringify({...data,sessionId:undefined}))};
|
|
154
155
|
};
|
|
155
156
|
const chooseMembers=async(desired,implicitClientId,dialogButton)=>{
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Stable public codes. Keep code values when changing their associated messages.
|
|
2
|
+
const codes = new Map([
|
|
3
|
+
["DOB and household head corrections require their dedicated workflows", "DOB_AND_HOUSEHOLD_HEAD_CORRECTIONS_REQUIRE_THEIR_DEDICATED_WORKFLOWS"],
|
|
4
|
+
["DOB helper required for birth-date answers and carry-forward", "DOB_HELPER_REQUIRED_FOR_BIRTH_DATE_ANSWERS_AND_CARRY_FORWARD"],
|
|
5
|
+
["DOB question type changed", "DOB_QUESTION_TYPE_CHANGED"],
|
|
6
|
+
["REST API, discovery, enrollment helpers and durable store required", "REST_API_DISCOVERY_ENROLLMENT_HELPERS_AND_DURABLE_STORE_REQUIRED"],
|
|
7
|
+
["active client required", "ACTIVE_CLIENT_REQUIRED"],
|
|
8
|
+
["active household required", "ACTIVE_HOUSEHOLD_REQUIRED"],
|
|
9
|
+
["active household required for adoption", "ACTIVE_HOUSEHOLD_REQUIRED_FOR_ADOPTION"],
|
|
10
|
+
["active operational program required", "ACTIVE_OPERATIONAL_PROGRAM_REQUIRED"],
|
|
11
|
+
["added enrollment identity changed", "ADDED_ENROLLMENT_IDENTITY_CHANGED"],
|
|
12
|
+
["addition requires new household members", "ADDITION_REQUIRES_NEW_HOUSEHOLD_MEMBERS"],
|
|
13
|
+
["additional member already has an independent enrollment", "ADDITIONAL_MEMBER_ALREADY_HAS_AN_INDEPENDENT_ENROLLMENT"],
|
|
14
|
+
["additional member assessment configuration changed", "ADDITIONAL_MEMBER_ASSESSMENT_CONFIGURATION_CHANGED"],
|
|
15
|
+
["additional member entry must follow original entry", "ADDITIONAL_MEMBER_ENTRY_MUST_FOLLOW_ORIGINAL_ENTRY"],
|
|
16
|
+
["adopted enrollment identity or configuration changed", "ADOPTED_ENROLLMENT_IDENTITY_OR_CONFIGURATION_CHANGED"],
|
|
17
|
+
["adoption enrollment context mismatch", "ADOPTION_ENROLLMENT_CONTEXT_MISMATCH"],
|
|
18
|
+
["adoption exit precedes entry", "ADOPTION_EXIT_PRECEDES_ENTRY"],
|
|
19
|
+
["adoption household head mismatch", "ADOPTION_HOUSEHOLD_HEAD_MISMATCH"],
|
|
20
|
+
["adoption household membership mismatch", "ADOPTION_HOUSEHOLD_MEMBERSHIP_MISMATCH"],
|
|
21
|
+
["adoption key conflicts with previous intent", "ADOPTION_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
|
|
22
|
+
["adoption requires the original enrollment head relationship", "ADOPTION_REQUIRES_THE_ORIGINAL_ENROLLMENT_HEAD_RELATIONSHIP"],
|
|
23
|
+
["ambiguous assessment read", "AMBIGUOUS_ASSESSMENT_READ"],
|
|
24
|
+
["ambiguous closure snapshot", "AMBIGUOUS_CLOSURE_SNAPSHOT"],
|
|
25
|
+
["ambiguous correction assessment read", "AMBIGUOUS_CORRECTION_ASSESSMENT_READ"],
|
|
26
|
+
["ambiguous correction row", "AMBIGUOUS_CORRECTION_ROW"],
|
|
27
|
+
["ambiguous correction row cell", "AMBIGUOUS_CORRECTION_ROW_CELL"],
|
|
28
|
+
["ambiguous household membership", "AMBIGUOUS_HOUSEHOLD_MEMBERSHIP"],
|
|
29
|
+
["another unfinished household workflow must be reconciled first", "ANOTHER_UNFINISHED_HOUSEHOLD_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
|
|
30
|
+
["another unfinished workflow must be reconciled first", "ANOTHER_UNFINISHED_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
|
|
31
|
+
["assessment locations must be a map by Entry/Exit type", "ASSESSMENT_LOCATIONS_MUST_BE_A_MAP_BY_ENTRY_EXIT_TYPE"],
|
|
32
|
+
["assessment metadata incomplete", "ASSESSMENT_METADATA_INCOMPLETE"],
|
|
33
|
+
["assessment plan required", "ASSESSMENT_PLAN_REQUIRED"],
|
|
34
|
+
["attempted correction required for resolution", "ATTEMPTED_CORRECTION_REQUIRED_FOR_RESOLUTION"],
|
|
35
|
+
["authoritative adoption exit date required", "AUTHORITATIVE_ADOPTION_EXIT_DATE_REQUIRED"],
|
|
36
|
+
["authoritative enrollment session required for adoption", "AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED_FOR_ADOPTION"],
|
|
37
|
+
["authoritative exit details reader required", "AUTHORITATIVE_EXIT_DETAILS_READER_REQUIRED"],
|
|
38
|
+
["authoritative exit read required", "AUTHORITATIVE_EXIT_READ_REQUIRED"],
|
|
39
|
+
["authoritative head of household exit dates required", "AUTHORITATIVE_HEAD_OF_HOUSEHOLD_EXIT_DATES_REQUIRED"],
|
|
40
|
+
["authoritative individual exit read required", "AUTHORITATIVE_INDIVIDUAL_EXIT_READ_REQUIRED"],
|
|
41
|
+
["carry-forward birth date changed since enrollment", "CARRY_FORWARD_BIRTH_DATE_CHANGED_SINCE_ENROLLMENT"],
|
|
42
|
+
["carry-forward birth date is empty or follows assessment date", "CARRY_FORWARD_BIRTH_DATE_IS_EMPTY_OR_FOLLOWS_ASSESSMENT_DATE"],
|
|
43
|
+
["carry-forward field is empty", "CARRY_FORWARD_FIELD_IS_EMPTY"],
|
|
44
|
+
["client ID required", "CLIENT_ID_REQUIRED"],
|
|
45
|
+
["client, provider and supported Entry/Exit type required", "CLIENT_PROVIDER_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
|
|
46
|
+
["conflicting field coverage", "CONFLICTING_FIELD_COVERAGE"],
|
|
47
|
+
["correction answer could not be encoded", "CORRECTION_ANSWER_COULD_NOT_BE_ENCODED"],
|
|
48
|
+
["correction key conflicts with previous intent", "CORRECTION_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
|
|
49
|
+
["correction lookup could not be resolved", "CORRECTION_LOOKUP_COULD_NOT_BE_RESOLVED"],
|
|
50
|
+
["correction requires an existing entry row key and matching parent", "CORRECTION_REQUIRES_AN_EXISTING_ENTRY_ROW_KEY_AND_MATCHING_PARENT"],
|
|
51
|
+
["correction row metadata incomplete", "CORRECTION_ROW_METADATA_INCOMPLETE"],
|
|
52
|
+
["correction scalar read missing", "CORRECTION_SCALAR_READ_MISSING"],
|
|
53
|
+
["correction state changed during resolution", "CORRECTION_STATE_CHANGED_DURING_RESOLUTION"],
|
|
54
|
+
["correction state differs from its pre-write snapshot; non-application cannot be resolved", "CORRECTION_STATE_DIFFERS_FROM_ITS_PRE_WRITE_SNAPSHOT_NON_APPLICATION_CANNOT_BE_RESOLVED"],
|
|
55
|
+
["correction subassessment read missing", "CORRECTION_SUBASSESSMENT_READ_MISSING"],
|
|
56
|
+
["correction was resolved as not applied; use a new key for a corrected intent", "CORRECTION_WAS_RESOLVED_AS_NOT_APPLIED_USE_A_NEW_KEY_FOR_A_CORRECTED_INTENT"],
|
|
57
|
+
["destination and reason for leaving required", "DESTINATION_AND_REASON_FOR_LEAVING_REQUIRED"],
|
|
58
|
+
["duplicate adoption targets", "DUPLICATE_ADOPTION_TARGETS"],
|
|
59
|
+
["duplicate correction journal keys", "DUPLICATE_CORRECTION_JOURNAL_KEYS"],
|
|
60
|
+
["duplicate household journal keys", "DUPLICATE_HOUSEHOLD_JOURNAL_KEYS"],
|
|
61
|
+
["duplicate household member enrollment", "DUPLICATE_HOUSEHOLD_MEMBER_ENROLLMENT"],
|
|
62
|
+
["duplicate household review IDs", "DUPLICATE_HOUSEHOLD_REVIEW_IDS"],
|
|
63
|
+
["duplicate journal keys", "DUPLICATE_JOURNAL_KEYS"],
|
|
64
|
+
["duplicate matching enrollments", "DUPLICATE_MATCHING_ENROLLMENTS"],
|
|
65
|
+
["duplicate matching subassessment rows", "DUPLICATE_MATCHING_SUBASSESSMENT_ROWS"],
|
|
66
|
+
["duplicate semantic subassessment rows", "DUPLICATE_SEMANTIC_SUBASSESSMENT_ROWS"],
|
|
67
|
+
["duplicate subassessment key", "DUPLICATE_SUBASSESSMENT_KEY"],
|
|
68
|
+
["empty entry correction", "EMPTY_ENTRY_CORRECTION"],
|
|
69
|
+
["empty row correction", "EMPTY_ROW_CORRECTION"],
|
|
70
|
+
["end date precedes record start", "END_DATE_PRECEDES_RECORD_START"],
|
|
71
|
+
["end-date target interval missing", "END_DATE_TARGET_INTERVAL_MISSING"],
|
|
72
|
+
["end-date target missing", "END_DATE_TARGET_MISSING"],
|
|
73
|
+
["enrollment already journaled; use its existing entry key", "ENROLLMENT_ALREADY_JOURNALED_USE_ITS_EXISTING_ENTRY_KEY"],
|
|
74
|
+
["enrollment count unavailable or changed", "ENROLLMENT_COUNT_UNAVAILABLE_OR_CHANGED"],
|
|
75
|
+
["enrollment list context mismatch", "ENROLLMENT_LIST_CONTEXT_MISMATCH"],
|
|
76
|
+
["enrollment list exceeds limit", "ENROLLMENT_LIST_EXCEEDS_LIMIT"],
|
|
77
|
+
["entry and interim assessment locations required; exit location must be nonempty when supplied", "ENTRY_AND_INTERIM_ASSESSMENT_LOCATIONS_REQUIRED_EXIT_LOCATION_MUST_BE_NONEMPTY_WHEN_SUPPLIED"],
|
|
78
|
+
["entry changed since correction preflight", "ENTRY_CHANGED_SINCE_CORRECTION_PREFLIGHT"],
|
|
79
|
+
["entry correction did not verify", "ENTRY_CORRECTION_DID_NOT_VERIFY"],
|
|
80
|
+
["entry correction enrollment context mismatch", "ENTRY_CORRECTION_ENROLLMENT_CONTEXT_MISMATCH"],
|
|
81
|
+
["entry correction must precede interim or exit workflows", "ENTRY_CORRECTION_MUST_PRECEDE_INTERIM_OR_EXIT_WORKFLOWS"],
|
|
82
|
+
["entry correction requires an open enrollment without reviews", "ENTRY_CORRECTION_REQUIRES_AN_OPEN_ENROLLMENT_WITHOUT_REVIEWS"],
|
|
83
|
+
["existing exit conflicts with requested exit", "EXISTING_EXIT_CONFLICTS_WITH_REQUESTED_EXIT"],
|
|
84
|
+
["existing household review conflict", "EXISTING_HOUSEHOLD_REVIEW_CONFLICT"],
|
|
85
|
+
["exit UI and authoritative enrollment session required", "EXIT_UI_AND_AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED"],
|
|
86
|
+
["exit UI, destination and reason for leaving required", "EXIT_UI_DESTINATION_AND_REASON_FOR_LEAVING_REQUIRED"],
|
|
87
|
+
["exit assessment configuration changed", "EXIT_ASSESSMENT_CONFIGURATION_CHANGED"],
|
|
88
|
+
["exit identity changed", "EXIT_IDENTITY_CHANGED"],
|
|
89
|
+
["exit identity missing", "EXIT_IDENTITY_MISSING"],
|
|
90
|
+
["exit precedes an existing review", "EXIT_PRECEDES_AN_EXISTING_REVIEW"],
|
|
91
|
+
["exit subgroup identity changed", "EXIT_SUBGROUP_IDENTITY_CHANGED"],
|
|
92
|
+
["exit subgroup membership mismatch", "EXIT_SUBGROUP_MEMBERSHIP_MISMATCH"],
|
|
93
|
+
["explicit enrollment targets required", "EXPLICIT_ENROLLMENT_TARGETS_REQUIRED"],
|
|
94
|
+
["head and household members required", "HEAD_AND_HOUSEHOLD_MEMBERS_REQUIRED"],
|
|
95
|
+
["head client ID required", "HEAD_CLIENT_ID_REQUIRED"],
|
|
96
|
+
["head of household assessment option unresolved", "HEAD_OF_HOUSEHOLD_ASSESSMENT_OPTION_UNRESOLVED"],
|
|
97
|
+
["head of household change is a red flag; use a separate retroactive correction for a member with the same first entry date, or exit all enrollments and create a fresh enrollment", "HEAD_OF_HOUSEHOLD_CHANGE_IS_A_RED_FLAG_USE_A_SEPARATE_RETROACTIVE_CORRECTION_FOR_A_MEMBER_WITH"],
|
|
98
|
+
["household API and authoritative enrollment session required", "HOUSEHOLD_API_AND_AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED"],
|
|
99
|
+
["household enrollment context mismatch", "HOUSEHOLD_ENROLLMENT_CONTEXT_MISMATCH"],
|
|
100
|
+
["household enrollment identity changed", "HOUSEHOLD_ENROLLMENT_IDENTITY_CHANGED"],
|
|
101
|
+
["household enrollment members changed", "HOUSEHOLD_ENROLLMENT_MEMBERS_CHANGED"],
|
|
102
|
+
["household exits are not linked", "HOUSEHOLD_EXITS_ARE_NOT_LINKED"],
|
|
103
|
+
["household head mismatch; head changes are a red flag and require separate retroactive correction or a fresh enrollment", "HOUSEHOLD_HEAD_MISMATCH_HEAD_CHANGES_ARE_A_RED_FLAG_AND_REQUIRE_SEPARATE_RETROACTIVE"],
|
|
104
|
+
["household key conflicts with previous intent", "HOUSEHOLD_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
|
|
105
|
+
["household members unavailable", "HOUSEHOLD_MEMBERS_UNAVAILABLE"],
|
|
106
|
+
["household membership changed while acquiring locks; retry", "HOUSEHOLD_MEMBERSHIP_CHANGED_WHILE_ACQUIRING_LOCKS_RETRY"],
|
|
107
|
+
["household review identity changed", "HOUSEHOLD_REVIEW_IDENTITY_CHANGED"],
|
|
108
|
+
["household review membership mismatch", "HOUSEHOLD_REVIEW_MEMBERSHIP_MISMATCH"],
|
|
109
|
+
["household reviews unavailable", "HOUSEHOLD_REVIEWS_UNAVAILABLE"],
|
|
110
|
+
["household, original head and complete enrollment cohort required", "HOUSEHOLD_ORIGINAL_HEAD_AND_COMPLETE_ENROLLMENT_COHORT_REQUIRED"],
|
|
111
|
+
["household, program and supported Entry/Exit type required", "HOUSEHOLD_PROGRAM_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
|
|
112
|
+
["incomplete enrollment list", "INCOMPLETE_ENROLLMENT_LIST"],
|
|
113
|
+
["incomplete existing exit metadata", "INCOMPLETE_EXISTING_EXIT_METADATA"],
|
|
114
|
+
["independent non-application confirmation required", "INDEPENDENT_CONFIRMATION_REQUIRED"],
|
|
115
|
+
["individual enrollment context mismatch", "INDIVIDUAL_ENROLLMENT_CONTEXT_MISMATCH"],
|
|
116
|
+
["individual exit membership mismatch", "INDIVIDUAL_EXIT_MEMBERSHIP_MISMATCH"],
|
|
117
|
+
["interim did not verify", "INTERIM_DID_NOT_VERIFY"],
|
|
118
|
+
["invalid assessment plan", "INVALID_ASSESSMENT_PLAN"],
|
|
119
|
+
["invalid correction client or rows", "INVALID_CORRECTION_CLIENT_OR_ROWS"],
|
|
120
|
+
["invalid correction date", "INVALID_CORRECTION_DATE"],
|
|
121
|
+
["invalid correction effective date", "INVALID_CORRECTION_EFFECTIVE_DATE"],
|
|
122
|
+
["invalid enrollment data: adopted members require matching program and head coverage", "INVALID_ENROLLMENT_DATA_ADOPTED_MEMBERS_REQUIRE_MATCHING_PROGRAM_AND_HEAD_COVERAGE"],
|
|
123
|
+
["invalid enrollment data: enrollment has no head of household", "INVALID_ENROLLMENT_DATA_ENROLLMENT_HAS_NO_HEAD_OF_HOUSEHOLD"],
|
|
124
|
+
["invalid enrollment data: head of household relationship is missing or cleared", "INVALID_ENROLLMENT_DATA_HEAD_OF_HOUSEHOLD_RELATIONSHIP_IS_MISSING_OR_CLEARED"],
|
|
125
|
+
["invalid enrollment data: household has no active head of household", "INVALID_ENROLLMENT_DATA_HOUSEHOLD_HAS_NO_ACTIVE_HEAD_OF_HOUSEHOLD"],
|
|
126
|
+
["invalid enrollment data: member enrollment would exist without an enrolled head of household; the original head must remain enrolled through every member exit; reconcile the data or exit all members and create a fresh enrollment", "INVALID_ENROLLMENT_DATA_MEMBER_ENROLLMENT_WOULD_EXIST_WITHOUT_AN_ENROLLED_HEAD_OF_HOUSEHOLD"],
|
|
127
|
+
["invalid enrollment list", "INVALID_ENROLLMENT_LIST"],
|
|
128
|
+
["invalid household enrollment members", "INVALID_HOUSEHOLD_ENROLLMENT_MEMBERS"],
|
|
129
|
+
["invalid household journal", "INVALID_HOUSEHOLD_JOURNAL"],
|
|
130
|
+
["invalid or conflicting correction answers and clears", "INVALID_OR_CONFLICTING_CORRECTION_ANSWERS_AND_CLEARS"],
|
|
131
|
+
["invalid or duplicate end-date target", "INVALID_OR_DUPLICATE_END_DATE_TARGET"],
|
|
132
|
+
["invalid or reused subassessment ID", "INVALID_OR_REUSED_SUBASSESSMENT_ID"],
|
|
133
|
+
["invalid stored correction date", "INVALID_STORED_CORRECTION_DATE"],
|
|
134
|
+
["invalid stored correction number", "INVALID_STORED_CORRECTION_NUMBER"],
|
|
135
|
+
["invalid subassessment plan", "INVALID_SUBASSESSMENT_PLAN"],
|
|
136
|
+
["invalid workflow journal", "INVALID_WORKFLOW_JOURNAL"],
|
|
137
|
+
["key conflicts with previous intent", "KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
|
|
138
|
+
["linked household enrollment requires exitHousehold", "LINKED_HOUSEHOLD_ENROLLMENT_REQUIRES_EXITHOUSEHOLD"],
|
|
139
|
+
["mandatory correction field could not be encoded", "MANDATORY_CORRECTION_FIELD_COULD_NOT_BE_ENCODED"],
|
|
140
|
+
["mandatory correction field requires an explicit answer or clear", "MANDATORY_CORRECTION_FIELD_REQUIRES_AN_EXPLICIT_ANSWER_OR_CLEAR"],
|
|
141
|
+
["mandatory correction lookup label unavailable", "MANDATORY_CORRECTION_LOOKUP_LABEL_UNAVAILABLE"],
|
|
142
|
+
["matching correction journal required", "MATCHING_CORRECTION_JOURNAL_REQUIRED"],
|
|
143
|
+
["member already has an independent matching enrollment", "MEMBER_ALREADY_HAS_AN_INDEPENDENT_MATCHING_ENROLLMENT"],
|
|
144
|
+
["member client and assessment required", "MEMBER_CLIENT_AND_ASSESSMENT_REQUIRED"],
|
|
145
|
+
["member enrollment has not been attached", "MEMBER_ENROLLMENT_HAS_NOT_BEEN_ATTACHED"],
|
|
146
|
+
["member entry date port required", "MEMBER_ENTRY_DATE_PORT_REQUIRED"],
|
|
147
|
+
["member exit identity missing", "MEMBER_EXIT_IDENTITY_MISSING"],
|
|
148
|
+
["member is not active in household at entry", "MEMBER_IS_NOT_ACTIVE_IN_HOUSEHOLD_AT_ENTRY"],
|
|
149
|
+
["multiple end-date overrides", "MULTIPLE_END_DATE_OVERRIDES"],
|
|
150
|
+
["new member must remain enrolled", "NEW_MEMBER_MUST_REMAIN_ENROLLED"],
|
|
151
|
+
["not-applicable explanation required", "NOT_APPLICABLE_EXPLANATION_REQUIRED"],
|
|
152
|
+
["open household enrollment required to add a member", "OPEN_HOUSEHOLD_ENROLLMENT_REQUIRED_TO_ADD_A_MEMBER"],
|
|
153
|
+
["partial existing household exit requires reconciliation", "PARTIAL_EXISTING_HOUSEHOLD_EXIT_REQUIRES_RECONCILIATION"],
|
|
154
|
+
["partial household review requires reconciliation", "PARTIAL_HOUSEHOLD_REVIEW_REQUIRES_RECONCILIATION"],
|
|
155
|
+
["program and supported Entry/Exit type required", "PROGRAM_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
|
|
156
|
+
["program assessment configuration changed", "PROGRAM_ASSESSMENT_CONFIGURATION_CHANGED"],
|
|
157
|
+
["program assessment default unresolved", "PROGRAM_ASSESSMENT_DEFAULT_UNRESOLVED"],
|
|
158
|
+
["provider discovery mismatch", "PROVIDER_DISCOVERY_MISMATCH"],
|
|
159
|
+
["record already has a different end date", "RECORD_ALREADY_HAS_A_DIFFERENT_END_DATE"],
|
|
160
|
+
["resolution evidence reference changed", "RESOLUTION_EVIDENCE_REFERENCE_CHANGED"],
|
|
161
|
+
["review follows project exit", "REVIEW_FOLLOWS_PROJECT_EXIT"],
|
|
162
|
+
["review precedes entry", "REVIEW_PRECEDES_ENTRY"],
|
|
163
|
+
["row closure must fall between entry and assessment", "ROW_CLOSURE_MUST_FALL_BETWEEN_ENTRY_AND_ASSESSMENT"],
|
|
164
|
+
["saved enrollment missing", "SAVED_ENROLLMENT_MISSING"],
|
|
165
|
+
["saved enrollment missing or changed", "SAVED_ENROLLMENT_MISSING_OR_CHANGED"],
|
|
166
|
+
["stable correction key required", "STABLE_CORRECTION_KEY_REQUIRED"],
|
|
167
|
+
["stable key required", "STABLE_KEY_REQUIRED"],
|
|
168
|
+
["standard assessment writes and authoritative enrollment reader required for corrections", "STANDARD_ASSESSMENT_WRITES_AND_AUTHORITATIVE_ENROLLMENT_READER_REQUIRED_FOR_CORRECTIONS"],
|
|
169
|
+
["stored exit details conflict with requested exit", "STORED_EXIT_DETAILS_CONFLICT_WITH_REQUESTED_EXIT"],
|
|
170
|
+
["subassessment read missing", "SUBASSESSMENT_READ_MISSING"],
|
|
171
|
+
["timezone-qualified whole-second date required", "TIMEZONE_QUALIFIED_WHOLE_SECOND_DATE_REQUIRED"],
|
|
172
|
+
["uncertain entry correction requires reconciliation", "UNCERTAIN_ENTRY_CORRECTION_REQUIRES_RECONCILIATION"],
|
|
173
|
+
["uncertain household exit requires reconciliation", "UNCERTAIN_HOUSEHOLD_EXIT_REQUIRES_RECONCILIATION"],
|
|
174
|
+
["uncertain member entry date requires reconciliation", "UNCERTAIN_MEMBER_ENTRY_DATE_REQUIRES_RECONCILIATION"],
|
|
175
|
+
["uncertain write requires reconciliation", "UNCERTAIN_WRITE_REQUIRES_RECONCILIATION"],
|
|
176
|
+
["unfinished household workflow must be reconciled first", "UNFINISHED_HOUSEHOLD_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
|
|
177
|
+
["unique members including head required", "UNIQUE_MEMBERS_INCLUDING_HEAD_REQUIRED"],
|
|
178
|
+
["unknown carry-forward field", "UNKNOWN_CARRY_FORWARD_FIELD"],
|
|
179
|
+
["unknown or invalid correction fields", "UNKNOWN_OR_INVALID_CORRECTION_FIELDS"],
|
|
180
|
+
["unknown or invalid input fields", "UNKNOWN_OR_INVALID_INPUT_FIELDS"],
|
|
181
|
+
["unselected household member changed", "UNSELECTED_HOUSEHOLD_MEMBER_CHANGED"],
|
|
182
|
+
["unselected member exit identity missing", "UNSELECTED_MEMBER_EXIT_IDENTITY_MISSING"],
|
|
183
|
+
["unsupported Entry/Exit type in assessment locations", "UNSUPPORTED_ENTRY_EXIT_TYPE_IN_ASSESSMENT_LOCATIONS"],
|
|
184
|
+
["unsupported correction field type", "UNSUPPORTED_CORRECTION_FIELD_TYPE"],
|
|
185
|
+
["unsupported mandatory correction field", "UNSUPPORTED_MANDATORY_CORRECTION_FIELD"],
|
|
186
|
+
["verified correction cannot be rejected", "VERIFIED_CORRECTION_CANNOT_BE_REJECTED"],
|
|
187
|
+
["verified enrollment workflow required", "VERIFIED_ENROLLMENT_WORKFLOW_REQUIRED"],
|
|
188
|
+
["verified entry correction changed", "VERIFIED_ENTRY_CORRECTION_CHANGED"],
|
|
189
|
+
["verified exit missing", "VERIFIED_EXIT_MISSING"],
|
|
190
|
+
["verified household entry required", "VERIFIED_HOUSEHOLD_ENTRY_REQUIRED"],
|
|
191
|
+
["verified household entry with identical members required; unknown selected member", "VERIFIED_HOUSEHOLD_ENTRY_WITH_IDENTICAL_MEMBERS_REQUIRED_UNKNOWN_SELECTED_MEMBER"],
|
|
192
|
+
["verified row correction changed", "VERIFIED_ROW_CORRECTION_CHANGED"],
|
|
193
|
+
["yes/no answers cannot be cleared", "YES_NO_ANSWERS_CANNOT_BE_CLEARED"],
|
|
194
|
+
["yes/no write mode changed since this workflow started", "YES_NO_WRITE_MODE_CHANGED_SINCE_THIS_WORKFLOW_STARTED"],
|
|
195
|
+
["yesNoWrites must be mobile, or standard with writeLabel and assessments.create", "YESNOWRITES_MUST_BE_MOBILE_OR_STANDARD_WITH_WRITELABEL_AND_ASSESSMENTS_CREATE"]
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
export const workflowErrorCode = reason => codes.get(reason) ?? "WORKFLOW_FAILED";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WellSkyClient } from './generated/v3.js';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
3
|
+
type RawMobileWrite = Parameters<WellSkyClient['assessmentsMobile']['addAnswer']>[0];
|
|
4
|
+
export type CheckedMobileWriteOptions = Omit<RawMobileWrite, 'query'> & {
|
|
5
|
+
query: {
|
|
6
|
+
dateEffective: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type MobilePreflightCode = 'MOBILE_PREFLIGHT_INPUT_INVALID' | 'MOBILE_PREFLIGHT_READ_FAILED' | 'MOBILE_PREFLIGHT_UNAVAILABLE' | 'MOBILE_ANSWER_EXISTS' | 'MOBILE_PREFLIGHT_CANCELLED';
|
|
10
|
+
export declare class MobileAnswerPreflightError extends ValidationError {
|
|
11
|
+
readonly code: MobilePreflightCode;
|
|
12
|
+
readonly committed = false;
|
|
13
|
+
constructor(code: MobilePreflightCode, fields?: readonly string[], cause?: unknown);
|
|
14
|
+
}
|
|
15
|
+
/** Read-before-write protection for scalar inserts, including questions shared by instruments.
|
|
16
|
+
* Does not journal, retry writes, or make the read/write pair atomic. */
|
|
17
|
+
export declare function writeMobileAnswersChecked(api: Pick<WellSkyClient, 'assessments' | 'assessmentsMobile'>, options: CheckedMobileWriteOptions): ReturnType<WellSkyClient['assessmentsMobile']['addAnswer']>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=mobile-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-preflight.d.ts","sourceRoot":"","sources":["../../src/mobile-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAY,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAK5C,KAAK,cAAc,GAAG,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,cAAc,EAAC,OAAO,CAAC,GAAG;IAAC,KAAK,EAAC;QAAC,aAAa,EAAC,MAAM,CAAA;KAAC,CAAA;CAAC,CAAC;AACtG,MAAM,MAAM,mBAAmB,GAAG,gCAAgC,GAAC,8BAA8B,GAAC,8BAA8B,GAAC,sBAAsB,GAAC,4BAA4B,CAAC;AACrL,qBAAa,0BAA2B,SAAQ,eAAe;IAElD,QAAQ,CAAC,IAAI,EAAC,mBAAmB;IAD7C,QAAQ,CAAC,SAAS,SAAO;gBACJ,IAAI,EAAC,mBAAmB,EAAC,MAAM,GAAC,SAAS,MAAM,EAAK,EAAC,KAAK,CAAC,EAAC,OAAO;CAIxF;AAED;yEACyE;AACzE,wBAAsB,yBAAyB,CAC9C,GAAG,EAAC,IAAI,CAAC,aAAa,EAAC,aAAa,GAAC,mBAAmB,CAAC,EACzD,OAAO,EAAC,yBAAyB,GAChC,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAkC5D"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MobileAnswerPreflightError = void 0;
|
|
4
|
+
exports.writeMobileAnswersChecked = writeMobileAnswersChecked;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const encoding_js_1 = require("./encoding.js");
|
|
7
|
+
const runtime_js_1 = require("./runtime.js");
|
|
8
|
+
const validation_js_1 = require("./validation.js");
|
|
9
|
+
class MobileAnswerPreflightError extends errors_js_1.ValidationError {
|
|
10
|
+
code;
|
|
11
|
+
committed = false;
|
|
12
|
+
constructor(code, fields = [], cause) {
|
|
13
|
+
super('mobile assessment preflight', fields);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.name = 'MobileAnswerPreflightError';
|
|
16
|
+
if (cause !== undefined)
|
|
17
|
+
Object.defineProperty(this, 'cause', { value: cause, enumerable: false });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.MobileAnswerPreflightError = MobileAnswerPreflightError;
|
|
21
|
+
/** Read-before-write protection for scalar inserts, including questions shared by instruments.
|
|
22
|
+
* Does not journal, retry writes, or make the read/write pair atomic. */
|
|
23
|
+
async function writeMobileAnswersChecked(api, options) {
|
|
24
|
+
function fail(code, fields = [], cause) { throw new MobileAnswerPreflightError(code, fields, cause); }
|
|
25
|
+
const checkSignal = () => { if (options.signal?.aborted)
|
|
26
|
+
fail('MOBILE_PREFLIGHT_CANCELLED', [], options.signal.reason); };
|
|
27
|
+
if (!options?.path || !Number.isSafeInteger(options.path.clientId) || options.path.clientId <= 0 || typeof options.path.assessmentCode !== 'string' || !options.path.assessmentCode.trim() || !Array.isArray(options.body) || !options.body.length || typeof options.query?.dateEffective !== 'string')
|
|
28
|
+
fail('MOBILE_PREFLIGHT_INPUT_INVALID');
|
|
29
|
+
const instant = options.query.dateEffective;
|
|
30
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.000)?(?:Z|[+-]\d{2}:\d{2})$/.test(instant) || !Number.isFinite((0, encoding_js_1.parseRestInstant)(instant)) || new Date(instant.slice(0, 19) + 'Z').toISOString().slice(0, 19) !== instant.slice(0, 19))
|
|
31
|
+
fail('MOBILE_PREFLIGHT_INPUT_INVALID', ['dateEffective']);
|
|
32
|
+
checkSignal();
|
|
33
|
+
const captured = { ...options, path: { ...options.path }, query: { ...options.query }, body: structuredClone(options.body) };
|
|
34
|
+
const scalars = [], seen = new Set(), ids = new Set();
|
|
35
|
+
for (const q of captured.body) {
|
|
36
|
+
if (!(0, validation_js_1.isRecord)(q) || typeof q.question_type !== 'string' || typeof q.virt_field_name !== 'string' || !q.virt_field_name || typeof q.question_id !== 'number' || !Number.isSafeInteger(q.question_id) || q.question_id <= 0 || seen.has(q.virt_field_name) || ids.has(q.question_id))
|
|
37
|
+
fail('MOBILE_PREFLIGHT_INPUT_INVALID');
|
|
38
|
+
seen.add(q.virt_field_name);
|
|
39
|
+
ids.add(q.question_id);
|
|
40
|
+
if (q.question_type !== 'sub_assessment')
|
|
41
|
+
scalars.push({ id: q.question_id, field: q.virt_field_name, type: q.question_type });
|
|
42
|
+
}
|
|
43
|
+
if (scalars.length) {
|
|
44
|
+
let response;
|
|
45
|
+
try {
|
|
46
|
+
response = await api.assessments.get({ path: captured.path, query: { dateEffective: new Date((0, encoding_js_1.parseRestInstant)(instant) + 1000).toISOString() }, ...(options.signal ? { signal: options.signal } : {}), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }) });
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
fail('MOBILE_PREFLIGHT_READ_FAILED', scalars.map(q => q.field), cause);
|
|
50
|
+
}
|
|
51
|
+
const rows = (0, runtime_js_1.toItems)(response), conflicts = [];
|
|
52
|
+
for (const q of scalars) {
|
|
53
|
+
const found = rows.filter(r => r.virt_field_name === q.field);
|
|
54
|
+
if (found.length !== 1 || found[0]?.question_id !== q.id || found[0]?.question_type !== q.type)
|
|
55
|
+
fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
|
|
56
|
+
const row = found[0];
|
|
57
|
+
if (!row)
|
|
58
|
+
fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
|
|
59
|
+
if (row.date_effective !== null && row.date_effective !== undefined && row.date_effective !== '') {
|
|
60
|
+
const recorded = (0, encoding_js_1.parseRestInstant)(row.date_effective);
|
|
61
|
+
if (!Number.isFinite(recorded) || recorded > (0, encoding_js_1.parseRestInstant)(instant))
|
|
62
|
+
fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
|
|
63
|
+
if (recorded === (0, encoding_js_1.parseRestInstant)(instant))
|
|
64
|
+
conflicts.push(q.field);
|
|
65
|
+
}
|
|
66
|
+
else if (row.answer_value !== null && row.answer_value !== undefined && row.answer_value !== '')
|
|
67
|
+
fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
|
|
68
|
+
}
|
|
69
|
+
if (conflicts.length)
|
|
70
|
+
fail('MOBILE_ANSWER_EXISTS', conflicts);
|
|
71
|
+
}
|
|
72
|
+
checkSignal();
|
|
73
|
+
return api.assessmentsMobile.addAnswer(captured);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=mobile-preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-preflight.js","sourceRoot":"","sources":["../../src/mobile-preflight.ts"],"names":[],"mappings":"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","sourcesContent":["import type {WellSkyClient,Operations} from './generated/v3.js';\nimport {ValidationError} from './errors.js';\nimport {parseRestInstant} from './encoding.js';\nimport {toItems} from './runtime.js';\nimport {isRecord} from './validation.js';\n\ntype RawMobileWrite = Parameters<WellSkyClient['assessmentsMobile']['addAnswer']>[0];\nexport type CheckedMobileWriteOptions = Omit<RawMobileWrite,'query'> & {query:{dateEffective:string}};\nexport type MobilePreflightCode = 'MOBILE_PREFLIGHT_INPUT_INVALID'|'MOBILE_PREFLIGHT_READ_FAILED'|'MOBILE_PREFLIGHT_UNAVAILABLE'|'MOBILE_ANSWER_EXISTS'|'MOBILE_PREFLIGHT_CANCELLED';\nexport class MobileAnswerPreflightError extends ValidationError {\n readonly committed=false;\n constructor(readonly code:MobilePreflightCode,fields:readonly string[]=[],cause?:unknown){\n super('mobile assessment preflight',fields);this.name='MobileAnswerPreflightError';\n if(cause!==undefined)Object.defineProperty(this,'cause',{value:cause,enumerable:false});\n }\n}\n\n/** Read-before-write protection for scalar inserts, including questions shared by instruments.\n * Does not journal, retry writes, or make the read/write pair atomic. */\nexport async function writeMobileAnswersChecked(\n api:Pick<WellSkyClient,'assessments'|'assessmentsMobile'>,\n options:CheckedMobileWriteOptions,\n):ReturnType<WellSkyClient['assessmentsMobile']['addAnswer']>{\n function fail(code:MobilePreflightCode,fields:readonly string[]=[],cause?:unknown):never{throw new MobileAnswerPreflightError(code,fields,cause);}\n const checkSignal=()=>{if(options.signal?.aborted)fail('MOBILE_PREFLIGHT_CANCELLED',[],options.signal.reason);};\n if(!options?.path||!Number.isSafeInteger(options.path.clientId)||options.path.clientId<=0||typeof options.path.assessmentCode!=='string'||!options.path.assessmentCode.trim()||!Array.isArray(options.body)||!options.body.length||typeof options.query?.dateEffective!=='string')fail('MOBILE_PREFLIGHT_INPUT_INVALID');\n const instant=options.query.dateEffective;\n if(!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.000)?(?:Z|[+-]\\d{2}:\\d{2})$/.test(instant)||!Number.isFinite(parseRestInstant(instant))||new Date(instant.slice(0,19)+'Z').toISOString().slice(0,19)!==instant.slice(0,19))fail('MOBILE_PREFLIGHT_INPUT_INVALID',['dateEffective']);\n checkSignal();\n const captured={...options,path:{...options.path},query:{...options.query},body:structuredClone(options.body)};\n const scalars:Array<{id:number;field:string;type:string}>=[],seen=new Set<string>(),ids=new Set<number>();\n for(const q of captured.body){\n if(!isRecord(q)||typeof q.question_type!=='string'||typeof q.virt_field_name!=='string'||!q.virt_field_name||typeof q.question_id!=='number'||!Number.isSafeInteger(q.question_id)||q.question_id<=0||seen.has(q.virt_field_name)||ids.has(q.question_id))fail('MOBILE_PREFLIGHT_INPUT_INVALID');\n seen.add(q.virt_field_name);ids.add(q.question_id);\n if(q.question_type!=='sub_assessment')scalars.push({id:q.question_id,field:q.virt_field_name,type:q.question_type});\n }\n if(scalars.length){\n let response:Awaited<ReturnType<WellSkyClient['assessments']['get']>>;\n try{response=await api.assessments.get({path:captured.path,query:{dateEffective:new Date(parseRestInstant(instant)+1000).toISOString()},...(options.signal?{signal:options.signal}:{}),...(options.timeoutMs===undefined?{}:{timeoutMs:options.timeoutMs}),...(options.correlationId===undefined?{}:{correlationId:options.correlationId})});}\n catch(cause){fail('MOBILE_PREFLIGHT_READ_FAILED',scalars.map(q=>q.field),cause);}\n const rows=toItems<Operations['GET /assessments/{clientId}/{assessmentCode}']['item']>(response),conflicts:string[]=[];\n for(const q of scalars){\n const found=rows.filter(r=>r.virt_field_name===q.field);\n if(found.length!==1||found[0]?.question_id!==q.id||found[0]?.question_type!==q.type)fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n const row=found[0];\n if(!row)fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n if(row.date_effective!==null&&row.date_effective!==undefined&&row.date_effective!==''){\n const recorded=parseRestInstant(row.date_effective);\n if(!Number.isFinite(recorded)||recorded>parseRestInstant(instant))fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n if(recorded===parseRestInstant(instant))conflicts.push(q.field);\n }else if(row.answer_value!==null&&row.answer_value!==undefined&&row.answer_value!=='')fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n }\n if(conflicts.length)fail('MOBILE_ANSWER_EXISTS',conflicts);\n }\n checkSignal();\n return api.assessmentsMobile.addAnswer(captured);\n}\n"]}
|
package/dist/cjs/v2.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export * from "./subassessments.js";
|
|
|
10
10
|
export * from "./enrollment-verifications.js";
|
|
11
11
|
export * from "./gwt-enrollments.js";
|
|
12
12
|
export * from "./program-discovery.js";
|
|
13
|
+
export * from "./mobile-preflight.js";
|
|
14
|
+
export * from "./write-labels.js";
|
|
13
15
|
//# sourceMappingURL=v2.d.ts.map
|
package/dist/cjs/v2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
|
package/dist/cjs/v2.js
CHANGED
|
@@ -26,4 +26,6 @@ __exportStar(require("./subassessments.js"), exports);
|
|
|
26
26
|
__exportStar(require("./enrollment-verifications.js"), exports);
|
|
27
27
|
__exportStar(require("./gwt-enrollments.js"), exports);
|
|
28
28
|
__exportStar(require("./program-discovery.js"), exports);
|
|
29
|
+
__exportStar(require("./mobile-preflight.js"), exports);
|
|
30
|
+
__exportStar(require("./write-labels.js"), exports);
|
|
29
31
|
//# sourceMappingURL=v2.js.map
|
package/dist/cjs/v2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.js","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC","sourcesContent":["export * from \"./generated/v2.js\";\nexport * from \"./generated/v2-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"v2.js","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC;AACvC,wDAAsC;AACtC,oDAAkC","sourcesContent":["export * from \"./generated/v2.js\";\nexport * from \"./generated/v2-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\nexport * from \"./mobile-preflight.js\";\nexport * from \"./write-labels.js\";\n"]}
|
package/dist/cjs/v3.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export * from "./subassessments.js";
|
|
|
10
10
|
export * from "./enrollment-verifications.js";
|
|
11
11
|
export * from "./gwt-enrollments.js";
|
|
12
12
|
export * from "./program-discovery.js";
|
|
13
|
+
export * from "./mobile-preflight.js";
|
|
14
|
+
export * from "./write-labels.js";
|
|
13
15
|
//# sourceMappingURL=v3.d.ts.map
|
package/dist/cjs/v3.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
|
package/dist/cjs/v3.js
CHANGED
|
@@ -26,4 +26,6 @@ __exportStar(require("./subassessments.js"), exports);
|
|
|
26
26
|
__exportStar(require("./enrollment-verifications.js"), exports);
|
|
27
27
|
__exportStar(require("./gwt-enrollments.js"), exports);
|
|
28
28
|
__exportStar(require("./program-discovery.js"), exports);
|
|
29
|
+
__exportStar(require("./mobile-preflight.js"), exports);
|
|
30
|
+
__exportStar(require("./write-labels.js"), exports);
|
|
29
31
|
//# sourceMappingURL=v3.js.map
|
package/dist/cjs/v3.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3.js","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC","sourcesContent":["export * from \"./generated/v3.js\";\nexport * from \"./generated/v3-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"v3.js","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC;AACvC,wDAAsC;AACtC,oDAAkC","sourcesContent":["export * from \"./generated/v3.js\";\nexport * from \"./generated/v3-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\nexport * from \"./mobile-preflight.js\";\nexport * from \"./write-labels.js\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssessmentDefinition } from './metadata.js';
|
|
2
|
+
import type { EncodingOptions } from './encoding.js';
|
|
3
|
+
export interface WriteLabelObservation {
|
|
4
|
+
tenant: string;
|
|
5
|
+
response: unknown;
|
|
6
|
+
}
|
|
7
|
+
export type ReadBackWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {
|
|
8
|
+
readonly tenant: string;
|
|
9
|
+
};
|
|
10
|
+
/** Builds labels only from exact readback evidence. It never infers HUD suffixes.
|
|
11
|
+
* Unobserved or changed options return undefined, so the standard encoder refuses them. */
|
|
12
|
+
export declare function createReadBackWriteLabelResolver(definition: AssessmentDefinition, observations: readonly WriteLabelObservation[]): ReadBackWriteLabelResolver;
|
|
13
|
+
//# sourceMappingURL=write-labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-labels.d.ts","sourceRoot":"","sources":["../../src/write-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAmC,MAAM,eAAe,CAAC;AAE1F,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAInD,MAAM,WAAW,qBAAqB;IAAE,MAAM,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,OAAO,CAAC;CAAC;AACxE,MAAM,MAAM,0BAA0B,GAAG,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,GAAG;IAAC,QAAQ,CAAC,MAAM,EAAC,MAAM,CAAA;CAAC,CAAC;AAE/G;2FAC2F;AAC3F,wBAAgB,gCAAgC,CAC/C,UAAU,EAAC,oBAAoB,EAC/B,YAAY,EAAC,SAAS,qBAAqB,EAAE,GAC5C,0BAA0B,CA2C3B"}
|