diff --git a/apps/mobile/packages/core/lib/core.dart b/apps/mobile/packages/core/lib/core.dart index e5dff061..3b8a44c3 100644 --- a/apps/mobile/packages/core/lib/core.dart +++ b/apps/mobile/packages/core/lib/core.dart @@ -23,6 +23,8 @@ export 'src/services/api_service/core_api_services/llm/llm_service.dart'; export 'src/services/api_service/core_api_services/llm/llm_response.dart'; export 'src/services/api_service/core_api_services/verification/verification_service.dart'; export 'src/services/api_service/core_api_services/verification/verification_response.dart'; +export 'src/services/api_service/core_api_services/rapid_order/rapid_order_service.dart'; +export 'src/services/api_service/core_api_services/rapid_order/rapid_order_response.dart'; // Device Services export 'src/services/device/camera/camera_service.dart'; diff --git a/apps/mobile/packages/core/lib/src/core_module.dart b/apps/mobile/packages/core/lib/src/core_module.dart index bd782a8a..4759f802 100644 --- a/apps/mobile/packages/core/lib/src/core_module.dart +++ b/apps/mobile/packages/core/lib/src/core_module.dart @@ -29,6 +29,9 @@ class CoreModule extends Module { () => VerificationService(i.get()), ); i.addSingleton(() => LlmService(i.get())); + i.addSingleton( + () => RapidOrderService(i.get()), + ); // 4. Register Device dependency i.addSingleton(() => ImagePicker()); diff --git a/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/core_api_endpoints.dart b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/core_api_endpoints.dart index 1c2a80cd..89dd46d9 100644 --- a/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/core_api_endpoints.dart +++ b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/core_api_endpoints.dart @@ -30,4 +30,11 @@ class CoreApiEndpoints { /// Retry a verification job. static String verificationRetry(String id) => '$baseUrl/core/verifications/$id/retry'; + + /// Transcribe audio to text for rapid orders. + static const String transcribeRapidOrder = + '$baseUrl/core/rapid-orders/transcribe'; + + /// Parse text to structured rapid order. + static const String parseRapidOrder = '$baseUrl/core/rapid-orders/parse'; } diff --git a/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_response.dart b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_response.dart new file mode 100644 index 00000000..eeefb7fc --- /dev/null +++ b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_response.dart @@ -0,0 +1,239 @@ +/// Response model for RAPID order transcription. +class RapidOrderTranscriptionResponse { + /// Creates a [RapidOrderTranscriptionResponse]. + const RapidOrderTranscriptionResponse({ + required this.transcript, + required this.confidence, + required this.language, + required this.warnings, + required this.model, + }); + + /// Factory to create [RapidOrderTranscriptionResponse] from JSON. + factory RapidOrderTranscriptionResponse.fromJson(Map json) { + return RapidOrderTranscriptionResponse( + transcript: json['transcript'] as String, + confidence: (json['confidence'] as num).toDouble(), + language: json['language'] as String, + warnings: List.from(json['warnings'] as Iterable), + model: json['model'] as String, + ); + } + + /// The transcribed text. + final String transcript; + + /// Confidence score (0.0 to 1.0). + final double confidence; + + /// Language code. + final String language; + + /// Any warnings from the transcription process. + final List warnings; + + /// The model name used for transcription. + final String model; + + /// Converts the response to a JSON map. + Map toJson() { + return { + 'transcript': transcript, + 'confidence': confidence, + 'language': language, + 'warnings': warnings, + 'model': model, + }; + } +} + +/// Response model for RAPID order parsing. +class RapidOrderParseResponse { + /// Creates a [RapidOrderParseResponse]. + const RapidOrderParseResponse({ + required this.parsed, + required this.missingFields, + required this.warnings, + required this.confidence, + required this.model, + }); + + /// Factory to create [RapidOrderParseResponse] from JSON. + factory RapidOrderParseResponse.fromJson(Map json) { + return RapidOrderParseResponse( + parsed: RapidOrderParsedData.fromJson( + json['parsed'] as Map, + ), + missingFields: List.from( + json['missingFields'] as Iterable, + ), + warnings: List.from(json['warnings'] as Iterable), + confidence: RapidOrderParseConfidence.fromJson( + json['confidence'] as Map, + ), + model: json['model'] as String, + ); + } + + /// The parsed order data. + final RapidOrderParsedData parsed; + + /// Fields that were identified as missing from the input. + final List missingFields; + + /// Any warnings from the parsing process. + final List warnings; + + /// Confidence scores. + final RapidOrderParseConfidence confidence; + + /// The model name used for parsing. + final String model; + + /// Converts the response to a JSON map. + Map toJson() { + return { + 'parsed': parsed.toJson(), + 'missingFields': missingFields, + 'warnings': warnings, + 'confidence': confidence.toJson(), + 'model': model, + }; + } +} + +/// Parsed data for a rapid order. +class RapidOrderParsedData { + /// Creates a [RapidOrderParsedData]. + const RapidOrderParsedData({ + required this.orderType, + required this.isRapid, + required this.positions, + required this.sourceText, + this.startAt, + this.endAt, + this.durationMinutes, + this.locationHint, + this.notes, + }); + + /// Factory to create [RapidOrderParsedData] from JSON. + factory RapidOrderParsedData.fromJson(Map json) { + return RapidOrderParsedData( + orderType: json['orderType'] as String, + isRapid: json['isRapid'] as bool, + positions: (json['positions'] as List) + .map( + (dynamic e) => + RapidOrderPosition.fromJson(e as Map), + ) + .toList(), + sourceText: json['sourceText'] as String, + startAt: json['startAt'] as String?, + endAt: json['endAt'] as String?, + durationMinutes: json['durationMinutes'] as int?, + locationHint: json['locationHint'] as String?, + notes: json['notes'] as String?, + ); + } + + /// The type of order (typically ONE_TIME). + final String orderType; + + /// Whether this is a rapid order. + final bool isRapid; + + /// The requested positions. + final List positions; + + /// The source text that was parsed. + final String sourceText; + + /// ISO datetime string for the start of the order. + final String? startAt; + + /// ISO datetime string for the end of the order. + final String? endAt; + + /// Duration in minutes. + final int? durationMinutes; + + /// Hint about the location. + final String? locationHint; + + /// Any notes captured from the input. + final String? notes; + + /// Converts to a JSON map. + Map toJson() { + return { + 'orderType': orderType, + 'isRapid': isRapid, + 'positions': positions.map((RapidOrderPosition e) => e.toJson()).toList(), + 'sourceText': sourceText, + if (startAt != null) 'startAt': startAt, + if (endAt != null) 'endAt': endAt, + if (durationMinutes != null) 'durationMinutes': durationMinutes, + if (locationHint != null) 'locationHint': locationHint, + if (notes != null) 'notes': notes, + }; + } +} + +/// A position within a rapid order. +class RapidOrderPosition { + /// Creates a [RapidOrderPosition]. + const RapidOrderPosition({required this.role, required this.count}); + + /// Factory to create [RapidOrderPosition] from JSON. + factory RapidOrderPosition.fromJson(Map json) { + return RapidOrderPosition( + role: json['role'] as String, + count: json['count'] as int, + ); + } + + /// The role name. + final String role; + + /// Number of people needed for this role. + final int count; + + /// Converts to a JSON map. + Map toJson() { + return {'role': role, 'count': count}; + } +} + +/// Confidence scores for a rapid order parse. +class RapidOrderParseConfidence { + /// Creates a [RapidOrderParseConfidence]. + const RapidOrderParseConfidence({ + required this.overall, + required this.fields, + }); + + /// Factory to create [RapidOrderParseConfidence] from JSON. + factory RapidOrderParseConfidence.fromJson(Map json) { + return RapidOrderParseConfidence( + overall: (json['overall'] as num).toDouble(), + fields: Map.from( + (json['fields'] as Map).map( + (String k, dynamic v) => + MapEntry(k, (v as num).toDouble()), + ), + ), + ); + } + + /// Overall confidence score (0.0 to 1.0). + final double overall; + + /// Confidence scores for specific fields. + final Map fields; + + /// Converts to a JSON map. + Map toJson() { + return {'overall': overall, 'fields': fields}; + } +} diff --git a/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_service.dart b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_service.dart new file mode 100644 index 00000000..5715d9ec --- /dev/null +++ b/apps/mobile/packages/core/lib/src/services/api_service/core_api_services/rapid_order/rapid_order_service.dart @@ -0,0 +1,70 @@ +import 'package:krow_domain/krow_domain.dart'; +import '../core_api_endpoints.dart'; +import 'rapid_order_response.dart'; + +/// Service for handling RAPID order operations (Transcription and Parsing). +class RapidOrderService extends BaseCoreService { + /// Creates a [RapidOrderService]. + RapidOrderService(super.api); + + /// Transcribes audio from a file to text for a RAPID order. + /// + /// [audioFileUri] is the URI of the audio file to transcribe. + /// [locale] is the optional locale hint (default: 'en-US'). + /// [promptHints] are optional domain hints to improve transcription quality. + Future transcribeAudio({ + required String audioFileUri, + String locale = 'en-US', + List? promptHints, + }) async { + final ApiResponse res = await action(() async { + return api.post( + CoreApiEndpoints.transcribeRapidOrder, + data: { + 'audioFileUri': audioFileUri, + 'locale': locale, + if (promptHints != null) 'promptHints': promptHints, + }, + ); + }); + + if (res.code.startsWith('2')) { + return RapidOrderTranscriptionResponse.fromJson( + res.data as Map, + ); + } + + throw Exception(res.message); + } + + /// Parses text into a structured RAPID order draft JSON. + /// + /// [text] is the input text to parse. + /// [locale] is the optional locale hint (default: 'en-US'). + /// [timezone] is the optional IANA timezone hint. + /// [now] is an optional current ISO datetime for reference. + Future parseText({ + required String text, + String locale = 'en-US', + String? timezone, + String? now, + }) async { + final ApiResponse res = await action(() async { + return api.post( + CoreApiEndpoints.parseRapidOrder, + data: { + 'text': text, + 'locale': locale, + if (timezone != null) 'timezone': timezone, + if (now != null) 'now': now, + }, + ); + }); + + if (res.code.startsWith('2')) { + return RapidOrderParseResponse.fromJson(res.data as Map); + } + + throw Exception(res.message); + } +}