feat: Add RAPID order transcription and parsing services with associated response models and API endpoints.
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -29,6 +29,9 @@ class CoreModule extends Module {
|
||||
() => VerificationService(i.get<BaseApiService>()),
|
||||
);
|
||||
i.addSingleton<LlmService>(() => LlmService(i.get<BaseApiService>()));
|
||||
i.addSingleton<RapidOrderService>(
|
||||
() => RapidOrderService(i.get<BaseApiService>()),
|
||||
);
|
||||
|
||||
// 4. Register Device dependency
|
||||
i.addSingleton<ImagePicker>(() => ImagePicker());
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
return RapidOrderTranscriptionResponse(
|
||||
transcript: json['transcript'] as String,
|
||||
confidence: (json['confidence'] as num).toDouble(),
|
||||
language: json['language'] as String,
|
||||
warnings: List<String>.from(json['warnings'] as Iterable<dynamic>),
|
||||
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<String> warnings;
|
||||
|
||||
/// The model name used for transcription.
|
||||
final String model;
|
||||
|
||||
/// Converts the response to a JSON map.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'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<String, dynamic> json) {
|
||||
return RapidOrderParseResponse(
|
||||
parsed: RapidOrderParsedData.fromJson(
|
||||
json['parsed'] as Map<String, dynamic>,
|
||||
),
|
||||
missingFields: List<String>.from(
|
||||
json['missingFields'] as Iterable<dynamic>,
|
||||
),
|
||||
warnings: List<String>.from(json['warnings'] as Iterable<dynamic>),
|
||||
confidence: RapidOrderParseConfidence.fromJson(
|
||||
json['confidence'] as Map<String, dynamic>,
|
||||
),
|
||||
model: json['model'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// The parsed order data.
|
||||
final RapidOrderParsedData parsed;
|
||||
|
||||
/// Fields that were identified as missing from the input.
|
||||
final List<String> missingFields;
|
||||
|
||||
/// Any warnings from the parsing process.
|
||||
final List<String> warnings;
|
||||
|
||||
/// Confidence scores.
|
||||
final RapidOrderParseConfidence confidence;
|
||||
|
||||
/// The model name used for parsing.
|
||||
final String model;
|
||||
|
||||
/// Converts the response to a JSON map.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'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<String, dynamic> json) {
|
||||
return RapidOrderParsedData(
|
||||
orderType: json['orderType'] as String,
|
||||
isRapid: json['isRapid'] as bool,
|
||||
positions: (json['positions'] as List<dynamic>)
|
||||
.map(
|
||||
(dynamic e) =>
|
||||
RapidOrderPosition.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.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<RapidOrderPosition> 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<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return <String, dynamic>{'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<String, dynamic> json) {
|
||||
return RapidOrderParseConfidence(
|
||||
overall: (json['overall'] as num).toDouble(),
|
||||
fields: Map<String, double>.from(
|
||||
(json['fields'] as Map<String, dynamic>).map(
|
||||
(String k, dynamic v) =>
|
||||
MapEntry<String, double>(k, (v as num).toDouble()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Overall confidence score (0.0 to 1.0).
|
||||
final double overall;
|
||||
|
||||
/// Confidence scores for specific fields.
|
||||
final Map<String, double> fields;
|
||||
|
||||
/// Converts to a JSON map.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{'overall': overall, 'fields': fields};
|
||||
}
|
||||
}
|
||||
@@ -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<RapidOrderTranscriptionResponse> transcribeAudio({
|
||||
required String audioFileUri,
|
||||
String locale = 'en-US',
|
||||
List<String>? promptHints,
|
||||
}) async {
|
||||
final ApiResponse res = await action(() async {
|
||||
return api.post(
|
||||
CoreApiEndpoints.transcribeRapidOrder,
|
||||
data: <String, dynamic>{
|
||||
'audioFileUri': audioFileUri,
|
||||
'locale': locale,
|
||||
if (promptHints != null) 'promptHints': promptHints,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (res.code.startsWith('2')) {
|
||||
return RapidOrderTranscriptionResponse.fromJson(
|
||||
res.data as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
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<RapidOrderParseResponse> 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: <String, dynamic>{
|
||||
'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<String, dynamic>);
|
||||
}
|
||||
|
||||
throw Exception(res.message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user