Add chat preference extraction on shared ingest

This commit is contained in:
Rijad Zuzo
2026-02-22 23:22:46 +01:00
parent 8c79ba1345
commit 5ffe970179
5 changed files with 494 additions and 12 deletions
@@ -0,0 +1,65 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:relationship_saver/features/local/chat_preference_extractor.dart';
import 'package:relationship_saver/features/local/local_models.dart';
void main() {
const ChatPreferenceExtractor extractor = ChatPreferenceExtractor();
test('extracts prefer-over like/dislike signals', () {
final List<ExtractedPreferenceSignalCandidate> result = extractor.extract(
'I prefer tea over coffee, especially at night.',
);
expect(
result.any(
(ExtractedPreferenceSignalCandidate item) =>
item.key == 'drink:tea' &&
item.polarity == PreferenceSignalPolarity.like,
),
isTrue,
);
expect(
result.any(
(ExtractedPreferenceSignalCandidate item) =>
item.key == 'drink:coffee' &&
item.polarity == PreferenceSignalPolarity.dislike,
),
isTrue,
);
});
test('extracts positive and negative statements with categories', () {
final List<ExtractedPreferenceSignalCandidate> result = extractor.extract(
"I love bookstores and I don't like loud clubs.",
);
expect(
result.any(
(ExtractedPreferenceSignalCandidate item) =>
item.category == 'hobby' &&
item.polarity == PreferenceSignalPolarity.like,
),
isTrue,
);
expect(
result.any(
(ExtractedPreferenceSignalCandidate item) =>
item.category == 'environment' &&
item.polarity == PreferenceSignalPolarity.dislike,
),
isTrue,
);
});
test('extracts allergy as high-confidence dislike signal', () {
final List<ExtractedPreferenceSignalCandidate> result = extractor.extract(
'I am allergic to peanuts, please remember that.',
);
final ExtractedPreferenceSignalCandidate signal = result.firstWhere(
(ExtractedPreferenceSignalCandidate item) => item.key == 'health:peanuts',
);
expect(signal.polarity, PreferenceSignalPolarity.dislike);
expect(signal.confidence, greaterThan(0.9));
});
}
@@ -226,6 +226,57 @@ void main() {
},
);
test(
'ingestSharedMessage extracts chat-derived preference signals',
() async {
final ProviderContainer container = _createContainer();
addTearDown(container.dispose);
await container.read(localRepositoryProvider.future);
final SharedMessageIngestResult result = await container
.read(localRepositoryProvider.notifier)
.ingestSharedMessage(
const SharedMessageIngestInput(
sourceApp: 'whatsapp',
sourceDisplayName: 'Nora Diaz',
sourceUserId: 'wa:nora_001',
messageText:
"I prefer tea over coffee and I don't like loud clubs.",
),
);
final LocalDataState after = await container.read(
localRepositoryProvider.future,
);
final List<PersonPreferenceSignal> signals = after.preferenceSignals
.where(
(PersonPreferenceSignal signal) =>
signal.personId == result.profileId,
)
.toList(growable: false);
expect(signals, isNotEmpty);
expect(
signals.any(
(PersonPreferenceSignal signal) =>
signal.key == 'drink:tea' &&
signal.polarity == PreferenceSignalPolarity.like &&
signal.status == PreferenceSignalStatus.inferred,
),
isTrue,
);
expect(
signals.any(
(PersonPreferenceSignal signal) =>
signal.key == 'drink:coffee' &&
signal.polarity == PreferenceSignalPolarity.dislike,
),
isTrue,
);
},
);
test(
'queues near-match conflict and then uses fingerprint mapping for follow-up shares',
() async {