From dc836f911a65aeac2f29bb7c21529fefeb850610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 6 Oct 2026 15:46:22 +0200 Subject: [PATCH] refactor(parser): normalize percentage value handling Refactor percentage value parsing to use a dedicated method for better readability and maintainability. Add tests to ensure legacy overflow percentages are normalized correctly. --- .../Parsers/IncoCompositionParser.php | 63 ++++++++++++++++--- .../Parsers/IncoCompositionParserTest.php | 19 ++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/Infra/Adapters/Parsers/IncoCompositionParser.php b/src/Infra/Adapters/Parsers/IncoCompositionParser.php index 3d98b83..f36e3d7 100644 --- a/src/Infra/Adapters/Parsers/IncoCompositionParser.php +++ b/src/Infra/Adapters/Parsers/IncoCompositionParser.php @@ -117,8 +117,8 @@ final readonly class IncoCompositionParser return new FreshProduceAndNuts( reference: $refFnl, portion: $porFnl, - freshFnlPercentage: new PercentageValue(self::getString($row, 'POURCENT_FNL_FRAIS', 'pourcent_fnl_frais')), - fruitsPercentage: new PercentageValue(self::getString($row, 'POURCENT_FRUITS', 'pourcent_fruits')), + freshFnlPercentage: self::toPercentageValue($row, 'POURCENT_FNL_FRAIS', 'pourcent_fnl_frais'), + fruitsPercentage: self::toPercentageValue($row, 'POURCENT_FRUITS', 'pourcent_fruits'), ); } @@ -128,14 +128,14 @@ final readonly class IncoCompositionParser public static function toReferenceIntakes(array $row): ReferenceIntakes { return new ReferenceIntakes( - energyE14: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_E14_P', 'pourcent_apport_ref_e14_p')), - energyKj: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_KJO_P', 'pourcent_apport_ref_kjo_p')), - fat: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_FAT_P', 'pourcent_apport_ref_fat_p')), - saturatedFat: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_FATSAT_P', 'pourcent_apport_ref_fatsat_p')), - carbohydrates: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_CHOVAL_P', 'pourcent_apport_ref_choval_p')), - sugars: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_SUGAR_P', 'pourcent_apport_ref_sugar_p')), - protein: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_PRO_P', 'pourcent_apport_ref_pro_p')), - salt: new PercentageValue(self::getString($row, 'POURCENT_APPORT_REF_SALTEQ_P', 'pourcent_apport_ref_salteq_p')), + energyE14: self::toPercentageValue($row, 'POURCENT_APPORT_REF_E14_P', 'pourcent_apport_ref_e14_p'), + energyKj: self::toPercentageValue($row, 'POURCENT_APPORT_REF_KJO_P', 'pourcent_apport_ref_kjo_p'), + fat: self::toPercentageValue($row, 'POURCENT_APPORT_REF_FAT_P', 'pourcent_apport_ref_fat_p'), + saturatedFat: self::toPercentageValue($row, 'POURCENT_APPORT_REF_FATSAT_P', 'pourcent_apport_ref_fatsat_p'), + carbohydrates: self::toPercentageValue($row, 'POURCENT_APPORT_REF_CHOVAL_P', 'pourcent_apport_ref_choval_p'), + sugars: self::toPercentageValue($row, 'POURCENT_APPORT_REF_SUGAR_P', 'pourcent_apport_ref_sugar_p'), + protein: self::toPercentageValue($row, 'POURCENT_APPORT_REF_PRO_P', 'pourcent_apport_ref_pro_p'), + salt: self::toPercentageValue($row, 'POURCENT_APPORT_REF_SALTEQ_P', 'pourcent_apport_ref_salteq_p'), portionAmount: self::getString($row, 'APPORT_PORT', 'apport_port', '100g'), ); } @@ -345,4 +345,47 @@ final readonly class IncoCompositionParser return is_scalar($val) ? (string) $val : $default; } + + /** + * @param array $row + */ + private static function toPercentageValue(array $row, string $key, string $fallbackKey = ''): PercentageValue + { + return new PercentageValue( + self::normalizeLegacyOverflowPercentage( + self::getString($row, $key, $fallbackKey) + ) + ); + } + + private static function normalizeLegacyOverflowPercentage(string $value): string + { + $trimmed = trim($value); + if ($trimmed === '') { + return $value; + } + + $numericCandidate = str_replace(',', '.', $trimmed); + if (! is_numeric($numericCandidate)) { + return $value; + } + + $numeric = (float) $numericCandidate; + if ($numeric <= 100.0) { + return $value; + } + + $normalized = $numeric / 10.0; + + return self::toCompactNumericString($normalized); + } + + private static function toCompactNumericString(float $value): string + { + if (fmod($value, 1.0) === 0.0) { + return (string) (int) $value; + } + + return rtrim(rtrim(number_format($value, 6, '.', ''), '0'), '.'); + } } diff --git a/tests/Unit/Infra/Adapters/Parsers/IncoCompositionParserTest.php b/tests/Unit/Infra/Adapters/Parsers/IncoCompositionParserTest.php index 97f1ef5..951daa4 100644 --- a/tests/Unit/Infra/Adapters/Parsers/IncoCompositionParserTest.php +++ b/tests/Unit/Infra/Adapters/Parsers/IncoCompositionParserTest.php @@ -145,6 +145,25 @@ describe('IncoCompositionParser', function (): void { ->and($score->grade)->toBe('B'); }); + it('normalizes legacy overflow percentages by dividing by 10', function (): void { + $row = [ + 'POURCENT_APPORT_REF_PRO_P' => '130', + 'POURCENT_APPORT_REF_FAT_P' => '105', + 'POURCENT_APPORT_REF_E14_P' => '10', + 'POURCENT_APPORT_REF_KJO_P' => '10', + 'POURCENT_APPORT_REF_FATSAT_P' => '14', + 'POURCENT_APPORT_REF_CHOVAL_P' => '6', + 'POURCENT_APPORT_REF_SUGAR_P' => '1', + 'POURCENT_APPORT_REF_SALTEQ_P' => '18', + 'APPORT_PORT' => '100g', + ]; + + $intakes = IncoCompositionParser::toReferenceIntakes($row); + + expect($intakes->protein->value)->toBe('13') + ->and($intakes->fat->value)->toBe('10.5'); + }); + it('parses minerals, vitamins, fatty acids, amino acids, and special compounds', function (): void { $row = [ 'NUT_CA' => '120',