Skip to content

Commit e4d309c

Browse files
feat: reroute Advice Services from housing terminal to non-housing with subcategory selection and Citizens Advice floor
Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent d613d0c commit e4d309c

5 files changed

Lines changed: 105 additions & 13 deletions

File tree

__tests__/safeguarding.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,24 @@ describe('Non-Housing Terminal Path', () => {
487487

488488
});
489489

490+
// =============================================================================
491+
// ADVICE SUBCATEGORY ROUTING - Advice bypasses housing profiling
492+
// =============================================================================
493+
494+
describe('Advice Subcategory Routing', () => {
495+
496+
test('selecting Advice at B5 routes to B5A_ADVICE_TYPE, not housing profiling', () => {
497+
const session = sessionAt('B5_MAIN_SUPPORT_NEED', {
498+
routeType: 'FULL',
499+
localAuthority: 'Birmingham',
500+
});
501+
const result = select(session, 5); // Advice is option 5
502+
expect(result.stateUpdates.currentGate).toBe('B5A_ADVICE_TYPE');
503+
expect(result.options).toHaveLength(7);
504+
});
505+
506+
});
507+
490508
// =============================================================================
491509
// DETAILED AGE/GENDER PRIORITY - detailedAge/detailedGender override basics
492510
// =============================================================================

lib/phrasebank.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,36 @@ Support with Benefits and Grants | Turn2us - https://www.turn2us.org.uk/`
334334
options: ["Emergency Housing or Other Accommodation", "Food", "Work", "Health Services", "Advice Services", "Drop In", "Financial Help", "Personal Items", "Personal Services", "Communications", "Training", "Activities"]
335335
},
336336

337+
// ============================================================
338+
// ADVICE TYPE SELECTION
339+
// ============================================================
340+
341+
B5A_ADVICE_TYPE: {
342+
text: `What kind of advice are you looking for?
343+
344+
1. Benefit advice
345+
2. Debt or financial problems
346+
3. Employment advice
347+
4. Immigration or asylum advice
348+
5. Health service advice
349+
6. Legal advice
350+
7. General support`,
351+
options: ["Benefit advice", "Debt or financial problems", "Employment advice", "Immigration or asylum advice", "Health service advice", "Legal advice", "General support"]
352+
},
353+
354+
B5A_ADVICE_TYPE__SUPPORTER: {
355+
text: `What kind of advice are they looking for?
356+
357+
1. Benefit advice
358+
2. Debt or financial problems
359+
3. Employment advice
360+
4. Immigration or asylum advice
361+
5. Health service advice
362+
6. Legal advice
363+
7. General support`,
364+
options: ["Benefit advice", "Debt or financial problems", "Employment advice", "Immigration or asylum advice", "Health service advice", "Legal advice", "General support"]
365+
},
366+
337367
// ============================================================
338368
// CATEGORY-SPECIFIC PROFILING GATES
339369
// Asked based on which support need was selected

lib/serviceMatcher.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ const needToCategoryMap: Record<string, string[]> = {
127127
'Activities': ['activities']
128128
};
129129

130+
// Map advice subcategories to database category.sub values
131+
const adviceSubcategoryMap: Record<string, string[]> = {
132+
'Advice:Benefits': ['benefits'],
133+
'Advice:Debt': ['debt-financial-problems', 'money-management'],
134+
'Advice:Employment': ['employment'],
135+
'Advice:Immigration': ['immigration', 'asylum', 'refugees'],
136+
'Advice:Health': ['health', 'mental-health'],
137+
'Advice:Legal': ['legal'],
138+
'Advice:General': ['general'],
139+
};
140+
130141
// Needs where profile (gender, age, LGBTQ+, etc.) should affect filtering
131142
const profileRelevantNeeds = ['Health', 'Work', 'Financial', 'Training', 'Activities', 'Drop In'];
132143

