代码在调试模式下可以正常运行,但在发布模式下却不能正常运行

移动开发 2026-07-10

下面是看起来在发布模式下不起作用的那段代码:

  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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章