@choiceapps/communityservices-sdk 0.7.0 → 0.8.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 - 2026-11-11
4
+
5
+ - Resolve full lookup write labels without existing answers using `enrollmentSession.readPicklistOptions` and `createCatalogWriteLabelResolver`.
6
+ - Discover labels automatically in enrollment workflows when the configured session supports catalog reads and no explicit `writeLabel` override is supplied, including corrections and mandatory carry-forward.
7
+ - Match option ID, picklist ID and code; preserve full names including HUD suffixes, and reject incomplete or mismatched catalogs before writes. Read explicit option IDs in bounded batches.
8
+ - Read-only training verification matched 1,288 distinct catalog options to Coordinated Entry metadata from three synthetic clients, including the shared enhanced yes/no list and living-situation options. Live profile dropdowns matched the DOB-quality catalog labels. No new assessment writes were performed.
9
+
3
10
  ## 0.7.0 - 2026-11-10
4
11
 
5
12
  - Add `createReadBackWriteLabelResolver` for exact observed lookup labels, including HUD suffixes. Unobserved options remain unresolved; no suffix is inferred from metadata.
@@ -1,5 +1,5 @@
1
1
  import type {DateOfBirth} from './date-of-birth.mjs';
2
- import type {Answers, AssessmentDefinition, EncodingOptions, GwtEnrollmentHelpers, Provider, WellSkyClient} from '../dist/esm/index.js';
2
+ import type {PicklistCatalogSnapshot, Answers, AssessmentDefinition, EncodingOptions, GwtEnrollmentHelpers, Provider, WellSkyClient} from '../dist/esm/index.js';
3
3
  import type {UiMutationStore,EnrollmentReader} from './index.mjs';
4
4
  import type {ProviderAssessmentItem, ProgramDiscovery} from './program-discovery.mjs';
5
5
  import type {HouseholdExitUi,HouseholdExitDetailInput} from './household-exit.mjs';
@@ -41,6 +41,8 @@ export interface EnrollmentDiscoveryInput {
41
41
  entryDate:string;
42
42
  }
43
43
  export interface EnrollmentWorkflowDiscovery {
44
+ /** Current exact label catalog when automatic browser discovery is available. */
45
+ picklistOptions?:PicklistCatalogSnapshot;
44
46
  provider:Provider;
45
47
  entry:{assessment:ProviderAssessmentItem;definition:AssessmentDefinition};
46
48
  interim:{assessment:ProviderAssessmentItem;definition:AssessmentDefinition};
@@ -254,7 +256,7 @@ export interface EnrollmentWorkflowOptions {
254
256
  * `standard` writes them through `assessments.create` with every mandatory answer, after the mobile scalars;
255
257
  * every mandatory question must be a lookup. An unfinished journal never reconciles in the other mode. */
256
258
  yesNoWrites?:'mobile'|'standard';
257
- /** Required for `yesNoWrites: 'standard'`: tenant-verified labels for mandatory lookup answers. */
259
+ /** Explicit tenant-verified override. When omitted, a configured readPicklistOptions reader supplies exact catalog labels automatically. */
258
260
  writeLabel?:EncodingOptions['writeLabel'];
259
261
  }
260
262
  export class EnrollmentWorkflowError extends Error {
@@ -6,7 +6,7 @@ import {createEntryRowRegistrar} from './entry-row-registration.mjs';
6
6
  import {createHouseholdEnrollmentDiscovery} from './household-enrollment-discovery.mjs';
7
7
  import {workflowErrorCode} from './workflow-error-codes.mjs';
8
8
  import {createHash} from 'node:crypto';
9
- import {readProgramCatalog,encodeAnswers,encodeNewSubassessmentRow,toItems,parseRestInstant} from '../dist/esm/index.js';
9
+ import {readProgramCatalog,flattenQuestions,createCatalogWriteLabelResolver,encodeAnswers,encodeNewSubassessmentRow,toItems,parseRestInstant} from '../dist/esm/index.js';
10
10
 
11
11
  const object=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
12
12
  const positive=v=>Number.isSafeInteger(v)&&v>0;
@@ -128,7 +128,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
128
128
  }
129
129
  };
130
130
  if(typeof discovery?.getProviderAssessments!=='function'||typeof helpers?.createInterimUpdate!=='function'||!store)fail('REST API, discovery, enrollment helpers and durable store required');
131
- if(!['mobile','standard'].includes(yesNoWrites)||yesNoWrites==='standard'&&(typeof writeLabel!=='function'||typeof api.assessments?.create!=='function'))fail('yesNoWrites must be mobile, or standard with writeLabel and assessments.create');
131
+ if(!['mobile','standard'].includes(yesNoWrites)||yesNoWrites==='standard'&&(typeof writeLabel!=='function'&&typeof enrollmentSession?.readPicklistOptions!=='function'||typeof api.assessments?.create!=='function'))fail('yesNoWrites must be mobile, or standard with writeLabel and assessments.create');
132
132
  if(!object(assessmentLocations))fail('assessment locations must be a map by Entry/Exit type');
133
133
  for(const [type,locations]of Object.entries(assessmentLocations)){
134
134
  if(!entryExitTypes.includes(type))fail('unsupported Entry/Exit type in assessment locations');
@@ -149,7 +149,14 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
149
149
  if(definition.tenant!==api.tenant||definition.assessmentCode!==assessment.code||definition.unresolvedPicklists.length)fail('assessment metadata incomplete');
150
150
  resolved[phase]={assessment,definition};
151
151
  }
152
- return {provider:providers[0],...resolved};
152
+ let picklistOptions;
153
+ if(writeLabel===undefined&&typeof enrollmentSession?.readPicklistOptions==='function'){
154
+ const optionIds=[...new Set(Object.values(resolved).flatMap(({definition})=>flattenQuestions(definition.questions).filter(({question})=>question.questionType==='lookup').flatMap(({question})=>question.options.map(o=>o.id))))];
155
+ picklistOptions=await enrollmentSession.readPicklistOptions({clientId:input.clientId,optionIds});
156
+ if(picklistOptions?.clientId!==input.clientId)fail('picklist catalog client mismatch',{code:'PICKLIST_CATALOG_CONTEXT_MISMATCH'});
157
+ for(const {definition}of Object.values(resolved))createCatalogWriteLabelResolver(definition,picklistOptions);
158
+ }
159
+ return {provider:providers[0],...resolved,...(picklistOptions?{picklistOptions}:{})};
153
160
  };
154
161
  const scope=clientId=>JSON.stringify([tenantScope,'enrollment-workflow',clientId]);
155
162
  const reservationScope=clientId=>JSON.stringify([tenantScope,'household-workflow-reservation',clientId]);
@@ -210,7 +217,8 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
210
217
  const definition=discovered[kind].definition,code=definition.assessmentCode,at=kind==='entry'?input.entryDate:(input.exitDate??input.reviewDate);
211
218
  const exitSchemaHash=kind==='exit'?hash({assessment:discovered.exit.assessment,questions:definition.questions}):undefined;
212
219
  if(record&&kind==='exit'&&record.exitSchemaHash!==exitSchemaHash)fail('exit assessment configuration changed');
213
- const plan=compile(definition,input.assessment,{yesNoWrites,writeLabel});
220
+ const operationWriteLabel=writeLabel??(discovered.picklistOptions?createCatalogWriteLabelResolver(definition,discovered.picklistOptions):undefined);
221
+ const plan=compile(definition,input.assessment,{yesNoWrites,writeLabel:operationWriteLabel});
214
222
  const carriesBirthDate=plan.carry.includes('SVPPROFDOB'),hasBirthDate=plan.dateOfBirth!==undefined||carriesBirthDate;
