代码在调试模式下可以正常运行,但在发布模式下却不能正常运行
下面是看起来在发布模式下不起作用的那段代码:
Future<void> _handleMatchIfAny() async {
if (!_shouldProcessMatch()) return;
if (_detController.finalImage == null) return;
final stockController = Get.find<StockController>();
if (checkoutMedications.length != stockController.medications.length) {
checkoutMedications = List.from(stockController.medications);
_initializeCache();
}
if (checkoutMedications.isEmpty) return;
_setProcessingState();
HapticFeedback.lightImpact();
try {
final fullImage = _detController.finalImage!;
setState(() => _processingStatus = 'Scan en cours...');
// [Bug 2 FIX] Use controller's scaleBoxToImage() which always divides
// by 192.0. The original code divided by model_input (a constant set
// to 320 for the checkout page), producing wrong crop boxes and broken
// OCR results whenever model_input != 192.
final scaledPpaBox = _detController.scaleBoxToImage(
_detController.bestPpaBoxModel!,
fullImage,
);
final croppedPpaImage = img.copyCrop(
fullImage,
x: scaledPpaBox.left.round(),
y: scaledPpaBox.top.round(),
width: scaledPpaBox.width.round(),
height: scaledPpaBox.height.round(),
);
img.Image? croppedLotImage;
if (_detController.bestLotBoxModel != null) {
final scaledLotBox = _detController.scaleBoxToImage(
_detController.bestLotBoxModel!,
fullImage,
);
croppedLotImage = img.copyCrop(
fullImage,
x: scaledLotBox.left.round(),
y: scaledLotBox.top.round(),
width: scaledLotBox.width.round(),
height: scaledLotBox.height.round(),
);
}
final String ppaOcrText = await OcrService.extractTextFromImgEnhanced(
croppedPpaImage,
);
HapticFeedback.lightImpact();
logger.d('📋 OCR PPA Text: $ppaOcrText');
final normalizedPpa = _normalizePpa(ppaOcrText);
final scored =
checkoutMedications
.map(
(m) => MapEntry(
m,
_scoreMedication(
_normalizedCache[m.id]!,
normalizedPpa,
ppaOcrText,
),
),
)
.where((e) => e.value >= 50.0)
.toList()
..sort((a, b) => b.value.compareTo(a.value));
if (scored.isEmpty) {
_resetHandling(isError: true);
return;
}
final bestScore = scored.first.value;
final topScorers = scored.where((e) => e.value == bestScore).toList();
// If only 1 candidate passed the threshold, use it directly —
// LOT matching is only useful when there are multiple candidates to
// differentiate between.
if (scored.length == 1 || (topScorers.length == 1 && bestScore >= 60.0)) {
final winner = scored.first.key;
logger.i(
'🎯 Single/confident match: ${winner.name} (${bestScore.toStringAsFixed(1)} pts)',
);
HapticFeedback.heavyImpact();
await _showChifaPlusCheckoutBottomSheet(context, winner);
_resetHandling();
return;
}
logger.i(
'LOT MATCHING: ${scored.length} candidates need LOT verification',
);
final filteredMedics = scored.map((e) => e.key).toList();
logger.i(
'🔍 Proceeding to LOT matching for ${filteredMedics.length} candidates...',
);
if (croppedLotImage == null) {
logger.w('⚠️ Need LOT matching but no LOT box detected! Aborting.');
_resetHandling(isError: true);
return;
}
final String lotOcrText = await OcrService.extractTextFromImgEnhanced(
croppedLotImage,
);
HapticFeedback.lightImpact();
logger.i('📋 OCR LOT Text: $lotOcrText');
final lotMatch = RegExp(
r'[Ll1][Oo0][Tt]\s*:?\s*([^\s\nFfEePpDd]+)',
caseSensitive: false,
).firstMatch(lotOcrText);
String sequencedLot = _normalizeLot(lotMatch?.group(1) ?? lotOcrText);
bool matchFound = false;
Medication? bestMatch;
double bestSimilarity = 0.0;
String bestStrategy = '';
for (final medic in filteredMedics) {
final medicLot = _normalizeLot(medic.lotNumber!);
final String lotOcrTextForDatePer = _normalizeLotForDatePeremption(
lotOcrText,
);
final String medicDatePerStartToEnd =
medic.expiryDate!.split('-').first +
medic.expiryDate!.split('-')[1];
final String normalizedMedicDatePerStartToEnd = medicDatePerStartToEnd
.replaceAll('20', '');
final String medicDatePerEndToStart =
medic.expiryDate!.split('-')[1] +
medic.expiryDate!.split('-').first;
final String normalizedMedicDatePerEndToStart = medicDatePerEndToStart
.replaceAll('20', '');
logger.i('normalized lot ocr for dateper : $lotOcrTextForDatePer');
// STRATEGY 1: Direct substring match
if (sequencedLot.contains(medicLot)) {
if (lotOcrTextForDatePer.contains(normalizedMedicDatePerEndToStart) ||
lotOcrTextForDatePer.contains(normalizedMedicDatePerStartToEnd)) {
bestMatch = medic;
bestSimilarity = 1.0;
bestStrategy = 'Direct substring';
logger.i('🎯 Direct match: LOT $medicLot in OCR');
break;
}
}
// STRATEGY 2: Sliding window fuzzy matching
final lotLength = medicLot.length;
final minWindow = (lotLength * 0.7).floor();
final maxWindow = (lotLength * 1.8).ceil();
for (int wSize = minWindow; wSize <= maxWindow; wSize++) {
if (wSize > sequencedLot.length || wSize < 1) continue;
for (int i = 0; i <= sequencedLot.length - wSize; i++) {
final window = sequencedLot.substring(i, i + wSize);
final similarity = window.similarityTo(medicLot);
if (similarity > 0.70 && similarity > bestSimilarity) {
if (lotOcrTextForDatePer.contains(
normalizedMedicDatePerEndToStart,
) ||
lotOcrTextForDatePer.contains(
normalizedMedicDatePerStartToEnd,
)) {
bestSimilarity = similarity;
bestMatch = medic;
bestStrategy = 'Sliding window ($wSize chars, pos $i)';
logger.i(
'🔍 Window: "$window" ≈ "$medicLot" (${(similarity * 100).toStringAsFixed(1)}%)',
);
}
}
}
}
// STRATEGY 3: Numeric sequence extraction
final ocrNumbers = RegExp(
r'\d+',
).allMatches(sequencedLot).map((m) => m.group(0)!).toList();
final medicNumbers = RegExp(
r'\d+',
).allMatches(medicLot).map((m) => m.group(0)!).toList();
for (final ocrNum in ocrNumbers) {
if (ocrNum.length < 3) continue;
for (final medicNum in medicNumbers) {
if (medicNum.length < 3) continue;
if (ocrNum.contains(medicNum) || medicNum.contains(ocrNum)) {
if (0.88 > bestSimilarity) {
if (lotOcrTextForDatePer.contains(
normalizedMedicDatePerEndToStart,
) ||
lotOcrTextForDatePer.contains(
normalizedMedicDatePerStartToEnd,
)) {
bestSimilarity = 0.88;
bestMatch = medic;
bestStrategy = 'Numeric contains';
logger.i('🔢 Numeric: "$ocrNum" ⊃ "$medicNum"');
}
}
}
final numSim = ocrNum.similarityTo(medicNum);
if (numSim > 0.75 && numSim > bestSimilarity) {
if (lotOcrTextForDatePer.contains(
normalizedMedicDatePerEndToStart,
) ||
lotOcrTextForDatePer.contains(
normalizedMedicDatePerStartToEnd,
)) {
bestSimilarity = numSim;
bestMatch = medic;
bestStrategy = 'Numeric fuzzy';
logger.i(
'🔢 Fuzzy: "$ocrNum" ≈ "$medicNum" (${(numSim * 100).toStringAsFixed(1)}%)',
);
}
}
}
}
// STRATEGY 4: Character n-grams (3–5)
for (int n = 3; n <= 5; n++) {
if (medicLot.length < n || sequencedLot.length < n) continue;
final medicNGrams = <String>{};
for (int i = 0; i <= medicLot.length - n; i++) {
medicNGrams.add(medicLot.substring(i, i + n));
}
int matches = 0;
for (int i = 0; i <= sequencedLot.length - n; i++) {
if (medicNGrams.contains(sequencedLot.substring(i, i + n))) {
matches++;
}
}
if (medicNGrams.isNotEmpty) {
final ngramSim = matches / medicNGrams.length;
if (ngramSim > 0.60 && ngramSim > bestSimilarity) {
if (lotOcrTextForDatePer.contains(
normalizedMedicDatePerEndToStart,
) ||
lotOcrTextForDatePer.contains(
normalizedMedicDatePerStartToEnd,
)) {
bestSimilarity = ngramSim;
bestMatch = medic;
bestStrategy = '$n-gram overlap';
logger.i(
'📊 $n-gram: $matches/${medicNGrams.length} (${(ngramSim * 100).toStringAsFixed(1)}%)',
);
}
}
}
}
}
if (bestMatch != null && bestSimilarity >= 0.65) {
matchFound = true;
HapticFeedback.heavyImpact();
await _showChifaPlusCheckoutBottomSheet(context, bestMatch);
}
if (!matchFound) {
_resetHandling(isError: true);
}
} catch (e, stacktrace) {
logger.e('CRASH in handleMatchIfAny: $e\n$stacktrace');
_resetHandling(isError: true);
}
}
解决方案
我已经修复了。问题在于我把 google_mlkit_text_recognition 声明为 dev_dependency,并放在 pubspec.yaml 中;这是我的错。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。