Fix kJ-as-calories, API key leak, and empty abridged foods#10
Merged
Conversation
Three bugs found reviewing the library, each of which fails quietly and hands the caller a confident wrong answer. 1. A food's kilojoules could be reported as its calories. FDC lists energy several times per food under the same name: 1008 (kcal), 1062 (the same energy in kJ), and the Atwater variants 2047/2048. All were matched on the name alone and keyed as "calories", so whichever came last won. Ghee (fdc_id 171314) lists 900 kcal then 3766 kJ and analyzed as 3766 "calories" — which the CLI and HTML report printed with a kcal suffix. Energy is now matched on its id/number and unit, the plain Energy row beats the Atwater variants so order cannot decide the answer, and the kJ figure is kept under energy_kj instead of overwriting anything. 2. The API key travelled as a query parameter, and requests puts the full URL in its exception text — so the first connection blip wrote the key into the caller's traceback, logs and error tracker. It now goes in an X-Api-Key header (which api.data.gov accepts) and is redacted from error messages as a backstop. 3. format="abridged" returned a food with zero nutrients. Abridged rows inline the nutrient fields and carry no nutrient id; only the nested full-format shape was parsed and the rest were dropped in silence. Ghee came back with 0 nutrients instead of 18. All three are verified against the live FDC API. The 11 new tests fail on the current main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014avzoF5VnrUxwoy7m1aK6K
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three bugs found reviewing the library. Each one fails quietly and hands the caller a confident wrong answer, which for nutrition data is worse than an error. All three are verified against the live FDC API, and the 11 new tests fail on
main.1. A food's kilojoules could be reported as its calories
FDC lists energy several times per food, under the same name:
_get_nutrient_idmatched on the name alone, so every one of them was keyed ascaloriesand whichever came last in the list won. Clarified butter (fdc_id171314) lists 900 kcal and then 3766 kJ:analysis/cli.py:85andvisualization.py:141then printed that with akcalsuffix. It was never only about kJ, either — the Fuji apple carries both Atwater rows (64.66 and 58.20 kcal) and the winner was decided by list order.Energy is now matched on its nutrient id/number and unit: only kcal rows can become
calories, the plainEnergyrow is preferred over the Atwater variants so the result cannot depend on order, and the kJ figure is kept underenergy_kjrather than discarded.2. The API key leaked into exception messages
The key travelled as a query parameter, and
requestsembeds the full URL in its exception text:One connection blip and the key is in the caller's traceback, log files and error tracker. It now goes in an
X-Api-Keyheader — confirmed accepted by api.data.gov, which fronts FDC — so it never enters a URL, and it is redacted from error messages as a backstop. No API change:FdcClient(api_key=...)andFDC_API_KEYbehave exactly as before.3.
format="abridged"silently returned a food with no nutrientsAn abridged response inlines each nutrient's fields on the row and carries no nutrient id;
format="full"nests them under anutrientkey. Only the nested shape was parsed, and every abridged nutrient was dropped without a word:Anyone reaching for abridged to save bandwidth was computing nutrition from an empty list.
Tests
11 new regression tests, all of which fail on
main:tests/unit/test_analysis_energy.py— kJ never becomes calories, order does not decide the answer, kJ stays available under its own id, abridged foods (no nutrient id,KCALuppercase) still match.tests/unit/test_client.py— key sent as a header, absent from the query string, and redacted from both network errors and HTTP error bodies.tests/unit/test_models.py— both nutrient shapes parse; an abridged food keeps its nutrients.Full suite: 57 passed, 8 skipped (integration/django).
Found while reviewing the library after v0.1.10; #4 (
FdcApiErrorhas nostatus_codethough the docs tell users to branch on it) and #5 (onlyDriType.RDAworks) are left for a follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_014avzoF5VnrUxwoy7m1aK6K