215
223
  let carriedBirthDate;
216
224
  if(hasBirthDate){
@@ -266,7 +274,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
266
274
  const recorded=initial.find(q=>q.virt_field_name===m.virt_field_name)?.answer_value;
267
275
  if(m.carry){
268
276
  const question=definition.questions.find(q=>q.fieldName===m.virt_field_name),options=question.options.filter(o=>String(o.id)===String(recorded));
269
- const label=options.length===1?writeLabel(question,options[0]):undefined;
277
+ const label=options.length===1?operationWriteLabel?.(question,options[0]):undefined;
270
278
  if(!label)fail(`carried mandatory answer ${m.virt_field_name} has no verified write label`,{code:'LOOKUP_WRITE_LABEL_UNAVAILABLE',fields:[m.virt_field_name]});
271
279
  if(m.virt_field_name==='HUD_RELATIONTOHOH')checkHeadAnswer(recorded);
272
280
  return {question_id:m.question_id,question_type:m.question_type,virt_field_name:m.virt_field_name,answer_value:label,expected:String(recorded)};
@@ -1,5 +1,5 @@
1
1
  import {createHash} from 'node:crypto';
2
- import {encodeAnswers,parseRestInstant,toItems} from '../dist/esm/index.js';
2
+ import {createCatalogWriteLabelResolver,encodeAnswers,parseRestInstant,toItems} from '../dist/esm/index.js';
3
3
 
4
4
  const object=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
5
5
  const canonical=v=>JSON.stringify(v,(_,x)=>object(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
@@ -24,7 +24,7 @@ export function createEntryCorrector({api,store,enrollmentSession,writeLabel,dis
24
24
  if(type==='int'||type==='money'){const n=Number(v);if(!Number.isFinite(n))fail('invalid stored correction number');return n;}
25
25
  return String(v);
26
26
  };
27
- const encode=(questions,answers,clear,endpoint)=>{
27
+ const encode=(questions,answers,clear,endpoint,writeLabel)=>{
28
28
  if(!object(answers)||!Array.isArray(clear)||new Set(clear).size!==clear.length||clear.some(f=>typeof f!=='string'||Object.hasOwn(answers,f)))fail('invalid or conflicting correction answers and clears');
29
29
  const index=new Map(questions.map(q=>[q.fieldName,q]));
30
30
  const unsupportedClears=clear.filter(field=>index.get(field)?.questionType==='yes_no');
@@ -94,7 +94,7 @@ export function createEntryCorrector({api,store,enrollmentSession,writeLabel,dis
94
94
  const discovered=await discover({clientId:input.clientId,providerId,type,entryDate:at});
95
95
  const schemaHash=hash({providerId:discovered.provider.providerId,entry:{assessment:discovered.entry.assessment,questions:discovered.entry.definition.questions},interim:{assessment:discovered.interim.assessment,questions:discovered.interim.definition.questions}});
96
96
  if(schemaHash!==entry.schemaHash||record&&schemaHash!==record.schemaHash)fail('program assessment configuration changed');
97
- const definition=discovered.entry.definition,code=definition.assessmentCode;
97
+ const definition=discovered.entry.definition,operationWriteLabel=writeLabel??(discovered.picklistOptions?createCatalogWriteLabelResolver(discovered.entry.definition,discovered.picklistOptions):undefined),code=definition.assessmentCode;
98
98
  const snapshot=async()=>{
99
99
  const raw=await readQuestions(input.clientId,code,readAt),current=await readQuestions(input.clientId,code,new Date(Math.max(Date.now(),parseRestInstant(readAt))).toISOString());
100
100
  const scalars={},rows={};
@@ -145,7 +145,7 @@ export function createEntryCorrector({api,store,enrollmentSession,writeLabel,dis
145
145
  if(!record){
146
146
  readOverrides(observed);
147
147
  const answers=input.answers??{},clear=input.clear??[];
148
- const scalarBody=encode(definition.questions,answers,clear,'standalone'),scalarExpected=expected(definition.questions,scalarBody,answers);
148
+ const scalarBody=encode(definition.questions,answers,clear,'standalone',operationWriteLabel),scalarExpected=expected(definition.questions,scalarBody,answers);
149
149
  // The standard endpoint requires every mandatory scalar, even if this patch leaves it unchanged.
150
150
  if(scalarBody.length)for(const q of definition.questions.filter(q=>q.mandatory&&!['label','blank','sub_assessment'].includes(q.questionType)&&!scalarBody.some(c=>c.virt_field_name===q.fieldName))){
151
151
  if(q.fieldName==='SVPPROFDOB'||!scalarTypes.has(q.questionType))fail('unsupported mandatory correction field');
@@ -153,10 +153,10 @@ export function createEntryCorrector({api,store,enrollmentSession,writeLabel,dis
153
153
  if(prior?.answer_value===null||prior?.answer_value===undefined||prior?.answer_value==='')fail('mandatory correction field requires an explicit answer or clear');
154
154
  let body;
155
155
  if(q.questionType==='lookup'){
156
- const options=q.options.filter(o=>String(o.id)===String(prior.answer_value)),label=options.length===1?writeLabel?.(q,options[0]):undefined;
156
+ const options=q.options.filter(o=>String(o.id)===String(prior.answer_value)),label=options.length===1?operationWriteLabel?.(q,options[0]):undefined;
157
157
  if(!label)fail('mandatory correction lookup label unavailable');
158
158
  body=[{question_id:q.questionId,question_type:q.questionType,virt_field_name:q.fieldName,answer_value:label}];
159
- }else body=encodeAnswers([q],{[q.fieldName]:{value:prior.answer_value}},{endpoint:'standalone',writeLabel});
159
+ }else body=encodeAnswers([q],{[q.fieldName]:{value:prior.answer_value}},{endpoint:'standalone',writeLabel:operationWriteLabel});
160
160
  if(body.length!==1)fail('mandatory correction field could not be encoded');scalarBody.push(body[0]);
161
161
  scalarExpected.push({...body[0],answer_value:prior.answer_value});
162
162
  }
@@ -165,7 +165,7 @@ export function createEntryCorrector({api,store,enrollmentSession,writeLabel,dis
165
165
  fields(patch,['key','parentField','answers','clear']);key(patch.key);
166
166
  const id=entry.rows[patch.key],parent=definition.questions.find(q=>q.fieldName===patch.parentField&&q.questionType==='sub_assessment');
167
167
  if(!positive(id)||ids.has(id)||!parent||!observed.rows[parent.fieldName]?.[id]||entry.rowParents?.[patch.key]&&entry.rowParents[patch.key]!==patch.parentField)fail('correction requires an existing entry row key and matching parent');ids.add(id);
168
- const cells=encode(parent.children,patch.answers??{},patch.clear??[],'mobile');if(!cells.length)fail('empty row correction');
168
+ const cells=encode(parent.children,patch.answers??{},patch.clear??[],'mobile',operationWriteLabel);if(!cells.length)fail('empty row correction');
169
169
  rowPlans.push({id,parentField:parent.fieldName,questionId:parent.questionId,cells});
170
170
  }
171
171
  if(!scalarBody.length&&!rowPlans.length)fail('empty entry correction');
@@ -1,6 +1,6 @@
1
1
  import type {UiConnection,UiConnectionOptions} from './connection.mjs';
2
2
  import type {Browser, BrowserContext, BrowserType, Page} from 'playwright-core';
3
- import type {GwtEnrollmentExit,GwtEnrollmentHelpers,GwtEnrollmentSession,GwtEnrollmentSnapshot,GwtMutationStore} from '../dist/esm/index.js';
3
+ import type {PicklistCatalogSnapshot,GwtEnrollmentExit,GwtEnrollmentHelpers,GwtEnrollmentSession,GwtEnrollmentSnapshot,GwtMutationStore} from '../dist/esm/index.js';
4
4
  export interface LoginOptions extends UiConnectionOptions {
5
5
  chromium: BrowserType;
6
6
  envFile?: string;
@@ -29,12 +29,15 @@ export interface EnrollmentListSnapshot {
29
29
  }
30
30
  export interface EnrollmentReader {
31
31
  readonly connection?:UiConnection;
32
+ /** Exact full names for explicit REST option IDs; does not read existing answers. */
33
+ readPicklistOptions?(target:{clientId:number;optionIds:readonly number[]}):Promise<PicklistCatalogSnapshot>;
32
34
  readEnrollment(target:{clientId:number;enrollmentId:number}):Promise<PlaywrightEnrollmentSnapshot>;
33
35
  /** Built-in sessions supply this complete, counted list, including exited entries.
34
36
  * Legacy custom readers without it retain REST list behavior. */
35
37
  listEnrollments?(target:{clientId:number}):Promise<EnrollmentListSnapshot>;
36
38
  }
37
39
  export interface EnrollmentCatalogReader extends EnrollmentReader {
40
+ readPicklistOptions(target:{clientId:number;optionIds:readonly number[]}):Promise<PicklistCatalogSnapshot>;
38
41
  listEnrollments(target:{clientId:number}):Promise<EnrollmentListSnapshot>;
39
42
  }
40
43
  export function openUiSession(options:LoginOptions):Promise<UiSession>;
package/browser/index.mjs CHANGED
@@ -10,7 +10,7 @@ export async function openEnrollmentSession({store,...loginOptions}) {
10
10
  try {
11
11
  const helpers=createPlaywrightEnrollmentHelpers({page:session.page,store,...session.connection});
12
12
  const reader=createPlaywrightGwtSession({page:session.page,...session.connection});
13
- const enrollmentSession={connection:session.connection,readEnrollment:target=>withPageLock(session.page,()=>reader.readEnrollment(target)),listEnrollments:target=>withPageLock(session.page,()=>reader.listEnrollments(target))};
13
+ const enrollmentSession={connection:session.connection,readEnrollment:target=>withPageLock(session.page,()=>reader.readEnrollment(target)),listEnrollments:target=>withPageLock(session.page,()=>reader.listEnrollments(target)),readPicklistOptions:target=>withPageLock(session.page,()=>reader.readPicklistOptions(target))};
14
14
  return Object.assign(session,{helpers,enrollmentSession,workflows:createClientWorkflows({page:session.page,store})});
15
15
  }catch(error){await session.close();throw error;}
16
16
  }
@@ -84,10 +84,10 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
84
84
  try{const r=await fetch(url,{method:'POST',headers,body,credentials:'same-origin',redirect:'error',signal:controller.signal});return {status:r.status,body:await r.text()};}finally{clearTimeout(timer);}
85
85
  },request);
86
86
  };
87
- const mapped=async query=>{
87
+ const mapped=async(query,limit=1001)=>{
88
88
  if(!wire||!/^SELECT /.test(query)||/[|\\\r\n]/.test(query))throw Error('Invalid mapped read');
89
- const strings=[moduleBaseUrl,wire.policy,rpcInterface,'getMappedResults','java.lang.String/2004016611','java.util.HashMap/1797211028','I','Z',query+' LIMIT 1001,0'];
90
- const body=['7','0',strings.length,...strings,1,2,3,4,5,5,6,7,7,8,9,0,0,1001,1,''].join('|');
89
+ const strings=[moduleBaseUrl,wire.policy,rpcInterface,'getMappedResults','java.lang.String/2004016611','java.util.HashMap/1797211028','I','Z',query+` LIMIT ${limit},0`];
90
+ const body=['7','0',strings.length,...strings,1,2,3,4,5,5,6,7,7,8,9,0,0,limit,1,''].join('|');
91
91
  const response=await post({url:moduleBaseUrl+'service/MetaQueryService',headers:wire.headers,body});if(response.status!==200)throw Error('Mapped read failed');
92
92
  const data=decodeMappedResponse(response.body);if(data.total!==data.rows.length||data.total>1000)throw Error('Incomplete enrollment list');return data.rows;
93
93
  };
@@ -100,7 +100,7 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
100
100
  if(ids.some(n=>!Number.isSafeInteger(n)||n<=0))throw Error('Invalid household tree');
101
101
  await closeDialogs();return [...new Set([target.clientId,...ids])].sort((a,b)=>a-b);
102
102
  };
103
- const listEnrollments=async({clientId})=>{
103
+ const openReadContext=async clientId=>{
104
104
  id(clientId);await guard();wire=undefined;let captured;
105
105
  const listener=request=>{
106
106
  if(request.url()===moduleBaseUrl+'service/MetaQueryService'&&request.postData()?.startsWith('7|0|')){
@@ -111,6 +111,9 @@ 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
+ };
115
+ const listEnrollments=async({clientId})=>{
116
+ await openReadContext(clientId);
114
117
  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
118
  const ids=new Set();
116
119
  for(const row of rows){
@@ -120,6 +123,18 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
120
123
  }
121
124
  return {source:'gwt-rpc',tenant,clientId,count:rows.length,values:rows.map(row=>({...row,isActive:true,grouped:row.groupId!==null}))};
122
125
  };
126
+ const readPicklistOptions=async({clientId,optionIds})=>{
127
+ id(clientId);if(!Array.isArray(optionIds)||optionIds.length>10000)throw Error('Invalid picklist option IDs');
128
+ const ids=[...new Set(optionIds.map(id))].sort((a,b)=>a-b),values=[];
129
+ if(ids.length)await openReadContext(clientId);
130
+ for(let offset=0;offset<ids.length;offset+=200){
131
+ const chunk=ids.slice(offset,offset+200);
132
+ const rows=await mapped(`SELECT v.picklistValueId AS optionId, v.picklistId AS picklistId, v.name AS name, v.code AS code FROM SpPicklistValue AS v WHERE v.picklistValueId IN (${chunk.join(',')}) ORDER BY v.picklistValueId`,200);
133
+ if(rows.length!==chunk.length||new Set(rows.map(r=>r.optionId)).size!==chunk.length||rows.some(r=>!chunk.includes(r.optionId)||!Number.isSafeInteger(r.picklistId)||r.picklistId<=0||typeof r.name!=='string'||!r.name.trim()||r.code!==null&&typeof r.code!=='string'))throw Error('Incomplete picklist option catalog');
134
+ values.push(...rows);
135
+ }
136
+ return {source:'gwt-rpc',tenant,clientId,count:values.length,values};
137
+ };
123
138
  const readEnrollment=async target=>{
124
139
  const model=await openTarget(target),householdClientIds=await householdIds(target);
125
140
  const entries=await mapped(`SELECT e.entryExitId AS enrollmentId, e.clientId AS clientId, e.entryDate AS projectStartDate, e.groupId AS groupId, e.providerId AS providerId, e.typeEntryExit.name AS enrollmentType FROM SpEntryExit AS e WHERE e.active = TRUE AND e.entryExitId = ${id(target.enrollmentId)} AND e.clientId = ${id(target.clientId)}`);
@@ -213,7 +228,7 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
213
228
  try{await save.click();const result=await Promise.race([captured,new Promise(resolve=>{timer=setTimeout(()=>resolve(null),15000);})]);if(!result)throw Error('Expected mutation was not captured');pending=digest(JSON.stringify({url:result.url,headers:result.headers,body:result.body}));return result;}
214
229
  finally{clearTimeout(timer);await page.unroute('**/service/**',handler);await ready();await closeDialogs();}
215
230
  };
216
- return {connection:resolveUiConnection({tenant,moduleBaseUrl}),readEnrollment,listEnrollments,prepare,async send(request){
231
+ return {connection:resolveUiConnection({tenant,moduleBaseUrl}),readEnrollment,listEnrollments,readPicklistOptions,prepare,async send(request){
217
232
  checkGeneration();const fingerprint=digest(JSON.stringify(request));if(!pending||fingerprint!==pending)throw Error('Only the newly prepared request can be sent once');pending=undefined;return post(request);
218
233
  }};
219
234
  }
@@ -0,0 +1,22 @@
1
+ import type { AssessmentDefinition } from './metadata.js';
2
+ import type { EncodingOptions } from './encoding.js';
3
+ export interface PicklistCatalogOption {
4
+ optionId: number;
5
+ picklistId: number;
6
+ code: string | null;
7
+ name: string;
8
+ }
9
+ export interface PicklistCatalogSnapshot {
10
+ source: 'gwt-rpc';
11
+ tenant: string;
12
+ clientId: number;
13
+ count: number;
14
+ values: PicklistCatalogOption[];
15
+ }
16
+ export type CatalogWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {
17
+ readonly tenant: string;
18
+ };
19
+ /** Exact catalog names joined by identity, without reading any stored answer.
20
+ * Only choices present in the supplied assessment definition can resolve. */
21
+ export declare function createCatalogWriteLabelResolver(definition: AssessmentDefinition, catalog: PicklistCatalogSnapshot): CatalogWriteLabelResolver;
22
+ //# sourceMappingURL=picklist-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picklist-labels.d.ts","sourceRoot":"","sources":["../../src/picklist-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAmC,MAAM,eAAe,CAAC;AAE1F,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,qBAAqB;IAAE,QAAQ,EAAC,MAAM,CAAC;IAAA,UAAU,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,GAAC,IAAI,CAAC;IAAA,IAAI,EAAC,MAAM,CAAC;CAAC;AACxG,MAAM,WAAW,uBAAuB;IAAE,MAAM,EAAC,SAAS,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAC;IAAA,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,EAAC,qBAAqB,EAAE,CAAC;CAAC;AACtI,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,GAAG;IAAC,QAAQ,CAAC,MAAM,EAAC,MAAM,CAAA;CAAC,CAAC;AAE9G;6EAC6E;AAC7E,wBAAgB,+BAA+B,CAAC,UAAU,EAAC,oBAAoB,EAAC,OAAO,EAAC,uBAAuB,GAAE,yBAAyB,CAoBzI"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCatalogWriteLabelResolver = createCatalogWriteLabelResolver;
4
+ const metadata_js_1 = require("./metadata.js");
5
+ const errors_js_1 = require("./errors.js");
6
+ /** Exact catalog names joined by identity, without reading any stored answer.
7
+ * Only choices present in the supplied assessment definition can resolve. */
8
+ function createCatalogWriteLabelResolver(definition, catalog) {
9
+ const fail = () => { throw new errors_js_1.ValidationError('incomplete or mismatched picklist label catalog'); };
10
+ if (catalog?.source !== 'gwt-rpc' || catalog.tenant !== definition.tenant || !Number.isSafeInteger(catalog.clientId) || catalog.clientId <= 0 || !Array.isArray(catalog.values) || catalog.count !== catalog.values.length)
11
+ fail();
12
+ const rows = new Map();
13
+ for (const row of catalog.values) {
14
+ if (!row || !Number.isSafeInteger(row.optionId) || row.optionId <= 0 || !Number.isSafeInteger(row.picklistId) || row.picklistId <= 0 || row.code !== null && typeof row.code !== 'string' || typeof row.name !== 'string' || !row.name.trim() || rows.has(row.optionId))
15
+ fail();
16
+ rows.set(row.optionId, { ...row });
17
+ }
18
+ const signature = (q, o) => JSON.stringify([q.questionId, q.fieldName, q.questionType, o.id, o.picklistId, o.code, o.picklistCode, o.name, o.hudVisibility]);
19
+ const labels = new Map();
20
+ for (const { question } of (0, metadata_js_1.flattenQuestions)(definition.questions)) {
21
+ if (question.questionType !== 'lookup')
22
+ continue;
23
+ for (const option of question.options) {
24
+ const row = rows.get(option.id);
25
+ if (!row || row.picklistId !== option.picklistId || row.code !== option.code) {
26
+ fail();
27
+ continue;
28
+ }
29
+ labels.set(signature(question, option), row.name);
30
+ }
31
+ }
32
+ const resolver = (question, option) => labels.get(signature(question, option));
33
+ return Object.freeze(Object.assign(resolver, { tenant: definition.tenant }));
34
+ }
35
+ //# sourceMappingURL=picklist-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picklist-labels.js","sourceRoot":"","sources":["../../src/picklist-labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type {AssessmentDefinition,PicklistOption,QuestionDefinition} from './metadata.js';\nimport {flattenQuestions} from './metadata.js';\nimport type {EncodingOptions} from './encoding.js';\nimport {ValidationError} from './errors.js';\n\nexport interface PicklistCatalogOption {optionId:number;picklistId:number;code:string|null;name:string;}\nexport interface PicklistCatalogSnapshot {source:'gwt-rpc';tenant:string;clientId:number;count:number;values:PicklistCatalogOption[];}\nexport type CatalogWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {readonly tenant:string};\n\n/** Exact catalog names joined by identity, without reading any stored answer.\n * Only choices present in the supplied assessment definition can resolve. */\nexport function createCatalogWriteLabelResolver(definition:AssessmentDefinition,catalog:PicklistCatalogSnapshot):CatalogWriteLabelResolver{\n const fail=()=>{throw new ValidationError('incomplete or mismatched picklist label catalog');};\n if(catalog?.source!=='gwt-rpc'||catalog.tenant!==definition.tenant||!Number.isSafeInteger(catalog.clientId)||catalog.clientId<=0||!Array.isArray(catalog.values)||catalog.count!==catalog.values.length)fail();\n const rows=new Map<number,PicklistCatalogOption>();\n for(const row of catalog.values){\n if(!row||!Number.isSafeInteger(row.optionId)||row.optionId<=0||!Number.isSafeInteger(row.picklistId)||row.picklistId<=0||row.code!==null&&typeof row.code!=='string'||typeof row.name!=='string'||!row.name.trim()||rows.has(row.optionId))fail();\n rows.set(row.optionId,{...row});\n }\n const signature=(q:QuestionDefinition,o:PicklistOption)=>JSON.stringify([q.questionId,q.fieldName,q.questionType,o.id,o.picklistId,o.code,o.picklistCode,o.name,o.hudVisibility]);\n const labels=new Map<string,string>();\n for(const {question}of flattenQuestions(definition.questions)){\n if(question.questionType!=='lookup')continue;\n for(const option of question.options){\n const row=rows.get(option.id);\n if(!row||row.picklistId!==option.picklistId||row.code!==option.code){fail();continue;}\n labels.set(signature(question,option),row.name);\n }\n }\n const resolver=(question:QuestionDefinition,option:PicklistOption)=>labels.get(signature(question,option));\n return Object.freeze(Object.assign(resolver,{tenant:definition.tenant}));\n}\n"]}
package/dist/cjs/v2.d.ts CHANGED
@@ -12,4 +12,6 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
16
+ export type { PicklistCatalogOption, PicklistCatalogSnapshot, CatalogWriteLabelResolver } from './picklist-labels.js';
15
17
  //# sourceMappingURL=v2.d.ts.map
@@ -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;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAC"}
package/dist/cjs/v2.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createCatalogWriteLabelResolver = void 0;
17
18
  __exportStar(require("./generated/v2.js"), exports);
18
19
  __exportStar(require("./generated/v2-models.js"), exports);
19
20
  __exportStar(require("./runtime.js"), exports);
@@ -28,4 +29,6 @@ __exportStar(require("./gwt-enrollments.js"), exports);
28
29
  __exportStar(require("./program-discovery.js"), exports);
29
30
  __exportStar(require("./mobile-preflight.js"), exports);
30
31
  __exportStar(require("./write-labels.js"), exports);
32
+ var picklist_labels_js_1 = require("./picklist-labels.js");
33
+ Object.defineProperty(exports, "createCatalogWriteLabelResolver", { enumerable: true, get: function () { return picklist_labels_js_1.createCatalogWriteLabelResolver; } });
31
34
  //# sourceMappingURL=v2.js.map
@@ -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;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"]}
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;AAElC,2DAAqE;AAA7D,qIAAA,+BAA+B,OAAA","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\nexport {createCatalogWriteLabelResolver} from './picklist-labels.js';\nexport type {PicklistCatalogOption,PicklistCatalogSnapshot,CatalogWriteLabelResolver} from './picklist-labels.js';\n"]}
package/dist/cjs/v3.d.ts CHANGED
@@ -12,4 +12,6 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
16
+ export type { PicklistCatalogOption, PicklistCatalogSnapshot, CatalogWriteLabelResolver } from './picklist-labels.js';
15
17
  //# sourceMappingURL=v3.d.ts.map
@@ -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;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAC"}
package/dist/cjs/v3.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createCatalogWriteLabelResolver = void 0;
17
18
  __exportStar(require("./generated/v3.js"), exports);
18
19
  __exportStar(require("./generated/v3-models.js"), exports);
19
20
  __exportStar(require("./runtime.js"), exports);
@@ -28,4 +29,6 @@ __exportStar(require("./gwt-enrollments.js"), exports);
28
29
  __exportStar(require("./program-discovery.js"), exports);
29
30
  __exportStar(require("./mobile-preflight.js"), exports);
30
31
  __exportStar(require("./write-labels.js"), exports);
32
+ var picklist_labels_js_1 = require("./picklist-labels.js");
33
+ Object.defineProperty(exports, "createCatalogWriteLabelResolver", { enumerable: true, get: function () { return picklist_labels_js_1.createCatalogWriteLabelResolver; } });
31
34
  //# sourceMappingURL=v3.js.map
@@ -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;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"]}
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;AAElC,2DAAqE;AAA7D,qIAAA,+BAA+B,OAAA","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\nexport {createCatalogWriteLabelResolver} from './picklist-labels.js';\nexport type {PicklistCatalogOption,PicklistCatalogSnapshot,CatalogWriteLabelResolver} from './picklist-labels.js';\n"]}
@@ -0,0 +1,22 @@
1
+ import type { AssessmentDefinition } from './metadata.js';
2
+ import type { EncodingOptions } from './encoding.js';
3
+ export interface PicklistCatalogOption {
4
+ optionId: number;
5
+ picklistId: number;
6
+ code: string | null;
7
+ name: string;
8
+ }
9
+ export interface PicklistCatalogSnapshot {
10
+ source: 'gwt-rpc';
11
+ tenant: string;
12
+ clientId: number;
13
+ count: number;
14
+ values: PicklistCatalogOption[];
15
+ }
16
+ export type CatalogWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {
17
+ readonly tenant: string;
18
+ };
19
+ /** Exact catalog names joined by identity, without reading any stored answer.
20
+ * Only choices present in the supplied assessment definition can resolve. */
21
+ export declare function createCatalogWriteLabelResolver(definition: AssessmentDefinition, catalog: PicklistCatalogSnapshot): CatalogWriteLabelResolver;
22
+ //# sourceMappingURL=picklist-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picklist-labels.d.ts","sourceRoot":"","sources":["../../src/picklist-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAmC,MAAM,eAAe,CAAC;AAE1F,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,qBAAqB;IAAE,QAAQ,EAAC,MAAM,CAAC;IAAA,UAAU,EAAC,MAAM,CAAC;IAAA,IAAI,EAAC,MAAM,GAAC,IAAI,CAAC;IAAA,IAAI,EAAC,MAAM,CAAC;CAAC;AACxG,MAAM,WAAW,uBAAuB;IAAE,MAAM,EAAC,SAAS,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAC;IAAA,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,EAAC,qBAAqB,EAAE,CAAC;CAAC;AACtI,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,GAAG;IAAC,QAAQ,CAAC,MAAM,EAAC,MAAM,CAAA;CAAC,CAAC;AAE9G;6EAC6E;AAC7E,wBAAgB,+BAA+B,CAAC,UAAU,EAAC,oBAAoB,EAAC,OAAO,EAAC,uBAAuB,GAAE,yBAAyB,CAoBzI"}
@@ -0,0 +1,32 @@
1
+ import { flattenQuestions } from './metadata.js';
2
+ import { ValidationError } from './errors.js';
3
+ /** Exact catalog names joined by identity, without reading any stored answer.
4
+ * Only choices present in the supplied assessment definition can resolve. */
5
+ export function createCatalogWriteLabelResolver(definition, catalog) {
6
+ const fail = () => { throw new ValidationError('incomplete or mismatched picklist label catalog'); };
7
+ if (catalog?.source !== 'gwt-rpc' || catalog.tenant !== definition.tenant || !Number.isSafeInteger(catalog.clientId) || catalog.clientId <= 0 || !Array.isArray(catalog.values) || catalog.count !== catalog.values.length)
8
+ fail();
9
+ const rows = new Map();
10
+ for (const row of catalog.values) {
11
+ if (!row || !Number.isSafeInteger(row.optionId) || row.optionId <= 0 || !Number.isSafeInteger(row.picklistId) || row.picklistId <= 0 || row.code !== null && typeof row.code !== 'string' || typeof row.name !== 'string' || !row.name.trim() || rows.has(row.optionId))
12
+ fail();
13
+ rows.set(row.optionId, { ...row });
14
+ }
15
+ const signature = (q, o) => JSON.stringify([q.questionId, q.fieldName, q.questionType, o.id, o.picklistId, o.code, o.picklistCode, o.name, o.hudVisibility]);
16
+ const labels = new Map();
17
+ for (const { question } of flattenQuestions(definition.questions)) {
18
+ if (question.questionType !== 'lookup')
19
+ continue;
20
+ for (const option of question.options) {
21
+ const row = rows.get(option.id);
22
+ if (!row || row.picklistId !== option.picklistId || row.code !== option.code) {
23
+ fail();
24
+ continue;
25
+ }
26
+ labels.set(signature(question, option), row.name);
27
+ }
28
+ }
29
+ const resolver = (question, option) => labels.get(signature(question, option));
30
+ return Object.freeze(Object.assign(resolver, { tenant: definition.tenant }));
31
+ }
32
+ //# sourceMappingURL=picklist-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picklist-labels.js","sourceRoot":"","sources":["../../src/picklist-labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type {AssessmentDefinition,PicklistOption,QuestionDefinition} from './metadata.js';\nimport {flattenQuestions} from './metadata.js';\nimport type {EncodingOptions} from './encoding.js';\nimport {ValidationError} from './errors.js';\n\nexport interface PicklistCatalogOption {optionId:number;picklistId:number;code:string|null;name:string;}\nexport interface PicklistCatalogSnapshot {source:'gwt-rpc';tenant:string;clientId:number;count:number;values:PicklistCatalogOption[];}\nexport type CatalogWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {readonly tenant:string};\n\n/** Exact catalog names joined by identity, without reading any stored answer.\n * Only choices present in the supplied assessment definition can resolve. */\nexport function createCatalogWriteLabelResolver(definition:AssessmentDefinition,catalog:PicklistCatalogSnapshot):CatalogWriteLabelResolver{\n const fail=()=>{throw new ValidationError('incomplete or mismatched picklist label catalog');};\n if(catalog?.source!=='gwt-rpc'||catalog.tenant!==definition.tenant||!Number.isSafeInteger(catalog.clientId)||catalog.clientId<=0||!Array.isArray(catalog.values)||catalog.count!==catalog.values.length)fail();\n const rows=new Map<number,PicklistCatalogOption>();\n for(const row of catalog.values){\n if(!row||!Number.isSafeInteger(row.optionId)||row.optionId<=0||!Number.isSafeInteger(row.picklistId)||row.picklistId<=0||row.code!==null&&typeof row.code!=='string'||typeof row.name!=='string'||!row.name.trim()||rows.has(row.optionId))fail();\n rows.set(row.optionId,{...row});\n }\n const signature=(q:QuestionDefinition,o:PicklistOption)=>JSON.stringify([q.questionId,q.fieldName,q.questionType,o.id,o.picklistId,o.code,o.picklistCode,o.name,o.hudVisibility]);\n const labels=new Map<string,string>();\n for(const {question}of flattenQuestions(definition.questions)){\n if(question.questionType!=='lookup')continue;\n for(const option of question.options){\n const row=rows.get(option.id);\n if(!row||row.picklistId!==option.picklistId||row.code!==option.code){fail();continue;}\n labels.set(signature(question,option),row.name);\n }\n }\n const resolver=(question:QuestionDefinition,option:PicklistOption)=>labels.get(signature(question,option));\n return Object.freeze(Object.assign(resolver,{tenant:definition.tenant}));\n}\n"]}
package/dist/esm/v2.d.ts CHANGED
@@ -12,4 +12,6 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
16
+ export type { PicklistCatalogOption, PicklistCatalogSnapshot, CatalogWriteLabelResolver } from './picklist-labels.js';
15
17
  //# sourceMappingURL=v2.d.ts.map
@@ -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;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAC"}
package/dist/esm/v2.js CHANGED
@@ -12,4 +12,5 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
15
16
  //# sourceMappingURL=v2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v2.js","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","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"]}
1
+ {"version":3,"file":"v2.js","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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC","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\nexport {createCatalogWriteLabelResolver} from './picklist-labels.js';\nexport type {PicklistCatalogOption,PicklistCatalogSnapshot,CatalogWriteLabelResolver} from './picklist-labels.js';\n"]}
package/dist/esm/v3.d.ts CHANGED
@@ -12,4 +12,6 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
16
+ export type { PicklistCatalogOption, PicklistCatalogSnapshot, CatalogWriteLabelResolver } from './picklist-labels.js';
15
17
  //# sourceMappingURL=v3.d.ts.map
@@ -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;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAC"}
package/dist/esm/v3.js CHANGED
@@ -12,4 +12,5 @@ export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
13
  export * from "./mobile-preflight.js";
14
14
  export * from "./write-labels.js";
15
+ export { createCatalogWriteLabelResolver } from './picklist-labels.js';
15
16
  //# sourceMappingURL=v3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v3.js","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","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"]}
1
+ {"version":3,"file":"v3.js","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;AAElC,OAAO,EAAC,+BAA+B,EAAC,MAAM,sBAAsB,CAAC","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\nexport {createCatalogWriteLabelResolver} from './picklist-labels.js';\nexport type {PicklistCatalogOption,PicklistCatalogSnapshot,CatalogWriteLabelResolver} from './picklist-labels.js';\n"]}
@@ -29,7 +29,25 @@ The default metadata cache TTL is 60 seconds with 100 entries per client. Config
29
29
 
30
30
  WellSky requires a client context and known assessment code for these reads. There is no documented global assessment-list or standalone picklist-list endpoint. Mobile option metadata has no effective-date filter, so definitions explicitly identify its options as current even when the generic assessment read is dated. Metadata also does not provide a complete questionnaire branching engine.
31
31
 
32
- Use the raw `assessments.get`, `assessmentsMobile.getAnswers`, and `enrollments.getAssessment` methods for recorded answers. Use `encodeAnswers` and `encodeNewSubassessmentRow` for endpoint-specific encoding. Mobile lookups use option IDs; standalone/enrollment lookups require an explicit `writeLabel` resolver with tenant-verified labels. Raw option names are not assumed to be valid write labels. Some HUD-marked choices require `(HUD)` in the full write label even when metadata `name` omits it. Do not infer this from trailing whitespace or append the suffix to every option. Continue supplying tenant-verified labels until a label resolver has authoritative metadata; recorded `answer_value_text` is useful evidence for the corresponding option. Provider answers are refused for mobile writes because that endpoint can silently discard them. Answer-state code mappings and multi-value serialization are caller-supplied; a refusal is never converted to `No`.
32
+ Use the raw `assessments.get`, `assessmentsMobile.getAnswers`, and `enrollments.getAssessment` methods for recorded answers. Use `encodeAnswers` and `encodeNewSubassessmentRow` for endpoint-specific encoding. Mobile lookups use option IDs; standalone/enrollment lookups require an explicit `writeLabel` resolver with tenant-verified labels. Raw option names are not assumed to be valid write labels. Some HUD-marked choices require `(HUD)` in the full write label even when metadata `name` omits it. Do not infer this from trailing whitespace or append the suffix to every option. Use the catalog resolver below for exact labels without existing answers, or retain an explicitly verified callback; recorded `answer_value_text` also supplies evidence for observed options. Provider answers are refused for mobile writes because that endpoint can silently discard them. Answer-state code mappings and multi-value serialization are caller-supplied; a refusal is never converted to `No`.
33
+
34
+ ## Resolve lookup labels before an answer exists
35
+
36
+ The browser enrollment session exposes `readPicklistOptions({clientId, optionIds})`. It reads the current GWT picklist catalog and returns complete, counted rows with `optionId`, `picklistId`, `code`, and the exact full `name`. It needs an authenticated session and client context, but no existing enrollment or answer. Only explicit positive option IDs are queried, in batches of 200; missing, duplicated or malformed results stop discovery.
37
+
38
+ ```ts
39
+ import {flattenQuestions, createCatalogWriteLabelResolver} from '@choiceapps/communityservices-sdk';
40
+ const definition = await api.assessments.getDefinition({clientId, assessmentCode});
41
+ const optionIds = [...new Set(flattenQuestions(definition.questions)
42
+ .filter(({question}) => question.questionType === 'lookup')
43
+ .flatMap(({question}) => question.options.map(option => option.id)))];
44
+ const catalog = await session.enrollmentSession.readPicklistOptions({clientId, optionIds});
45
+ const writeLabel = createCatalogWriteLabelResolver(definition, catalog);
46
+ ```
47
+
48
+ The resolver matches option ID, picklist ID and code, preserves the full catalog name, and permits only options already allowed by the question definition. HUD and non-HUD choices have distinct IDs. It never appends a suffix, guesses from whitespace, or broadens the question's allowed list. Missing identity evidence or a tenant mismatch throws before encoding. The immutable resolver returns `undefined` if called with changed option metadata.
49
+
50
+ `createEnrollmentWorkflow` uses this catalog automatically when its `enrollmentSession` has `readPicklistOptions` and `writeLabel` is omitted. This covers standard entry/interim/exit batches, mandatory carry-forward and `correctEntry`. Explicit callbacks take precedence and skip automatic catalog reads. Legacy custom sessions without this reader still need a callback. Raw REST clients do not acquire UI access implicitly. See [the typed example](../examples/catalog-write-labels.mts) and [read-only evidence](vendor-issues/lookup-write-labels.md).
33
51
 
34
52
  ## Resolve exact lookup write labels from readback
35
53
 
@@ -98,4 +98,10 @@ The integrator reports these training-tenant results on November 10 and 11, 2026
98
98
 
99
99
  The integrator has not tested household adoption followed by `createHouseholdInterim`, scalar correction on an adopted enrollment, or a new correction key after resolution. Synthetic tests do not replace that acceptance.
100
100
 
101
- The 0.7.0 label resolver, group flags, household discovery, row registration, error codes and mobile collision preflight have local regression coverage, not new live tenant acceptance. Full write labels remain unresolved for unobserved options. Household discovery refuses ambiguous current-membership matches. Yes/no blanking remains unsupported; no alternative route has been verified.
101
+ The 0.7.0 label resolver, group flags, household discovery, row registration, error codes and mobile collision preflight have local regression coverage, not new live tenant acceptance. In 0.7.0, full write labels remained unresolved for unobserved options; the 0.8.0 catalog reader below removes that limitation when UI catalog access is available. Household discovery refuses ambiguous current-membership matches. Yes/no blanking remains unsupported; no alternative route has been verified.
102
+
103
+ ## Catalog label verification, November 11, 2026
104
+
105
+ Read-only training checks matched all 1,288 distinct option IDs in the Coordinated Entry instrument to current GWT catalog rows across three synthetic clients (4,089 question/option pairs). Catalog names differed from REST metadata names for 139 option occurrences in one definition. The five enhanced yes/no HUD questions shared a catalog; plain yes/no alternatives had separate option IDs. The visible profile DOB-quality dropdown matched the catalog's full labels. This establishes metadata identity and displayed labels without prior-answer evidence, not fresh acceptance of every standard write. Existing enrollment and answer records were left unchanged.
106
+
107
+ SDK 0.8.0 uses the catalog automatically for workflows with a built-in enrollment session. REST-only applications can supply a catalog resolver or retain their explicit callback. See [catalog evidence and limitations](vendor-issues/lookup-write-labels.md).
@@ -130,7 +130,7 @@ Every mandatory label is resolved before the first write. The `yesno` stage veri
130
130
 
131
131
  ## Correct an existing entry assessment
132
132
 
133
- `correctEntry` patches an entry at its original effective timestamp. Supply the verified `createEnrollment` key as `enrollmentKey` and a new stable `key` for each correction. Configure `enrollmentSession` and a `writeLabel` resolver for tenant-verified lookup labels. A correction requires an open enrollment with no interim reviews or exit workflows.
133
+ `correctEntry` patches an entry at its original effective timestamp. Supply the verified `createEnrollment` key as `enrollmentKey` and a new stable `key` for each correction. Configure `enrollmentSession`; built-in sessions discover exact lookup labels automatically. An explicit `writeLabel` callback overrides that discovery. A correction requires an open enrollment with no interim reviews or exit workflows.
134
134
 
135
135
  ```ts
136
136
  const corrected = await workflow.correctEntry({
@@ -234,6 +234,8 @@ const detail = await session.enrollmentSession.readEnrollment({
234
234
 
235
235
  Use the session returned by `openEnrollmentSession`. Lists include open and exited entries, validate complete counts, and refuse more than 1,000 records. List rows and details include `groupId` and `grouped`. The flag describes a stored group link, including a group with one participant; it is not a member count and requires no per-row detail reads. These fields are required on managed catalog rows and optional on custom detail-reader contracts for compatibility. `groupId` is never a household ID. For a verified household match from a head enrollment, use `discoverHouseholdEnrollment` below.
236
236
 
237
+ The same session also exposes `readPicklistOptions({clientId, optionIds})` for exact labels before answers exist. It uses fresh read context, validates counted results, and makes no record writes. Enrollment workflows use it automatically unless `writeLabel` is supplied. See [catalog lookup labels](assessments.md#resolve-lookup-labels-before-an-answer-exists).
238
+
237
239
  ## Individual enrollment exits
238
240
 
239
241
  Use `exitEnrollment` to end a standalone person's program enrollment. It requires a verified `createEnrollment` entry in the same durable store. Supply `enrollmentSession: session.enrollmentSession` and `exitUi: createHouseholdExitUi({page: session.page})` when constructing the workflow. The same exit UI port serves individual and household forms.
package/docs/reference.md CHANGED
@@ -8,14 +8,14 @@ Only the package export paths below are supported import entry points. Declarati
8
8
 
9
9
  Import from `@choiceapps/communityservices-sdk`, `@choiceapps/communityservices-sdk/v3`, or `@choiceapps/communityservices-sdk/v2`. The root selects V3 and also exports the testing helpers. Each version supplies its own generated HTTP models and fixed version header.
10
10
 
11
- Runtime exports at the root: `MobileAnswerPreflightError`, `writeMobileAnswersChecked`, `createReadBackWriteLabelResolver`, `AmbiguousCommitError`, `AssessmentMetadata`, `AuthError`, `CooldownError`, `DefinitionDriftError`, `FakeControl`, `FakeWellSkyClient`, `GwtEnrollmentConflictError`, `GwtEnrollmentHelpers`, `SubassessmentEditor`, `TenantMismatchError`, `ThrottledError`, `UnknownPicklistValueError`, `UpstreamError`, `ValidationError`, `WellSkyClient`, `WellSkyError`, `apiVersion`, `buildAssessmentDefinition`, `compareDefinition`, `createFakeWellSkyClient`, `createInMemoryGwtMutationStore`, `createWellSkyClient`, `encodeAnswers`, `encodeNewSubassessmentRow`, `flattenQuestions`, `mapBulk`, `operationRoutes`, `parseRestInstant`, `readEnrollmentVerifications`, `readOutcome`, `readProgramCatalog`, `resolvePicklistOption`, `semanticHash`, `specSha256`, `toItems`, `verifyWrite`.
11
+ Runtime exports at the root: `MobileAnswerPreflightError`, `writeMobileAnswersChecked`, `createReadBackWriteLabelResolver`, `createCatalogWriteLabelResolver`, `AmbiguousCommitError`, `AssessmentMetadata`, `AuthError`, `CooldownError`, `DefinitionDriftError`, `FakeControl`, `FakeWellSkyClient`, `GwtEnrollmentConflictError`, `GwtEnrollmentHelpers`, `SubassessmentEditor`, `TenantMismatchError`, `ThrottledError`, `UnknownPicklistValueError`, `UpstreamError`, `ValidationError`, `WellSkyClient`, `WellSkyError`, `apiVersion`, `buildAssessmentDefinition`, `compareDefinition`, `createFakeWellSkyClient`, `createInMemoryGwtMutationStore`, `createWellSkyClient`, `encodeAnswers`, `encodeNewSubassessmentRow`, `flattenQuestions`, `mapBulk`, `operationRoutes`, `parseRestInstant`, `readEnrollmentVerifications`, `readOutcome`, `readProgramCatalog`, `resolvePicklistOption`, `semanticHash`, `specSha256`, `toItems`, `verifyWrite`.
12
12
 
13
13
  | Contract | Declaration | Guide |
14
14
  | --- | --- | --- |
15
15
  | HTTP clients and generated models | [V3](../dist/esm/generated/v3.d.ts), [V2](../dist/esm/generated/v2.d.ts) | [REST](rest-client.md), [API table](../API.md) |
16
16
  | Authentication, transport, responses and errors | [Runtime](../dist/esm/runtime.d.ts) | [Configuration](configuration.md), [recovery](troubleshooting.md) |
17
17
  | Metadata and picklists | [Metadata](../dist/esm/metadata.d.ts) | [Assessments](assessments.md) |
18
- | Checked mobile inserts and exact lookup labels | [Preflight](../dist/esm/mobile-preflight.d.ts), [labels](../dist/esm/write-labels.d.ts) | [Assessments](assessments.md) |
18
+ | Checked mobile inserts and exact lookup labels | [Preflight](../dist/esm/mobile-preflight.d.ts), [readback labels](../dist/esm/write-labels.d.ts), [catalog labels](../dist/esm/picklist-labels.d.ts) | [Assessments](assessments.md) |
19
19
  | Answer encoding | [Encoding](../dist/esm/encoding.d.ts) | [Dates and answers](dates-and-answers.md) |
20
20
  | Write verification | [Verification](../dist/esm/verification.d.ts) | [REST](rest-client.md) |
21
21
  | Program catalog | [Program discovery](../dist/esm/program-discovery.d.ts) | [Enrollments](enrollments.md) |
@@ -0,0 +1,13 @@
1
+ # Exact lookup write labels from the UI catalog
2
+
3
+ On November 11, 2026, read-only inspection of the training application's current GWT catalog established a way to discover full lookup labels before an answer exists.
4
+
5
+ REST assessment option metadata can omit `(HUD)` from `name`. The current UI's `SpPicklistValue.name` retains it. A mapped query returns `picklistValueId`, `picklistId`, `code` and `name` together. HUD and non-HUD enhanced yes/no choices are separate catalog records. `show_on_hud_nonhud_site` and trailing whitespace are not reliable substitutes for those identities.
6
+
7
+ The read used the application's observed `MetaQueryService` / `GenericService.getMappedResults` wire context and selected only explicit option IDs already enumerated by REST metadata. The [browser reader](../../browser/playwright-session.mjs) implements the same query with bounded batches and counted result validation. The [public resolver contract](../../dist/esm/picklist-labels.d.ts) joins IDs and codes while retaining the question's allowed choices.
8
+
9
+ Evidence: Coordinated Entry definitions for three synthetic clients matched all 1,288 distinct option IDs (4,089 question/option pairs). Full names differed from REST metadata for 139 occurrences in a definition. Disabling condition, domestic violence, non-cash benefits, health insurance and income-source questions shared the enhanced yes/no list. Living-situation and homelessness-history options were included. The visible profile DOB-quality dropdown matched its full catalog names, including all displayed options rather than only the selected answer.
10
+
11
+ No enrollment or assessment was saved during this inspection. No new REST writes were made to test acceptance of every returned label. Private captures, client identifiers and tenant-specific catalogs are excluded from the package. This internal GWT route is not part of the published REST Swagger; schema, permission, count or identity failures must stop discovery. Explicit application callbacks remain available for deployments without the catalog reader.
12
+
13
+ See [assessment integration](../assessments.md#resolve-lookup-labels-before-an-answer-exists) for usage.
@@ -15,6 +15,7 @@ Every file here ships with the package and imports the public `@choiceapps/commu
15
15
  | [program-discovery.ts](program-discovery.ts) | Program and VA-entry assessment discovery | Exported function; authenticated page required |
16
16
  | [enrollment-workflow.mts](enrollment-workflow.mts) | Single entry and interim assessment plans | Exported function; caller supplies plans and dependencies |
17
17
  | [individual-exit-workflow.mts](individual-exit-workflow.mts) | Standalone program enrollment exit and assessment | Verified entry key, enrollment reader, exit UI port and durable store required |
18
+ | [catalog-write-labels.mts](catalog-write-labels.mts) | Discover full lookup labels without existing answers; automatic workflow configuration | Authenticated enrollment session and current assessment definition required |
18
19
  | [checked-assessment-writes.mts](checked-assessment-writes.mts) | Exact readback lookup labels and checked mobile scalar inserts | Unobserved labels remain unresolved; preflight does not make writes atomic |
19
20
  | [enrollment-discovery-and-correction.mts](enrollment-discovery-and-correction.mts) | Discover/adopt from the head and bind existing correction rows | Explicit row IDs and unique verified household membership required |
20
21
  | [entry-correction-workflow.mts](entry-correction-workflow.mts) | Same-date scalar clears and existing row edits | Original entry/row keys, verified lookup labels and durable store required |
@@ -0,0 +1,15 @@
1
+ import {createCatalogWriteLabelResolver,flattenQuestions,type WellSkyClient} from '@choiceapps/communityservices-sdk';
2
+ import {createEnrollmentWorkflow,type EnrollmentCatalogReader,type EnrollmentWorkflowOptions} from '@choiceapps/communityservices-sdk/browser';
3
+
4
+ /** Standalone encoders can discover exact labels before any option has been answered. */
5
+ export async function discoverLabels(api:WellSkyClient,enrollmentSession:EnrollmentCatalogReader,input:{clientId:number;assessmentCode:string}){
6
+ const definition=await api.assessments.getDefinition(input);
7
+ const optionIds=[...new Set(flattenQuestions(definition.questions).filter(({question})=>question.questionType==='lookup').flatMap(({question})=>question.options.map(o=>o.id)))];
8
+ const catalog=await enrollmentSession.readPicklistOptions({clientId:input.clientId,optionIds});
9
+ return {definition,writeLabel:createCatalogWriteLabelResolver(definition,catalog)};
10
+ }
11
+
12
+ /** Built-in enrollment sessions discover labels automatically when writeLabel is omitted. */
13
+ export function automaticWorkflow(options:Omit<EnrollmentWorkflowOptions,'writeLabel'|'yesNoWrites'|'enrollmentSession'>&{enrollmentSession:EnrollmentCatalogReader}){
14
+ return createEnrollmentWorkflow({...options,yesNoWrites:'standard'});
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choiceapps/communityservices-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "SDK for WellSky Community Services (HMIS), formerly ServicePoint, Client API V2 and V3",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",