Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/services/__tests__/answer-detail-evidence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Unit tests for query-aware answer-detail evidence blocks.
*/

import { describe, expect, it } from 'vitest';
import { createSearchResult } from './test-fixtures.js';
import { buildAnswerDetailEvidenceBlock } from '../answer-detail-evidence.js';

function makeMemory(id: string, content: string) {
return createSearchResult({
id,
content,
created_at: new Date('2026-02-01T00:00:00.000Z'),
});
}

describe('buildAnswerDetailEvidenceBlock', () => {
it('surfaces expensive-training evidence for practical concern questions', () => {
const block = buildAnswerDetailEvidenceBlock([
makeMemory('research', [
'As of February 1, 2026, user is exploring LoRA for language adaptation.',
'As of February 1, 2026, Training multilingual models is expensive.',
].join(' ')),
], 'What practical concern does the student raise about their NLP research?');

expect(block).toContain('Practical concern evidence:');
expect(block).toContain('Training multilingual models is expensive');
expect(block).toContain('LoRA');
});

it('does not emit concern evidence for ordinary research-topic questions', () => {
const block = buildAnswerDetailEvidenceBlock([
makeMemory('research', 'Training multilingual models is expensive.'),
], 'What NLP topic is the student researching?');

expect(block).toBe('');
});

it('surfaces colleague roles from split role and beta-tester memories', () => {
const block = buildAnswerDetailEvidenceBlock([
makeMemory('jake', "User's colleague Jake recommended Supabase for the personal finance tracker project."),
makeMemory('sarah', "Sarah (user's team lead) recommended using React Query for all data fetching patterns."),
makeMemory('beta', 'Jake is one of the first beta testers. Sarah is one of the first beta testers.'),
], 'Who are the two colleagues mentioned, and what roles do they play?');

expect(block).toContain('Colleague role evidence:');
expect(block).toContain('Jake:');
expect(block).toContain('recommended Supabase');
expect(block).toContain('beta tester');
expect(block).toContain('Sarah:');
expect(block).toContain('team lead');
expect(block).toContain('React Query');
});
});
9 changes: 9 additions & 0 deletions src/services/__tests__/current-state-ranking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ describe('isCurrentStateQuery', () => {
expect(isCurrentStateQuery('How much weight have I lost so far?')).toBe(true);
});

it('rejects temporal comparison quantity queries', () => {
expect(isCurrentStateQuery(
"How many months lapsed between Sam's first and second doctor's appointment?",
)).toBe(false);
expect(isCurrentStateQuery(
'How long did James and Samantha date before deciding to move in together?',
)).toBe(false);
});

it('blocks quantity starters that contain historical markers', () => {
expect(isCurrentStateQuery('How many things did I previously own?')).toBe(false);
expect(isCurrentStateQuery('How often did I used to run?')).toBe(false);
Expand Down
2 changes: 1 addition & 1 deletion src/services/__tests__/iterative-retrieval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ describe('applyIterativeRetrieval', () => {

expect(result.triggered).toBe(true);
expect(result.memories.some((memory) => memory.id === 'neighbor')).toBe(true);
expect(result.seedIds).toEqual(['seed-1', 'seed-2']);
expect(result.seedIds).toEqual(['seed-2', 'seed-1']);
});
});
11 changes: 11 additions & 0 deletions src/services/__tests__/quick-extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,15 @@ describe('quickExtractFacts', () => {
expect(facts.some((fact) => fact.fact.includes('Nate has had the turtles for 3 years now'))).toBe(true);
expect(facts.some((fact) => fact.fact.includes('Sam had a check-up with Sam\'s doctor a few days ago'))).toBe(true);
});

