-
-
Notifications
You must be signed in to change notification settings - Fork 77
feat: 1072 - Add Nutri-Score details #1079
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
04ce8c3
b070387
04d4dd2
52d541f
7723586
c221784
89a9358
6b3941c
1cf3598
6a8efad
073b70a
06a56c8
5f21cd3
37c533f
7860eab
e191580
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,135 @@ | ||||
| import 'package:openfoodfacts/openfoodfacts.dart'; | ||||
|
|
||||
| enum NutriScoreCategory2021 { | ||||
| general, | ||||
|
||||
| general, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: 'general' is an official Nutri-Score category that influences how the Nutri-Score is computed. It does not make sense to remove it.
https://www.eurofins.de/food-analysis/other-services/nutri-score/
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| general, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove general? This is an official Nutri-Score category that influences how the Nutri-Score is computed.
https://www.eurofins.de/food-analysis/other-services/nutri-score/
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"isUnknown"? What about hasMissingData?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isNotApplicable could also have missing data, e.g. when missing nutrients but having category which is not applicable.
I prefer to have isComputed, isNotApplicable and isUnknown to be mutually exclusive. (but I realize that this is currently not the case with this implementation - I need to fix it).
That's why I initially had the enum status {computed, notApplicable, unknown} to clearly distinguid between the 3 states - currently this information is distributed in the JSON data between nutriscore_computed field and not-applicable+ unknown values in grade. :-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@goerlitz I think you're trying to be too smart here, too related to your app needs.
If the "raw" data says it's computed, not applicable, with missing data, who are you to decide otherwise or which one has the priority?
Just keep it straightforward.
Then, in your own app, add an extension if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that makes sense. It's probably better not to introduce too much logic at this level and instead handle it in the app layer.
That said, part of the motivation behind the abstraction is that the API model is quite unclear and loosely documented. It's often hard to determine the intended logic (e.g., precedence, or how to interpret certain field combinations), which makes implementations prone to guesswork and inconsistency. I was trying to guard against that.
I'll adjust the implementation accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reintroduced the NutriScoreStatus enum (computed, notApplicable, unknown) for these reasons:
-
Matches the raw API: The
gradefield conflates status and value ('unknown','not-applicable','a'–'e'). The enum reflects these states while clearly separating status from actual grades. -
Avoids ambiguity: The previous
isComputed,isNotApplicable, andhasMissingDataflags could overlap. The enum enforces mutual exclusivity by design. -
Improves clarity: A single status is easier to reason about than multiple booleans with implicit precedence rules.
The raw data remains accessible; this enum is a clean abstraction on top for consistent downstream use.
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
goerlitz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import 'package:json_annotation/json_annotation.dart'; | ||
| import '../../interface/json_object.dart'; | ||
| import '../../utils/json_helper.dart'; | ||
|
|
||
| part 'nutriscore_data_2021.g.dart'; | ||
|
|
||
| /// Detailed data of NutriScore version 2021. | ||
| @JsonSerializable() | ||
| class NutriScoreData2021 extends JsonObject { | ||
| @JsonKey( | ||
| name: 'is_beverage', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isBeverage; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_cheese', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isCheese; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_fat', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isFat; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_water', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isWater; | ||
|
|
||
| @JsonKey(name: 'negative_points') | ||
| int? negativePoints; | ||
|
|
||
| @JsonKey(name: 'positive_points') | ||
| int? positivePoints; | ||
|
|
||
| NutriScoreData2021(); | ||
|
|
||
| factory NutriScoreData2021.fromJson(Map<String, dynamic> json) => | ||
| _$NutriScoreData2021FromJson(json); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() => _$NutriScoreData2021ToJson(this); | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import 'package:json_annotation/json_annotation.dart'; | ||
| import '../../interface/json_object.dart'; | ||
| import '../../utils/json_helper.dart'; | ||
|
|
||
| part 'nutriscore_data_2023.g.dart'; | ||
|
|
||
| /// Detailed data of NutriScore version 2023. | ||
| @JsonSerializable() | ||
| class NutriScoreData2023 extends JsonObject { | ||
| @JsonKey( | ||
| name: 'count_proteins', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonHelper.boolFromJSON, | ||
| ) | ||
| bool? countProteins; | ||
|
|
||
| @JsonKey(name: 'count_proteins_reason') | ||
| String? countProteinsReason; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_beverage', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isBeverage; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_cheese', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isCheese; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_fat_oil_nuts_seeds', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isFatOilNutsSeeds; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_red_meat_product', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isRedMeatProduct; | ||
|
|
||
| @JsonKey( | ||
| name: 'is_water', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonObject.parseBool, | ||
| ) | ||
| bool? isWater; | ||
|
|
||
| @JsonKey(name: 'negative_points') | ||
| int? negativePoints; | ||
|
|
||
| @JsonKey(name: 'negative_points_max') | ||
| int? negativePointsMax; | ||
|
|
||
| @JsonKey(name: 'positive_points') | ||
| int? positivePoints; | ||
|
|
||
| @JsonKey(name: 'positive_points_max') | ||
| int? positivePointsMax; | ||
|
|
||
| NutriScoreData2023(); | ||
|
|
||
| factory NutriScoreData2023.fromJson(Map<String, dynamic> json) => | ||
| _$NutriScoreData2023FromJson(json); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() => _$NutriScoreData2023ToJson(this); | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import 'package:json_annotation/json_annotation.dart'; | ||
| import '../../interface/json_object.dart'; | ||
| import '../../utils/json_helper.dart'; | ||
| import 'nutriscore_data_2021.dart'; | ||
|
|
||
| part 'nutriscore_detail_2021.g.dart'; | ||
|
|
||
| /// Data of NutriScore version 2021. | ||
| @JsonSerializable(explicitToJson: true) | ||
| class NutriScoreDetail2021 extends JsonObject { | ||
| @JsonKey() | ||
| String? grade; | ||
|
|
||
| @JsonKey() | ||
| int? score; | ||
|
|
||
| @JsonKey() | ||
| NutriScoreData2021? data; | ||
|
|
||
| @JsonKey(name: 'not_applicable_category') | ||
| String? notApplicableCategory; | ||
|
|
||
| @JsonKey( | ||
| name: 'category_available', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonHelper.boolFromJSON, | ||
| ) | ||
| bool? categoryAvailable; | ||
|
|
||
| @JsonKey( | ||
| name: 'nutrients_available', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonHelper.boolFromJSON, | ||
| ) | ||
| bool? nutrientsAvailable; | ||
|
|
||
| @JsonKey( | ||
| name: 'nutriscore_applicable', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonHelper.boolFromJSON, | ||
| ) | ||
| bool? nutriScoreApplicable; | ||
|
|
||
| @JsonKey( | ||
| name: 'nutriscore_computed', | ||
| toJson: JsonHelper.boolToJSON, | ||
| fromJson: JsonHelper.boolFromJSON, | ||
| ) | ||
| bool? nutriScoreComputed; | ||
|
|
||
| NutriScoreDetail2021(); | ||
|
|
||
| factory NutriScoreDetail2021.fromJson(Map<String, dynamic> json) => | ||
| _$NutriScoreDetail2021FromJson(json); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() => _$NutriScoreDetail2021ToJson(this); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.