feat: add local-first private AI digest workflow
Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:relationship_saver/app/state/local_data_state.dart';
|
||||
import 'package:relationship_saver/features/ideas/domain/idea_models.dart';
|
||||
import 'package:relationship_saver/features/people/domain/person_models.dart';
|
||||
import 'package:relationship_saver/features/reminders/domain/reminder_models.dart';
|
||||
import 'package:relationship_saver/features/share_intake/domain/share_models.dart';
|
||||
import 'package:relationship_saver/features/signals/domain/signals_feed_synthesizer.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'creates a check-in signal when interaction is stale and no reminder exists',
|
||||
() {
|
||||
final LocalDataState state = LocalDataState(
|
||||
people: <PersonProfile>[
|
||||
PersonProfile(
|
||||
id: 'p-1',
|
||||
name: 'Alex',
|
||||
relationship: 'Friend',
|
||||
affinityScore: 80,
|
||||
nextMoment: DateTime(2026, 4, 30),
|
||||
tags: const <String>[],
|
||||
notes: '',
|
||||
lastInteractedAt: DateTime(2026, 4, 1),
|
||||
),
|
||||
],
|
||||
moments: const [],
|
||||
ideas: const [],
|
||||
reminders: const [],
|
||||
tasks: const [],
|
||||
);
|
||||
|
||||
final signals = SignalsFeedSynthesizer.synthesize(
|
||||
state: state,
|
||||
now: DateTime(2026, 4, 17),
|
||||
);
|
||||
|
||||
expect(signals, isNotEmpty);
|
||||
expect(signals.first.title, 'Check in with Alex');
|
||||
expect(
|
||||
signals.first.metadata?[signalMetadataActionKey],
|
||||
createReminderSignalAction,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'suppresses stale check-in signal when a nearby reminder already exists',
|
||||
() {
|
||||
final LocalDataState state = LocalDataState(
|
||||
people: <PersonProfile>[
|
||||
PersonProfile(
|
||||
id: 'p-1',
|
||||
name: 'Alex',
|
||||
relationship: 'Friend',
|
||||
affinityScore: 80,
|
||||
nextMoment: DateTime(2026, 4, 30),
|
||||
tags: const <String>[],
|
||||
notes: '',
|
||||
lastInteractedAt: DateTime(2026, 4, 1),
|
||||
),
|
||||
],
|
||||
moments: const [],
|
||||
ideas: const [],
|
||||
reminders: <ReminderRule>[
|
||||
ReminderRule(
|
||||
id: 'r-1',
|
||||
personId: 'p-1',
|
||||
title: 'Weekly check in',
|
||||
cadence: ReminderCadence.weekly,
|
||||
nextAt: DateTime(2026, 4, 18),
|
||||
),
|
||||
],
|
||||
tasks: const [],
|
||||
);
|
||||
|
||||
final signals = SignalsFeedSynthesizer.synthesize(
|
||||
state: state,
|
||||
now: DateTime(2026, 4, 17),
|
||||
);
|
||||
|
||||
expect(
|
||||
signals.where((signal) => signal.title.contains('Check in')),
|
||||
isEmpty,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('creates an upcoming date signal with idea count context', () {
|
||||
final DateTime now = DateTime(2026, 4, 17);
|
||||
final LocalDataState state = LocalDataState(
|
||||
people: <PersonProfile>[
|
||||
PersonProfile(
|
||||
id: 'p-1',
|
||||
name: 'Mila',
|
||||
relationship: 'Sister',
|
||||
affinityScore: 88,
|
||||
nextMoment: DateTime(2026, 4, 25),
|
||||
tags: const <String>[],
|
||||
notes: '',
|
||||
lastInteractedAt: DateTime(2026, 4, 16),
|
||||
),
|
||||
],
|
||||
moments: const [],
|
||||
ideas: <RelationshipIdea>[
|
||||
RelationshipIdea(
|
||||
id: 'i-1',
|
||||
personId: 'p-1',
|
||||
type: IdeaType.gift,
|
||||
title: 'Plant stand',
|
||||
details: 'Oak finish',
|
||||
createdAt: DateTime(2026, 4, 10),
|
||||
),
|
||||
],
|
||||
reminders: const [],
|
||||
tasks: const [],
|
||||
importantDates: <PersonImportantDate>[
|
||||
PersonImportantDate(
|
||||
id: 'd-1',
|
||||
personId: 'p-1',
|
||||
label: 'Birthday',
|
||||
date: now.add(const Duration(days: 3)),
|
||||
classification: 'birthday',
|
||||
sourceKind: CaptureSourceKind.manual,
|
||||
createdAt: now.subtract(const Duration(days: 1)),
|
||||
updatedAt: now,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final signals = SignalsFeedSynthesizer.synthesize(state: state, now: now);
|
||||
|
||||
expect(
|
||||
signals.any(
|
||||
(signal) => signal.title.contains('Birthday for Mila is in 3 days'),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user