Files
lab-app/lib/core/services/ai_context_builder.dart
Emre Emir 8bbc9dbff2 Initial commit: DLS - Dental Lab System
- Flutter + PocketBase dental lab management system
- Clinic & lab dashboards, job tracking, patient management
- Product catalog, finance tracking, multi-language support
- AI assistant integration, realtime notifications
- Windows installer (Inno Setup) included
- Developed by kovakyazilim.com
2026-06-11 15:57:31 +03:00

227 lines
8.6 KiB
Dart

import 'package:pocketbase/pocketbase.dart';
import '../api/pocketbase_client.dart';
import '../../models/tenant.dart';
class AiContextBuilder {
AiContextBuilder._();
static final instance = AiContextBuilder._();
PocketBase get _pb => PocketBaseClient.instance.pb;
Future<String> build(TenantMembership membership) async {
final tenant = membership.tenant;
final tenantId = tenant.id;
final isLab = tenant.kind == TenantKind.lab;
final now = DateTime.now();
final dateStr = '${now.day}.${now.month}.${now.year}';
final results = await Future.wait([
_fetchActiveJobs(tenantId, isLab),
_fetchRecentDelivered(tenantId, isLab),
_fetchFinance(tenantId, isLab),
_fetchTeam(tenantId),
]);
final actions = _actionsPrompt(isLab);
return 'Sen DLS (Dental Lab System) uygulamasinin akilli asistanisin.\n'
'${tenant.companyName} adli ${isLab ? 'dental laboratuvarinin' : 'dis kliniginin'} verilerine erisebilirsin.\n'
'Kullanici rolu: ${isLab ? 'LABORATUVAR' : 'KLINIK'}\n'
'\n'
'Tarih: $dateStr\n'
'\n'
'${results[0]}\n'
'\n'
'${results[1]}\n'
'\n'
'${results[2]}\n'
'\n'
'${results[3]}\n'
'\n'
'$actions\n'
'\n'
'Yanit kurallari:\n'
'- Turkce, kisa ve net yaz\n'
'- Sadece yukaridaki verilerden hareketle yorum yap\n'
'- Listelerde madde isareti (- ) kullan\n'
'- Onemli bilgileri **kalin** yaz\n'
'- Aksiyon etiketlerini HERZAMAN metnin sonuna koy\n'
'- ${isLab ? 'Is kodlari icin [ID:...] formatini kullan' : 'Hasta kodlari ve is durumlarini net belirt'}\n';
}
static String _actionsPrompt(bool isLab) {
final buf = StringBuffer();
buf.writeln('## EYLEM YETKILERIN');
buf.writeln('Kullanici bir islem yapmak istediginde asagidaki XML etiketlerini yanita ekle:');
buf.writeln('');
buf.writeln('Is dosyalarini gostermek:');
buf.writeln('<dls-action type="job_files" job_id="JOB_ID" label="AB001 dosyalarini goster"/>');
buf.writeln('');
buf.writeln('Is iptal etmek:');
buf.writeln('<dls-action type="cancel_job" job_id="JOB_ID" label="AB001 isini iptal et"/>');
if (!isLab) {
buf.writeln('');
buf.writeln('Teslim edildi isaretlemek (sadece klinik):');
buf.writeln('<dls-action type="mark_delivered" job_id="JOB_ID" label="AB001 teslim edildi"/>');
}
buf.writeln('');
buf.writeln('Ekip uyesi eklemek (TUM bilgiler alindiktan sonra):');
buf.writeln('<dls-action type="add_member" email="..." first_name="..." last_name="..." role="technician|admin|doctor|delivery|finance|member" password="..." label="Ad Soyad ekle"/>');
buf.writeln('');
buf.writeln('KURALLAR:');
buf.writeln('- Etiketi SADECE kullanici acikca islem istediginde ekle');
buf.writeln('- Sifre sorulursa kullanicidan al, ASLA uydurma');
buf.writeln('- iptal gibi geri alinmaz islemleri acikca belirt');
buf.writeln('- Etiket icindeki job_id degerini yukaridaki is listesinden al');
buf.writeln('- <dls-action> etiketlerini KESINLİKLE kod blogu (```xml veya ```) icine ALMA, duz metin olarak yaz');
return buf.toString();
}
Future<String> _fetchActiveJobs(String tenantId, bool isLab) async {
try {
final tenantField = isLab ? 'lab_tenant_id' : 'clinic_tenant_id';
final counterpartField = isLab ? 'clinic_tenant_id' : 'lab_tenant_id';
final result = await _pb.collection('jobs').getList(
filter: '$tenantField = "$tenantId" && status != "delivered" && status != "cancelled"',
perPage: 60,
sort: '-created',
expand: counterpartField,
);
if (result.items.isEmpty) return '## Aktif Isler\nSu an aktif is yok.';
final counterpartLabel = isLab ? 'Klinik' : 'Lab';
final lines = result.items.map((r) {
final j = r.toJson();
final jobId = j['id'] as String? ?? '';
final expand = j['expand'] as Map<String, dynamic>?;
final counterpart =
(expand?[counterpartField] as Map?)?['company_name'] as String? ?? '-';
final status = _statusTr(j['status'] as String? ?? '');
final prosthetic = j['prosthetic_type'] as String? ?? '-';
final patient = j['patient_code'] as String? ?? '-';
final step = j['current_step'] as String?;
final stepPart = (step != null && step.isNotEmpty) ? ' | Adim: $step' : '';
final due = j['due_date'] as String? ?? '';
final duePart = due.isNotEmpty ? ' | Termin: ${due.substring(0, 10)}' : '';
return '- [ID:$jobId] Hasta: $patient | $prosthetic | $status$stepPart | $counterpartLabel: $counterpart$duePart';
}).join('\n');
return '## Aktif Isler (${result.items.length})\n$lines';
} catch (e) {
return '## Aktif Isler\n(Veri alinamadi: $e)';
}
}
Future<String> _fetchRecentDelivered(String tenantId, bool isLab) async {
try {
final tenantField = isLab ? 'lab_tenant_id' : 'clinic_tenant_id';
final counterpartField = isLab ? 'clinic_tenant_id' : 'lab_tenant_id';
final result = await _pb.collection('jobs').getList(
filter: '$tenantField = "$tenantId" && status = "delivered"',
perPage: 10,
sort: '-updated',
expand: counterpartField,
);
if (result.items.isEmpty) return '## Son Teslim Edilenler\nHenuz teslim edilen is yok.';
final counterpartLabel = isLab ? 'Klinik' : 'Lab';
final lines = result.items.map((r) {
final j = r.toJson();
final jobId = j['id'] as String? ?? '';
final expand = j['expand'] as Map<String, dynamic>?;
final counterpart =
(expand?[counterpartField] as Map?)?['company_name'] as String? ?? '-';
final prosthetic = j['prosthetic_type'] as String? ?? '-';
final patient = j['patient_code'] as String? ?? '-';
final updated = (j['updated'] as String? ?? '');
final datePart = updated.length >= 10 ? updated.substring(0, 10) : '';
return '- [ID:$jobId] Hasta: $patient | $prosthetic | $counterpartLabel: $counterpart${datePart.isNotEmpty ? ' | Tarih: $datePart' : ''}';
}).join('\n');
return '## Son Teslim Edilenler (son 10)\n$lines';
} catch (_) {
return '## Son Teslim Edilenler\n(Veri alinamadi)';
}
}
Future<String> _fetchFinance(String tenantId, bool isLab) async {
try {
final type = isLab ? 'receivable' : 'payable';
final result = await _pb.collection('finance_entries').getList(
filter: 'tenant_id = "$tenantId" && type = "$type"',
perPage: 200,
);
double pending = 0, paid = 0;
for (final r in result.items) {
final j = r.toJson();
final amount = (j['amount'] as num?)?.toDouble() ?? 0;
if (j['status'] == 'pending') {
pending += amount;
} else {
paid += amount;
}
}
final label = isLab ? 'alacak' : 'borc';
return '## Finans\n'
'- Bekleyen $label: ${pending.toStringAsFixed(0)} TL\n'
'- Tahsil edilen: ${paid.toStringAsFixed(0)} TL';
} catch (_) {
return '## Finans\n(Veri alinamadi)';
}
}
Future<String> _fetchTeam(String tenantId) async {
try {
final result = await _pb.collection('tenant_members').getList(
filter: 'tenant_id = "$tenantId"',
expand: 'user_id',
perPage: 50,
);
if (result.items.isEmpty) return '## Ekip\nUye yok.';
final lines = result.items.map((r) {
final j = r.toJson();
final expand = j['expand'] as Map<String, dynamic>?;
final user = expand?['user_id'] as Map<String, dynamic>?;
final first = (user?['first_name'] as String?) ?? '';
final last = (user?['last_name'] as String?) ?? '';
final email = (user?['email'] as String?) ?? '';
final name =
'$first $last'.trim().isNotEmpty ? '$first $last'.trim() : email;
final role = _roleTr(j['role'] as String? ?? '');
return '- $name ($role)';
}).join('\n');
return '## Ekip (${result.items.length} uye)\n$lines';
} catch (_) {
return '## Ekip\n(Veri alinamadi)';
}
}
static String _statusTr(String s) => switch (s) {
'pending' => 'Bekliyor',
'in_progress' => 'Devam ediyor',
'sent' => 'Gonderildi',
'revision' => 'Revizyon',
'delivered' => 'Teslim edildi',
'cancelled' => 'Iptal',
_ => s,
};
static String _roleTr(String s) => switch (s) {
'owner' => 'Sahibi',
'admin' => 'Yonetici',
'technician' => 'Teknisyen',
'delivery' => 'Teslimat',
'finance' => 'Finans',
'doctor' => 'Hekim',
_ => 'Uye',
};
}