Skip to content
Open
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
89 changes: 58 additions & 31 deletions lib/services/first_aid_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,36 @@ import '../database/app_database.dart';
/// on the **same** PowerSync database as the rest of the app (no separate sqflite DB).
///
/// **Not a substitute for professional training or emergency services.** Always call local EMS.
class _FirstAidEntry {
final String id;
final String title;
final String body;
final String tags;
final String source;

final String titleLower;
final String tagsLower;
final String searchHaystackLower;

_FirstAidEntry({
required this.id,
required this.title,
required this.body,
required this.tags,
required this.source,
}) : titleLower = title.toLowerCase(),
tagsLower = tags.toLowerCase(),
searchHaystackLower = '$title $body $tags'.toLowerCase();
}

class FirstAidRepository {
FirstAidRepository._();

static final FirstAidRepository instance = FirstAidRepository._();

static const String assetPath = 'assets/first_aid/corpus.json';

List<Map<String, dynamic>>? _corpusRows;
List<_FirstAidEntry>? _corpusEntries;
bool _ftsReady = false;

static const String medicalDisclaimer =
Expand All @@ -26,10 +48,19 @@ class FirstAidRepository {
'response where available — follow dispatcher instructions.';

Future<void> ensureInitialized() async {
if (_corpusRows == null) {
if (_corpusEntries == null) {
final raw = await rootBundle.loadString(assetPath);
final decoded = jsonDecode(raw) as List<dynamic>;
_corpusRows = decoded.cast<Map<String, dynamic>>();
final rawList = decoded.cast<Map<String, dynamic>>();
_corpusEntries = rawList.map((row) {
return _FirstAidEntry(
id: row['id'] as String? ?? '',
title: row['title'] as String? ?? '',
body: row['body'] as String? ?? '',
tags: row['tags'] as String? ?? '',
source: row['source'] as String? ?? '',
);
}).toList();
}
if (kIsWeb || !isDatabaseInitialized || _ftsReady) {
return;
Expand All @@ -47,18 +78,18 @@ CREATE VIRTUAL TABLE IF NOT EXISTS first_aid_fts USING fts5(
await appDb.getAll('SELECT COUNT(*) AS c FROM first_aid_fts');
final count = (countRows.first['c'] as num?)?.toInt() ?? 0;

if (count == 0 && _corpusRows!.isNotEmpty) {
for (final row in _corpusRows!) {
if (count == 0 && _corpusEntries!.isNotEmpty) {
for (final entry in _corpusEntries!) {
await appDb.execute(
'''
INSERT INTO first_aid_fts (title, body, tags, source)
VALUES (?, ?, ?, ?)
''',
[
row['title'] as String? ?? '',
row['body'] as String? ?? '',
row['tags'] as String? ?? '',
row['source'] as String? ?? '',
entry.title,
entry.body,
entry.tags,
entry.source,
],
);
}
Expand Down Expand Up @@ -87,11 +118,9 @@ CREATE VIRTUAL TABLE IF NOT EXISTS first_aid_fts USING fts5(
if (q.isEmpty) return [];

final suggestions = <String>[];
for (final row in _corpusRows!) {
final title = row['title'] as String? ?? '';
final tags = row['tags'] as String? ?? '';
if (title.toLowerCase().contains(q) || tags.toLowerCase().contains(q)) {
suggestions.add(title);
for (final entry in _corpusEntries!) {
if (entry.titleLower.contains(q) || entry.tagsLower.contains(q)) {
suggestions.add(entry.title);
}
if (suggestions.length >= 6) break;
}
Expand All @@ -105,17 +134,15 @@ CREATE VIRTUAL TABLE IF NOT EXISTS first_aid_fts USING fts5(
}

var bestScore = -1;
Map<String, dynamic>? best;
for (final row in _corpusRows!) {
final haystack =
'${row['title']} ${row['body']} ${row['tags']}'.toLowerCase();
_FirstAidEntry? best;
for (final entry in _corpusEntries!) {
var score = 0;
for (final t in tokens) {
if (haystack.contains(t)) score += 3;
if (entry.searchHaystackLower.contains(t)) score += 3;
}
if (score > bestScore) {
bestScore = score;
best = row;
best = entry;
}
}

Expand All @@ -124,9 +151,9 @@ CREATE VIRTUAL TABLE IF NOT EXISTS first_aid_fts USING fts5(
}

return _formatResult(
title: best['title'] as String? ?? 'Topic',
body: best['body'] as String? ?? '',
source: best['source'] as String? ?? '',
title: best.title,
body: best.body,
source: best.source,
);
}

Expand Down Expand Up @@ -186,18 +213,18 @@ CREATE VIRTUAL TABLE IF NOT EXISTS first_aid_fts USING fts5(
}

String _pickGeneralOrFirst() {
Map<String, dynamic>? row;
for (final r in _corpusRows!) {
if ((r['id'] as String?) == 'general-road-emergency-india') {
row = r;
_FirstAidEntry? entry;
for (final r in _corpusEntries!) {
if (r.id == 'general-road-emergency-india') {
entry = r;
break;
}
}
row ??= _corpusRows!.first;
entry ??= _corpusEntries!.first;
return _formatResult(
title: row['title'] as String? ?? '',
body: row['body'] as String? ?? '',
source: row['source'] as String? ?? '',
title: entry.title,
body: entry.body,
source: entry.source,
);
}

Expand Down
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1153,10 +1153,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
Expand Down Expand Up @@ -1774,10 +1774,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.9"
timezone:
dependency: transitive
description:
Expand Down
Loading