it('captures tournament wins from conversational first-person event sentences', () => {
const facts = quickExtractFacts(
[
'[Session date: 2022-08-22]',
'Nate: Woah Joanna, I won an international tournament yesterday! It was wild.',
].join('\n'),
);

expect(facts.some((fact) => fact.fact.includes('won an international tournament yesterday (on August 21, 2022)'))).toBe(true);
});
});
27 changes: 25 additions & 2 deletions src/services/__tests__/retrieval-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,29 @@ describe('formatTieredInjection', () => {
expect(result).toContain('Repeated event endpoints:');
expect(result).toContain('elapsed between endpoints: ~3 months (83 days)');
});

it('suppresses the generic timeline summary when query-aware temporal evidence is present', () => {
const memories = [
makeResult({ id: 'first', content: "Sam had a doctor's appointment as a wake-up call.", created_at: new Date('2023-05-24T00:00:00Z') }),
makeResult({ id: 'second', content: 'Sam had another doctor appointment after changing diet.', created_at: new Date('2023-08-15T00:00:00Z') }),
makeResult({ id: 'plan', content: 'Sam decided to make a new appointment in January.', created_at: new Date('2024-01-10T00:00:00Z') }),
];
const assignments = [
{ memoryId: 'first', tier: 'L2' as const, estimatedTokens: 5 },
{ memoryId: 'second', tier: 'L2' as const, estimatedTokens: 5 },
{ memoryId: 'plan', tier: 'L2' as const, estimatedTokens: 5 },
];
const result = formatTieredInjection(
memories,
assignments,
"How many months lapsed between Sam's first and second doctor's appointment?",
);

expect(result).toContain('Repeated event endpoints:');
expect(result).not.toContain('Timeline:');
expect(result).not.toContain('Key temporal evidence:');
expect(result).not.toContain('2024-01-10 →');
});
});

