<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FoodWarz Team</title>
    <description>The latest articles on DEV Community by FoodWarz Team (@foodwarz).</description>
    <link>https://dev.to/foodwarz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4167877%2F722863da-3756-453a-b274-24d31e21ff92.png</url>
      <title>DEV Community: FoodWarz Team</title>
      <link>https://dev.to/foodwarz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/foodwarz"/>
    <language>en</language>
    <item>
      <title>Model the review step before saving AI output</title>
      <dc:creator>FoodWarz Team</dc:creator>
      <pubDate>Wed, 07 Oct 2026 05:52:56 +0000</pubDate>
      <link>https://dev.to/foodwarz/model-the-review-step-before-saving-ai-output-97p</link>
      <guid>https://dev.to/foodwarz/model-the-review-step-before-saving-ai-output-97p</guid>
      <description>&lt;p&gt;An AI response can identify a meal and suggest nutrition values. That response still leaves a product decision unresolved: which information does the person want in their diary?&lt;/p&gt;

&lt;p&gt;If the response is treated as a saved meal immediately, a successful model request can change a daily total before the person has checked the food or portion. A review screen added afterwards cannot restore the distinction the data model has already lost.&lt;/p&gt;

&lt;p&gt;FoodWarz's domain vocabulary separates an &lt;strong&gt;analysis&lt;/strong&gt; from a &lt;strong&gt;meal&lt;/strong&gt;. Analysis is reviewable interpretation. A meal is a saved diary snapshot containing the amount, nutrition and source information the user chose to record. This post uses that boundary to describe a small modelling approach that also applies to other products which turn generated suggestions into durable records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each object one job
&lt;/h2&gt;

&lt;p&gt;The following TypeScript is an illustrative sketch, not FoodWarz's production schema. Identifiers, validation and storage details are deliberately omitted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Nutrition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;protein&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;carbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AnalysisResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analysis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;suggestedNutrition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Nutrition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReviewDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnalysisResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;editedNutrition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Nutrition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SavedMeal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nutritionSnapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Nutrition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PlannedDish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;planned&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;proposedNutrition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Nutrition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful part is the separate names. A function that accepts a &lt;code&gt;SavedMeal&lt;/code&gt; should not also accept an &lt;code&gt;AnalysisResult&lt;/code&gt; simply because both happen to contain calories. A production boundary needs runtime validation too: types cannot establish that a client request is valid or that the person confirmed it.&lt;/p&gt;

&lt;p&gt;A simplified flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analysis completes → open a review draft
User edits         → update the draft
User confirms      → validate and persist a meal
Persistence succeeds → include that saved meal in diary totals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The draft can also be abandoned. That must remain an ordinary outcome of analysis, with no new consumed-food record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write invariants before wiring UI events
&lt;/h2&gt;

&lt;p&gt;These invariants make event handlers easier to review:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Expected effect&lt;/th&gt;
&lt;th&gt;Boundary to preserve&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Analysis completes&lt;/td&gt;
&lt;td&gt;A suggestion becomes available for review&lt;/td&gt;
&lt;td&gt;Analysis completion alone does not add consumed food&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A portion is edited&lt;/td&gt;
&lt;td&gt;The review draft changes&lt;/td&gt;
&lt;td&gt;The original analysis remains distinguishable from the correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A meal is confirmed and saved&lt;/td&gt;
&lt;td&gt;A reviewed snapshot enters the diary&lt;/td&gt;
&lt;td&gt;The saved estimate retains its basis and provenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A source recipe or catalogue item changes&lt;/td&gt;
&lt;td&gt;The source can be used for future entries&lt;/td&gt;
&lt;td&gt;An existing historical snapshot is not silently recalculated from it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A dish is added to a plan&lt;/td&gt;
&lt;td&gt;Planning state changes&lt;/td&gt;
&lt;td&gt;Planned food does not enter consumption totals&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The snapshot rule is easy to miss. Suppose a catalogue item is corrected tomorrow. If yesterday's diary reads its nutrition by joining the current catalogue row every time, yesterday's total can change without a diary edit. Store the reviewed nutrition on the historical record and keep the source reference for traceability.&lt;/p&gt;

&lt;p&gt;A deliberate correction to an existing meal is a separate user action. Keeping a snapshot does not mean preventing the owner from correcting a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep correction evidence useful
&lt;/h2&gt;

&lt;p&gt;FoodWarz's saved-meal contract distinguishes the original analysis from the reviewed estimate. Corrections can be represented as user-corrected values while retaining links to the analysis context. That supports a concrete debugging question: what did the system suggest, and what was actually saved?&lt;/p&gt;

&lt;p&gt;An apparently plausible calorie total is insufficient evidence. The amount, unit and nutrition basis matter together. A value per 100 grams has a different meaning from a total for the eaten portion. Source and uncertainty also need to survive the transition into the diary.&lt;/p&gt;

&lt;p&gt;This is a contract to validate at the boundary. It is not proof that every upstream interpretation is correct, and review does not turn an estimate into an exact measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the transitions that could change history
&lt;/h2&gt;

&lt;p&gt;Useful regression scenarios start from actions a person can take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete an analysis, close review, and verify that no meal was added.&lt;/li&gt;
&lt;li&gt;Change the portion during review and verify that the saved snapshot contains the reviewed result.&lt;/li&gt;
&lt;li&gt;Change a reusable source record and verify that an existing meal keeps its recorded nutrition.&lt;/li&gt;
&lt;li&gt;Create or edit a planned dish and verify that consumption totals stay unchanged until an ordinary meal is saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These scenarios check observable meaning rather than the names of internal helper functions. They are suggested coverage for this model, not a report of a production test run.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.foodwarz.app/en/food-diary" rel="noopener noreferrer"&gt;FoodWarz diary overview&lt;/a&gt; shows the user-facing context for this engineering note. The modelling lesson is to decide when a suggestion becomes a record, then make that decision explicit in types, validation and state transitions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: Published by the FoodWarz team. This article was prepared with AI assistance using the project's domain documentation and selected code contracts. The code above is illustrative.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
