Skip to content

Commit e08a0e8

Browse files
committed
fix(assistant): align with #224 head - catalogue driver counts, no card badge, corpus doc 187
1 parent d8aa24c commit e08a0e8

7 files changed

Lines changed: 82 additions & 15 deletions

File tree

‎docs/foomatic-assistant.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Values live in `lib/assistant/constants.ts`; `yarn assistant:eval` fails if this
9494

9595
## Testing
9696

97-
- `lib/assistant/__tests__/` — always-run fixture suites: a 156-utterance natural-language corpus (`corpus.ts`) pinning intents and parse details, entity-resolution cases, execution-state semantics (unknown-never-false, duplex, relaxations, better-dimensions), and grounding property tests over rendered responses (every card id and similarity percentage must trace to input data; banned terminology can never appear).
97+
- `lib/assistant/__tests__/` — always-run fixture suites: a 187-utterance natural-language corpus (`corpus.ts`) pinning intents and parse details, entity-resolution cases, execution-state semantics (unknown-never-false, duplex, relaxations, better-dimensions), and grounding property tests over rendered responses (every card id and similarity percentage must trace to input data; banned terminology can never appear).
9898
- `lib/assistant/__tests__/assistant-artifact.test.ts` — the same engine against the real generated artifacts (skipped automatically when they are absent, e.g. CI's pre-generate test run).
9999
- `tools/assistant-eval/run.ts` (`yarn assistant:eval`) — corpus accuracy, a full grounding sweep, the approved end-to-end flows, latency and artifact-size measurement, and the tunables/wording drift gates. Requires generated data, like `tools/eval/`.
100100

‎lib/assistant/__tests__/assistant-artifact.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,21 @@ describe.skipIf(!present)("assistant against real foomatic artifacts", () => {
206206
expect(better.execution.kind).toBe("clarify")
207207
})
208208

209+
it("real recommendation cards never show a driver count", async () => {
210+
// The real shards carry no driver total and the catalogue's must not be
211+
// substituted in: a driver count is not a measure of support quality.
212+
for (const query of ["what printers are similar to this?", "why was HP LaserJet 4P recommended"]) {
213+
const turn = await runAssistant(query, lj4Context, data)
214+
for (const block of turn.plan.blocks) {
215+
if (block.kind !== "printer-cards") continue
216+
for (const card of block.printers) {
217+
if (card.score !== undefined) expect(card.driverCount).toBeUndefined()
218+
}
219+
}
220+
expect(planText(turn.plan)).not.toMatch(/listed drivers/)
221+
}
222+
})
223+
209224
it("every similarity percentage rendered anywhere matches a real shard score", async () => {
210225
const shard = await data.getRecommendations("HP-LaserJet_4")
211226
const scores = new Set(shard.map(entry => Math.round(entry.score * 100)))

‎lib/assistant/__tests__/fixtures.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,39 +64,39 @@ export const RECOMMENDATIONS: Record<string, RecommendationEntry[]> = {
6464
"HP-LaserJet_4": [
6565
{
6666
id: "HP-LaserJet_4P", score: 0.895, manufacturer: "HP", model: "LaserJet 4P",
67-
status: "Perfect", type: "laser", driverCount: 6,
67+
status: "Perfect", type: "laser",
6868
sharedFeatures: ["Preferred Linux driver: hplip", "Laser printer", "PCL 5e", "Similar resolution (300-600 dpi)", "Excellent Linux driver support"],
6969
},
7070
{
7171
id: "HP-LaserJet_5", score: 0.885, manufacturer: "HP", model: "LaserJet 5",
72-
status: "Perfect", type: "laser", driverCount: 8,
72+
status: "Perfect", type: "laser",
7373
sharedFeatures: ["Preferred Linux driver: hplip", "Laser printer", "PCL 5e"],
7474
},
7575
{
7676
id: "Okidata-OL400", score: 0.61, manufacturer: "Okidata", model: "OL400",
77-
status: "Mostly", type: "laser", driverCount: 2,
77+
status: "Mostly", type: "laser",
7878
sharedFeatures: ["Laser printer"],
7979
},
8080
{
8181
id: "Xerox-Phaser_6100", score: 0.52, manufacturer: "Xerox", model: "Phaser 6100",
82-
status: "Perfect", type: "laser", driverCount: 4,
82+
status: "Perfect", type: "laser",
8383
sharedFeatures: ["Laser printer"],
8484
},
8585
],
8686
"Canon-BJC-210": [
8787
{
8888
id: "Epson-Stylus_Color", score: 0.71, manufacturer: "Epson", model: "Stylus Color",
89-
status: "Perfect", type: "inkjet", driverCount: 5,
89+
status: "Perfect", type: "inkjet",
9090
sharedFeatures: ["Inkjet printer", "Color printing"],
9191
},
9292
{
9393
id: "HP-DeskJet_560C", score: 0.55, manufacturer: "HP", model: "DeskJet 560C",
94-
status: "Perfect", type: "inkjet", driverCount: 6,
94+
status: "Perfect", type: "inkjet",
9595
sharedFeatures: ["Inkjet printer", "Color printing"],
9696
},
9797
{
9898
id: "IBM-4019", score: 0.4, manufacturer: "IBM", model: "4019",
99-
status: "Unknown", type: "laser", driverCount: 2,
99+
status: "Unknown", type: "laser",
100100
sharedFeatures: [],
101101
},
102102
],

‎lib/assistant/__tests__/respond.test.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ describe("grounding: every rendered claim traces to fixture data", () => {
6868
knownScores.has(Math.round(card.score * 100)),
6969
`"${testCase.q}" rendered a similarity score not present in any shard`
7070
).toBe(true)
71+
// A driver total is not a support-quality signal, so it must
72+
// never ride alongside a similarity score on a recommendation
73+
// card (PR #224 review decision).
74+
expect(
75+
card.driverCount,
76+
`"${testCase.q}" put a driver count on a recommendation card`
77+
).toBeUndefined()
7178
}
7279
}
7380
}
@@ -105,6 +112,45 @@ describe("honest language for key flows", () => {
105112
expect(text).toContain("not a promise")
106113
})
107114

115+
it("recommendation cards never carry a driver count, on either recommendation flow", async () => {
116+
for (const query of ["what printers are similar to this?", "why was hp laserjet 4p recommended"]) {
117+
const turn = await runAssistant(query, LJ4_CONTEXT, data)
118+
const cards = turn.plan.blocks.filter(block => block.kind === "printer-cards")
119+
expect(cards.length, `"${query}" rendered no cards to check`).toBeGreaterThan(0)
120+
for (const block of cards) {
121+
for (const card of block.printers) {
122+
expect(card.score, `"${query}" card ${card.id} is not recommendation-backed`).toBeDefined()
123+
expect(card.driverCount, `"${query}" card ${card.id} showed a driver count`).toBeUndefined()
124+
}
125+
}
126+
expect(allText(turn.plan)).not.toMatch(/listed drivers/)
127+
}
128+
})
129+
130+
it("catalogue-backed search cards still show driver counts", async () => {
131+
// The badge is dropped from recommendation cards specifically, not removed
132+
// from the assistant: a directory-style result still reports the total.
133+
const turn = await runAssistant("find colour laser printers", HOME_CONTEXT, data)
134+
const cards = turn.plan.blocks.find(block => block.kind === "printer-cards")
135+
expect(cards).toBeDefined()
136+
if (cards && cards.kind === "printer-cards") {
137+
expect(cards.printers.some(card => typeof card.driverCount === "number")).toBe(true)
138+
}
139+
})
140+
141+
it("only an explicit driver-options comparison reports driver totals", async () => {
142+
const turn = await runAssistant("similar printers with better driver options", LJ4_CONTEXT, data)
143+
const text = allText(turn.plan)
144+
// Anchor total comes from the catalogue, not from the recommendation shard.
145+
expect(text).toContain("list more drivers than its 6")
146+
const cards = turn.plan.blocks.filter(block => block.kind === "printer-cards")
147+
for (const block of cards) {
148+
for (const card of block.printers) {
149+
expect(card.driverCount).toBeUndefined()
150+
}
151+
}
152+
})
153+
108154
it("similar-printers responses state that similarity is not a compatibility promise", async () => {
109155
const turn = await runAssistant("what printers are similar to this?", LJ4_CONTEXT, data)
110156
const text = allText(turn.plan)

‎lib/assistant/execute.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,10 @@ async function executeSimilar(
478478
entry => entry.status !== "Unknown" && (STATUS_RANK[entry.status] ?? 0) > sourceRank
479479
)
480480
} else if (better === "drivers") {
481-
filtered = filtered.filter(entry => entry.driverCount > (source.driverCount ?? 0))
481+
// The shard carries no driver total, so the count comes from the local
482+
// catalogue - the same lookup the resolution arm below uses.
483+
const anchorDrivers = source.driverCount ?? 0
484+
filtered = filtered.filter(entry => (byId.get(entry.id)?.driverCount ?? 0) > anchorDrivers)
482485
} else if (better === "resolution") {
483486
sourceMaxDpi = typeof source.maxDpi === "number" ? source.maxDpi : null
484487
if (sourceMaxDpi === null) {

‎lib/assistant/respond.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ function summaryFeatures(summary: PrinterSummary): string[] {
6868
return features
6969
}
7070

71+
// A recommendation card deliberately carries no driver count: a high driver
72+
// total is not a measure of good support, so it must never sit next to a
73+
// similarity score as though it were one (docs/foomatic-data-formats.md).
74+
// Driver counts belong on catalogue-backed cards (summaryCard) and in answers
75+
// to an explicit "better in driver options" question.
7176
function recommendationCard(entry: RecommendationEntry): PrinterCardData {
7277
const tier = confidenceTier(entry.score)
7378
return {
@@ -76,7 +81,6 @@ function recommendationCard(entry: RecommendationEntry): PrinterCardData {
7681
model: entry.model ?? entry.id,
7782
status: entry.status,
7883
type: entry.type !== "unknown" ? entry.type : undefined,
79-
driverCount: entry.driverCount,
8084
score: entry.score,
8185
tierLabel: tier.label,
8286
tierTone: tier.tone,
@@ -248,7 +252,7 @@ export function buildResponse(execution: Execution): ResponsePlan {
248252
return buildComparison(execution.a, execution.b)
249253

250254
case "explanation":
251-
return buildExplanation(execution.source, execution.entry)
255+
return buildExplanation(execution)
252256

253257
case "explanation-none":
254258
return {
@@ -557,16 +561,16 @@ function buildComparison(a: Printer, b: Printer): ResponsePlan {
557561
}
558562
}
559563

560-
function buildExplanation(source: PrinterSummary, entry: RecommendationEntry): ResponsePlan {
564+
function buildExplanation(execution: Extract<Execution, { kind: "explanation" }>): ResponsePlan {
565+
const { source, entry } = execution
561566
const sourceName = printerName(source)
562567
const entryName = `${entry.manufacturer ?? ""} ${entry.model ?? entry.id}`.trim()
563568
const tier = confidenceTier(entry.score)
564569
const blocks: ResponseBlock[] = [
565570
text(
566571
`${entryName} appears in ${sourceName}'s similar-printers list with a ${similarityPercent(entry.score)} score, ` +
567572
`in the "${tier.label}" tier. The score is a comparison of recorded printer features - it is not a promise that ` +
568-
`one printer can replace the other. ${entryName}'s own Foomatic Linux support grade is ${entry.status}, with ` +
569-
`${entry.driverCount} listed drivers.`
573+
`one printer can replace the other. ${entryName}'s own Foomatic Linux support grade is ${entry.status}.`
570574
),
571575
]
572576
if (entry.sharedFeatures.length > 0) {

‎lib/assistant/types.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ export interface RecommendationEntry {
153153
model?: string
154154
status: string
155155
type: string
156-
driverCount: number
157156
}
158157

159158
export type Execution =

0 commit comments

Comments
 (0)