@@ -406,19 +417,21 @@ function calculateMatchScore(service: Service, profile: UserProfile): number {
406417
* Get services by category and location
407418
*/
408419
export function getServicesByCategory(
409-
localAuthority: string | null,
410-
categories: string[]
420+
localAuthority: string | null,
421+
categories: string[],
422+
subcategories?: string[]
411423
): Service[] {
412424
if (!localAuthority || categories.length === 0) return [];
413-
425+
414426
const la = normalizeLA(localAuthority);
415427
const services = (servicesData as WMCAServicesData).services;
416-
428+
417429
return services.filter(s => {
418430
const serviceLA = normalizeLA(s.local_authority);
419431
const matchesLA = serviceLA === la;
420432
const matchesCategory = categories.includes(s.category.parent);
421-
return matchesLA && matchesCategory;
433+
const matchesSub = !subcategories || subcategories.length === 0 || subcategories.includes(s.category.sub);
434+
return matchesLA && matchesCategory && matchesSub;
422435
});
423436
}
424437

@@ -500,8 +513,9 @@ export function getServicesForNeed(need: string, profile: UserProfile): MatchedS
500513
const categories = needToCategoryMap[need] || [];
501514
if (categories.length === 0) return [];
502515

503-
// Get services matching category and location
504-
let services = getServicesByCategory(profile.localAuthority, categories);
516+
// Get services matching category and location, with optional subcategory filtering
517+
const subcategories = profile.adviceSubcategory ? adviceSubcategoryMap[profile.adviceSubcategory] : undefined;
518+
let services = getServicesByCategory(profile.localAuthority, categories, subcategories);
505519

506520
// Apply profile filtering if relevant for this need
507521
if (profileRelevantNeeds.includes(need)) {

lib/stateMachine.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function createSession(sessionId: string): SessionState {
9999
ageCategory: null,
100100
gender: null,
101101
supportNeed: null,
102+
adviceSubcategory: null,
102103
additionalNeeds: [],
103104
needCount: 0,
104105
homeless: null,
@@ -247,6 +248,9 @@ const needProfileRequirements: Record<string, string[]> = {
247248
// Drop In: Age (youth drop-ins), Gender (some gender-specific)
248249
'Drop In': ['age', 'gender'],
249250

251+
// Advice: subcategory selection provides targeting, no further profiling
252+
'Advice': [],
253+
250254
// Location-only needs - no profiling required
251255
'Food': [],
252256
'Items': [],
@@ -417,6 +421,14 @@ const nationalFallbacks: Record<string, Array<{name: string; phone?: string; web
417421
website: 'https://nationalcareers.service.gov.uk',
418422
description: 'Free careers advice, skills assessment and training information'
419423
}
424+
],
425+
'Advice': [
426+
{
427+
name: 'Citizens Advice',
428+
phone: '0800 144 8848',
429+
website: 'https://www.citizensadvice.org.uk',
430+
description: 'Free, confidential advice on benefits, debt, housing, legal and other issues'
431+
}
420432
]
421433
};
422434

@@ -514,7 +526,7 @@ function buildNonHousingTerminal(session: SessionState): string {
514526

515527
function buildTerminalServices(session: SessionState): string {
516528
// Check if this is a non-housing need
517-
const housingRelatedNeeds = ['Emergency Housing', 'Advice'];
529+
const housingRelatedNeeds = ['Emergency Housing'];
518530
if (session.supportNeed && !housingRelatedNeeds.includes(session.supportNeed)) {
519531
return buildNonHousingTerminal(session);
520532
}
@@ -1060,11 +1072,19 @@ export function processInput(session: SessionState, input: string): RoutingResul
10601072
case 'B5_MAIN_SUPPORT_NEED':
10611073
const needOptions = ['Emergency Housing', 'Food', 'Work', 'Health', 'Advice', 'Drop In', 'Financial', 'Items', 'Services', 'Comms', 'Training', 'Activities'];
10621074
const need = choice ? needOptions[choice - 1] : null;
1063-
1064-
// Housing-related needs require full profiling (age, gender, homelessness status, etc.)
1065-
const housingRelatedNeeds = ['Emergency Housing', 'Advice'];
1075+
1076+
// Advice routes to subcategory selection
1077+
if (need === 'Advice') {
1078+
return {
1079+
...phrase('B5A_ADVICE_TYPE', session.isSupporter),
1080+
stateUpdates: { currentGate: 'B5A_ADVICE_TYPE', supportNeed: need, needCount: session.needCount + 1 }
1081+
};
1082+
}
1083+
1084+
// Housing needs require full profiling
1085+
const housingRelatedNeeds = ['Emergency Housing'];
10661086
const needsFullProfiling = housingRelatedNeeds.includes(need || '');
1067-
1087+
10681088
if (needsFullProfiling) {
10691089
// Continue to homelessness status question
10701090
return {
@@ -1076,7 +1096,13 @@ export function processInput(session: SessionState, input: string): RoutingResul
10761096
const updatedSession = { ...session, supportNeed: need, needCount: session.needCount + 1 };
10771097
return routeToNextProfileQuestion(updatedSession);
10781098
}
1079-
1099+
1100+
case 'B5A_ADVICE_TYPE':
1101+
const adviceOptions = ['Advice:Benefits', 'Advice:Debt', 'Advice:Employment', 'Advice:Immigration', 'Advice:Health', 'Advice:Legal', 'Advice:General'];
1102+
const adviceSub = choice ? adviceOptions[choice - 1] : null;
1103+
const adviceSession = { ...session, adviceSubcategory: adviceSub };
1104+
return routeToNextProfileQuestion(adviceSession);
1105+
10801106
// ============================================================
10811107
// CATEGORY-SPECIFIC PROFILING GATES
10821108
// ============================================================

lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type GateType =
2626
| 'B3_AGE_CATEGORY'
2727
| 'B4_GENDER'
2828
| 'B5_MAIN_SUPPORT_NEED'
29+
| 'B5A_ADVICE_TYPE'
2930
| 'B5_PROFILE_AGE'
3031
| 'B5_PROFILE_GENDER'
3132
| 'B5_PROFILE_LGBTQ'
@@ -107,6 +108,7 @@ export interface SessionState {
107108
ageCategory: string | null;
108109
gender: string | null;
109110
supportNeed: string | null;
111+
adviceSubcategory: string | null;
110112
additionalNeeds: string[];
111113
needCount: number;
112114

@@ -223,6 +225,7 @@ export interface UserProfile {
223225
publicFunds?: string | null;
224226
lgbtqServicePreference?: string | null;
225227
dv?: boolean;
228+
adviceSubcategory?: string | null;
226229
}
227230

228231
export interface ServiceCard {
@@ -260,5 +263,6 @@ export function toUserProfile(session: SessionState): UserProfile {
260263
publicFunds: session.publicFunds,
261264
lgbtqServicePreference: session.lgbtqServicePreference,
262265
dv: session.safeguardingType === 'DOMESTIC_ABUSE',
266+
adviceSubcategory: session.adviceSubcategory,
263267
};
264268
}

0 commit comments

Comments
 (0)