describe('formatSimpleInjection', () => {
Expand Down Expand Up @@ -301,7 +324,7 @@ describe('formatSimpleInjection', () => {
});

describe('buildInjection query-term visibility', () => {
it('promotes a compressed memory when L0 hides an exact query term', () => {
it('keeps the exact query term visible in the final temporal injection', () => {
const result = buildInjection([
makeResult({
id: 'workshop',
Expand All @@ -312,8 +335,8 @@ describe('buildInjection query-term visibility', () => {
}),
], 'What workshop did Caroline attend recently?', 'tiered', 35);

expect(result.injectionText).toContain('[L1]');
expect(result.injectionText).toContain('workshop');
expect(result.injectionText).toContain('Temporal evidence candidates:');
});
});

Expand Down
61 changes: 61 additions & 0 deletions src/services/__tests__/shared-overlap-evidence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Unit tests for query-aware shared-overlap evidence blocks.
*/

import { describe, expect, it } from 'vitest';
import { createSearchResult } from './test-fixtures.js';
import { buildSharedOverlapEvidenceBlock } from '../shared-overlap-evidence.js';

function makeMemory(id: string, content: string) {
return createSearchResult({
id,
content,
created_at: new Date('2023-08-25T00:00:00.000Z'),
});
}

describe('buildSharedOverlapEvidenceBlock', () => {
it('emits shared painted-subject evidence when two speakers have sunset-painting facts', () => {
const block = buildSharedOverlapEvidenceBlock([
makeMemory('caroline', 'As of August 25, 2023, Caroline painted the subject of sunsets.'),
makeMemory('melanie', 'As of May 8, 2023, Melanie shared image evidence with caption "a photo of a painting of a sunset over a lake".'),
], 'What subject have Caroline and Melanie both painted?');

expect(block).toContain('Shared painted-subject evidence:');
expect(block).toContain('shared painted subject: sunsets');
expect(block).toContain('Caroline:');
expect(block).toContain('Melanie:');
});

it('emits shared visited-city evidence when two speakers have Rome evidence', () => {
const block = buildSharedOverlapEvidenceBlock([
makeMemory('gina', 'Gina has visited Rome once but has never been to Paris.'),
makeMemory('jon', 'In mid-June 2023, Jon took a short trip to Rome to clear his mind.'),
], 'Which city have both Jean and John visited?');

expect(block).toContain('Shared visited-city evidence:');
expect(block).toContain('shared visited city: Rome');
expect(block).toContain('Gina:');
expect(block).toContain('Jon:');
});

it('emits explicit shared activity evidence without promoting adjacent concerts', () => {
const block = buildSharedOverlapEvidenceBlock([
makeMemory('cars', 'Calvin and Dave share the activity of working on cars. Their shared car-work evidence involves restoration.'),
makeMemory('concerts', 'Dave likes concerts, and Calvin performs music for crowds.'),
], 'What shared activities do Dave and Calvin have?');

expect(block).toContain('Shared activity evidence:');
expect(block).toContain('explicit shared activity: working on cars');
expect(block).not.toContain('concert');
});

it('does not emit overlap evidence when only one speaker supports a candidate', () => {
const block = buildSharedOverlapEvidenceBlock([
makeMemory('caroline', 'Caroline painted the subject of sunsets.'),
makeMemory('melanie', 'Melanie painted a horse.'),
], 'What subject have Caroline and Melanie both painted?');

expect(block).toBe('');
});
});
55 changes: 53 additions & 2 deletions src/services/__tests__/subject-aware-ranking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ describe('applySubjectAwareRanking', () => {
]);

expect(ranked.subjects).toEqual(['Gina']);
expect(ranked.keywords).toEqual(['lost', 'job', 'door', 'dash']);
expect(ranked.keywords).toEqual(expect.arrayContaining([
'lost', 'job', 'door', 'dash', 'door dash',
]));
expect(ranked.protectedFingerprints).toHaveLength(1);
expect(ranked.results[0].id).toBe('gina');
});
Expand All @@ -49,6 +51,55 @@ describe('applySubjectAwareRanking', () => {

it('extracts subject and event anchors for exact keyword expansion', () => {
expect(extractSubjectQueryAnchors('When Gina lost her job at Door Dash?'))
.toEqual(['Gina', 'lost', 'job', 'door', 'dash']);
.toEqual(['Gina', 'lost', 'job', 'door', 'dash', 'lost job', 'job door', 'door dash']);
});

it('drops temporal filler anchors but keeps high-signal bigrams', () => {
const anchors = extractSubjectQueryAnchors(
"How many months lapsed between Sam's first and second doctor's appointment?",
);

expect(anchors).toContain('Sams');
expect(anchors).toContain('appointment');
expect(anchors).toContain('doctor appointment');
expect(anchors).not.toContain('many');
expect(anchors).not.toContain('months');
expect(anchors).not.toContain('between');
expect(anchors).not.toContain('first');
expect(anchors).not.toContain('second');
});

it('adds normalized event variants for temporal subject anchors', () => {
const anchors = extractSubjectQueryAnchors(
'How many weeks passed between Maria adopting Coco and Shadow?',
);

expect(anchors).toContain('adopting');
expect(anchors).toContain('adopt');
});

it('penalizes planning-like later memories for temporal event queries', () => {
const ranked = applySubjectAwareRanking(
"How many months lapsed between Sam's first and second doctor's appointment?",
[
buildResult('plan', 'Sam decided to make a new appointment in January.', 1.1),
buildResult('done', 'Sam had a second doctor appointment after changing diet.', 0.6),
],
);

expect(ranked.results[0].id).toBe('done');
expect(ranked.results[1].id).toBe('plan');
});

it('prefers memories that mention more of the requested endpoint anchors', () => {
const ranked = applySubjectAwareRanking(
'How many weeks passed between Maria adopting Coco and Shadow?',
[
buildResult('generic', 'Maria adopted a dog earlier this year.', 0.9),
buildResult('specific', 'Maria adopted Coco and felt instantly attached.', 0.4),
],
);

expect(ranked.results[0].id).toBe('specific');
});
});
Loading
Loading