<?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: tosane932</title>
    <description>The latest articles on DEV Community by tosane932 (@tosane932).</description>
    <link>https://dev.to/tosane932</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%2F4044609%2F1c89177e-ec1e-4634-8d68-81ba7993e406.jpg</url>
      <title>DEV Community: tosane932</title>
      <link>https://dev.to/tosane932</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tosane932"/>
    <language>en</language>
    <item>
      <title>Existing DB Works, Empty DB Fails — Repairing a Broken Flask-Migrate History</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sat, 08 Aug 2026 01:01:59 +0000</pubDate>
      <link>https://dev.to/tosane932/existing-db-works-empty-db-fails-repairing-a-broken-flask-migrate-history-220m</link>
      <guid>https://dev.to/tosane932/existing-db-works-empty-db-fails-repairing-a-broken-flask-migrate-history-220m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While reviewing the migration history of a sales management application built with Flask and PostgreSQL, I discovered a serious problem.&lt;/p&gt;

&lt;p&gt;The application worked normally with my existing local database.&lt;/p&gt;

&lt;p&gt;However, with a completely fresh PostgreSQL database, the current migration history could not build the required tables from scratch.&lt;/p&gt;

&lt;p&gt;The reason was simple but important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first migration did not create the tables. It started by adding a column to a table that was assumed to already exist.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Empty PostgreSQL database
        ↓
Add is_active column to products
        ↓
products does not exist
        ↓
Migration fails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article records the full process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How I discovered the problem&lt;/li&gt;
&lt;li&gt;How I investigated the migration history&lt;/li&gt;
&lt;li&gt;How I added a missing initial migration&lt;/li&gt;
&lt;li&gt;How I verified migration from an empty database&lt;/li&gt;
&lt;li&gt;How I verified that existing databases would not be damaged&lt;/li&gt;
&lt;li&gt;What I paid attention to while implementing and testing the fix&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Investigating and Verifying the Problem with Codex
&lt;/h2&gt;

&lt;p&gt;I used Codex in VS Code during this investigation and repair.&lt;/p&gt;

&lt;p&gt;However, I did not hand the entire task over to Codex.&lt;/p&gt;

&lt;p&gt;Instead, I divided the work into separate stages and restricted what Codex was allowed to do at each stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Static investigation
        ↓
Design the repair
        ↓
Implement only the approved migration changes
        ↓
Static verification
        ↓
Dynamic test with an empty database
        ↓
Test against a clone of the existing database
        ↓
Final verification
        ↓
Commit and push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each stage, I explicitly defined conditions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not modify files during the investigation stage&lt;/li&gt;
&lt;li&gt;Limit implementation to exactly two migration files&lt;/li&gt;
&lt;li&gt;Do not directly migrate the normal local database&lt;/li&gt;
&lt;li&gt;Use a different Docker Compose project name for testing&lt;/li&gt;
&lt;li&gt;Test the existing database only through a clone created with &lt;code&gt;pg_dump&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Do not automatically fix additional problems that are discovered&lt;/li&gt;
&lt;li&gt;Allow commit and push only after final verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used Codex for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspecting migration history&lt;/li&gt;
&lt;li&gt;Showing diffs&lt;/li&gt;
&lt;li&gt;Running verification commands&lt;/li&gt;
&lt;li&gt;Organizing the results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I made the final decisions about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which repair strategy to use&lt;/li&gt;
&lt;li&gt;Safety conditions around databases&lt;/li&gt;
&lt;li&gt;Which resources could be deleted&lt;/li&gt;
&lt;li&gt;Whether the migration was safe enough to commit and push&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One lesson from this work was that AI coding assistance is not only about generating code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is important to define the allowed change scope, prohibited operations, verification conditions, and stopping conditions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;p&gt;The main stack is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;Flask-SQLAlchemy&lt;/li&gt;
&lt;li&gt;Flask-Migrate&lt;/li&gt;
&lt;li&gt;Alembic&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;pytest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the Docker container starts, migrations are applied before Gunicorn starts.&lt;/p&gt;

&lt;p&gt;Conceptually, the startup process looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;flask db upgrade &amp;amp;&amp;amp; gunicorn ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, if the migration fails, Gunicorn is never started.&lt;/p&gt;

&lt;p&gt;That means the web application itself cannot start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Discovering the Problem
&lt;/h2&gt;

&lt;p&gt;When I checked &lt;code&gt;migrations/versions/&lt;/code&gt;, only one migration file existed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;043c481b4069_add_is_active_to_products.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file was the first revision in the migration history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;043c481b4069&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;down_revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, its &lt;code&gt;upgrade()&lt;/code&gt; function did not create the &lt;code&gt;products&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;It only added the &lt;code&gt;is_active&lt;/code&gt; column to an already existing &lt;code&gt;products&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch_alter_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;batch_op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;batch_op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;is_active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;true&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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;In other words, there was no migration that created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;products&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;daily_sales&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I searched the entire project and found no equivalent table-creation logic such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily_sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The migration history was incomplete.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Did the Existing Database Still Work?
&lt;/h2&gt;

&lt;p&gt;The existing database had most likely been created through another method before Flask-Migrate was introduced.&lt;/p&gt;

&lt;p&gt;The historical sequence was probably something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products and daily_sales created outside Alembic
        ↓
Flask-Migrate introduced
        ↓
is_active added to products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the tables already existed, the existing database could apply the column-addition migration successfully.&lt;/p&gt;

&lt;p&gt;A fresh database was different.&lt;/p&gt;

&lt;p&gt;The first migration effectively tried to execute something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But an empty database had no &lt;code&gt;products&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;The failure therefore looked approximately like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products does not exist
        ↓
First migration fails
        ↓
flask db upgrade exits with a non-zero status
        ↓
Gunicorn does not start
        ↓
The web application does not start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the key issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The application worked because the existing database already contained history that Alembic itself could not reproduce.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Current Models
&lt;/h2&gt;

&lt;p&gt;The application currently contains two models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Product
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;true&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;h3&gt;
  
  
  DailySales
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products.id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Repair Strategies I Considered
&lt;/h2&gt;

&lt;p&gt;I considered several approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add an Initial Migration Before the Existing Revision
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create initial tables
        ↓
Add is_active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This preserves the existing history while adding the missing foundation.&lt;/p&gt;

&lt;p&gt;It creates a natural migration chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rewrite the Existing First Migration
&lt;/h3&gt;

&lt;p&gt;Another option would be to change the existing revision so that it creates all current tables directly.&lt;/p&gt;

&lt;p&gt;However, that would significantly change the meaning of a migration that had already been applied.&lt;/p&gt;

&lt;p&gt;That would make the historical record less trustworthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create a Conditional Migration
&lt;/h3&gt;

&lt;p&gt;Another possibility would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If products does not exist:
    create it
else:
    add the column
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could support multiple database states.&lt;/p&gt;

&lt;p&gt;However, it would introduce more state-dependent behavior and make verification more complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use &lt;code&gt;db.create_all()&lt;/code&gt; and &lt;code&gt;stamp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Another approach would be to create tables directly from the models and then use Alembic only to mark the database as being at a particular revision.&lt;/p&gt;

&lt;p&gt;This could solve the immediate problem quickly.&lt;/p&gt;

&lt;p&gt;However, it could create divergence between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The models&lt;/li&gt;
&lt;li&gt;The actual database&lt;/li&gt;
&lt;li&gt;The migration history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I therefore did not use this approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Approach I Chose
&lt;/h2&gt;

&lt;p&gt;I chose to insert an initial migration at the beginning of the history.&lt;/p&gt;

&lt;p&gt;The repaired migration chain became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base
        ↓
b7e2c4a91f30 create products and daily_sales
        ↓
043c481b4069 add is_active to products
        ↓
head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allowed the migration history to tell the actual story:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the initial tables&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;is_active&lt;/code&gt; later&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Implementing the Initial Migration
&lt;/h2&gt;

&lt;p&gt;I added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;migrations/versions/b7e2c4a91f30_create_initial_tables.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The migration looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;create initial products and daily_sales tables

Revision ID: b7e2c4a91f30
Revises:
Create Date: 2026-08-06
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;alembic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;


&lt;span class="n"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b7e2c4a91f30&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;down_revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;branch_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PrimaryKeyConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily_sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKeyConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products.id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PrimaryKeyConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;downgrade&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily_sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Table Creation Order Matters
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;daily_sales.product_id&lt;/code&gt; references &lt;code&gt;products.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, the upgrade must create the tables in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. products
2. daily_sales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downgrade must remove them in reverse order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. daily_sales
2. products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;products&lt;/code&gt; were dropped first, the foreign key from &lt;code&gt;daily_sales&lt;/code&gt; could prevent the operation.&lt;/p&gt;

&lt;p&gt;This is a small detail, but an important one when creating migration history manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Did Not Put &lt;code&gt;is_active&lt;/code&gt; in the Initial Migration
&lt;/h2&gt;

&lt;p&gt;I deliberately did &lt;strong&gt;not&lt;/strong&gt; create &lt;code&gt;is_active&lt;/code&gt; in the initial migration.&lt;/p&gt;

&lt;p&gt;The history remained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base revision
└── Create products without is_active
        ↓
Existing revision
└── Add is_active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This preserved the historical meaning of the existing revision.&lt;/p&gt;

&lt;p&gt;The initial migration alone does not need to match the current model.&lt;/p&gt;

&lt;p&gt;What matters is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Applying every revision through &lt;code&gt;head&lt;/code&gt; should produce the current model structure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the goal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Changing the Existing Revision
&lt;/h2&gt;

&lt;p&gt;In the existing &lt;code&gt;043c481b4069&lt;/code&gt; migration, I changed only its revision relationship.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;043c481b4069&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;down_revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;043c481b4069&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;down_revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b7e2c4a91f30&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also updated the &lt;code&gt;Revises&lt;/code&gt; field in its docstring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-Revises:
&lt;/span&gt;&lt;span class="gi"&gt;+Revises: b7e2c4a91f30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; change the actual &lt;code&gt;upgrade()&lt;/code&gt; or &lt;code&gt;downgrade()&lt;/code&gt; logic.&lt;/p&gt;

&lt;p&gt;The original operation remained intact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Did Not Add a &lt;code&gt;server_default&lt;/code&gt; to &lt;code&gt;quantity&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;DailySales.quantity&lt;/code&gt; model has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a Python-side SQLAlchemy default.&lt;/p&gt;

&lt;p&gt;It is not a database-side default.&lt;/p&gt;

&lt;p&gt;A database default would instead be represented using something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I intentionally did not add that to the migration.&lt;/p&gt;

&lt;p&gt;Doing so would have introduced a database-level behavior that the current model did not define.&lt;/p&gt;

&lt;p&gt;I wanted the migration to reproduce the actual intended schema, not silently add extra behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static Verification Before Touching a Database
&lt;/h2&gt;

&lt;p&gt;Before starting PostgreSQL, I checked the syntax of both migration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PYTHONPYCACHEPREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/sales_data_app_pycompile &lt;span class="se"&gt;\&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; py_compile &lt;span class="se"&gt;\&lt;/span&gt;
migrations/versions/b7e2c4a91f30_create_initial_tables.py &lt;span class="se"&gt;\&lt;/span&gt;
migrations/versions/043c481b4069_add_is_active_to_products.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was successful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exit code: 0
Syntax errors: none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also confirmed that the revision chain formed a single path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base
        ↓
b7e2c4a91f30
        ↓
043c481b4069
        ↓
head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after these static checks did I move on to database testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing a Completely Empty Database
&lt;/h2&gt;

&lt;p&gt;I did not want to affect my normal local database.&lt;/p&gt;

&lt;p&gt;Instead, I created an isolated environment by changing the Docker Compose project name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; sales_data_app_migration_test &lt;span class="se"&gt;\&lt;/span&gt;
  up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a different Compose project name creates separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;Volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal environment:
sales_data_app_postgres_data

Migration test environment:
sales_data_app_migration_test_postgres_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test PostgreSQL database was completely empty.&lt;/p&gt;

&lt;p&gt;The startup logs showed the migrations running in the expected order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running upgrade  -&amp;gt; b7e2c4a91f30,
create initial products and daily_sales tables

Running upgrade b7e2c4a91f30 -&amp;gt; 043c481b4069,
add is_active to products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gunicorn then started normally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting gunicorn 26.0.0
Listening at: http://0.0.0.0:5000
Booting worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP request also succeeded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 text/html; charset=utf-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that the application could now start from a completely empty PostgreSQL database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema Created from the Empty Database
&lt;/h2&gt;

&lt;p&gt;Three tables were created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alembic_version
products
daily_sales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;products&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;NULL&lt;/th&gt;
&lt;th&gt;DB Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;ID sequence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;year&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;month&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_active&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;daily_sales&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;NULL&lt;/th&gt;
&lt;th&gt;DB Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;ID sequence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;product_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;date&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;quantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The foreign key was also created as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily_sales.product_id
        ↓
products.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delete and update behavior remained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ON DELETE: NO ACTION
ON UPDATE: NO ACTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;ON DELETE CASCADE&lt;/code&gt; behavior was added because it does not exist in the model.&lt;/p&gt;

&lt;p&gt;I also verified that the migration did not introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extra UNIQUE constraints&lt;/li&gt;
&lt;li&gt;CHECK constraints&lt;/li&gt;
&lt;li&gt;Model-independent indexes&lt;/li&gt;
&lt;li&gt;A database-side default for &lt;code&gt;quantity&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final revision was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;043c481b4069
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Verifying an Existing Database
&lt;/h2&gt;

&lt;p&gt;The next question was more dangerous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens to a database that has already reached &lt;code&gt;043c481b4069&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I needed to confirm that the newly inserted ancestor migration would &lt;strong&gt;not&lt;/strong&gt; suddenly run against the existing database.&lt;/p&gt;

&lt;p&gt;I did not run this experiment directly against the real local database.&lt;/p&gt;

&lt;p&gt;Instead, I used the following process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start the normal database in read-only mode
        ↓
Create a pg_dump
        ↓
Stop the normal database
        ↓
Restore the dump into a separate PostgreSQL environment
        ↓
Run flask db upgrade only against the cloned database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The normal web container was not started during the dump process.&lt;/p&gt;

&lt;p&gt;I also used a read-only PostgreSQL setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PGOPTIONS=-c default_transaction_read_only=on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Test the migration against data that behaves like the real database, without using the real database.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Running &lt;code&gt;upgrade&lt;/code&gt; Against the Cloned Database
&lt;/h2&gt;

&lt;p&gt;The cloned database initially contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alembic_version = 043c481b4069
products = 16 rows
daily_sales = 16 rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran the migration only against this cloned database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; sales_data_app_existing_migration_test &lt;span class="se"&gt;\&lt;/span&gt;
  run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--no-deps&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  web flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command completed successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exit code: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Importantly, the logs did &lt;strong&gt;not&lt;/strong&gt; contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running upgrade -&amp;gt; b7e2c4a91f30
create initial products and daily_sales tables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There were also no:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;operations and no duplicate-table errors.&lt;/p&gt;

&lt;p&gt;Alembic correctly treated the database as already being at &lt;code&gt;head&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The newly inserted ancestor revision was &lt;strong&gt;not&lt;/strong&gt; executed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparing the Database Before and After &lt;code&gt;upgrade&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I compared the cloned database before and after running &lt;code&gt;flask db upgrade&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The comparison included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alembic_version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Table list&lt;/li&gt;
&lt;li&gt;Column names&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;li&gt;NULL constraints&lt;/li&gt;
&lt;li&gt;Database defaults&lt;/li&gt;
&lt;li&gt;Primary keys&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;li&gt;UNIQUE constraints&lt;/li&gt;
&lt;li&gt;CHECK constraints&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Sequence states&lt;/li&gt;
&lt;li&gt;Every row in &lt;code&gt;products&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Every row in &lt;code&gt;daily_sales&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I exported all rows as CSV ordered by primary key and compared them using both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and SHA-256 hashes.&lt;/p&gt;

&lt;p&gt;Everything matched exactly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Revision&lt;/td&gt;
&lt;td&gt;&lt;code&gt;043c481b4069&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;043c481b4069&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;products&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 rows&lt;/td&gt;
&lt;td&gt;16 rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;daily_sales&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 rows&lt;/td&gt;
&lt;td&gt;16 rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constraints&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexes&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequences&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All data&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This confirmed both migration paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Empty DB:
Base revision runs

Existing DB:
Base revision does not run again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the result I needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running pytest
&lt;/h2&gt;

&lt;p&gt;Finally, I ran the existing test suite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PYTHONDONTWRITEBYTECODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
pytest &lt;span class="nt"&gt;-p&lt;/span&gt; no:cacheprovider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 passed in 0.06s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing tests also continued to pass after the migration repair.&lt;/p&gt;




&lt;h2&gt;
  
  
  Git Diff
&lt;/h2&gt;

&lt;p&gt;Only two migration-related files changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;migrations/versions/
├── b7e2c4a91f30_create_initial_tables.py
└── 043c481b4069_add_is_active_to_products.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final commit was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9a4422e fix: add initial database migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. “The Existing Database Works” Does Not Mean the Migration History Is Correct
&lt;/h3&gt;

&lt;p&gt;If an existing database already contains the necessary tables, an application can continue working even with incomplete migration history.&lt;/p&gt;

&lt;p&gt;But a new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development environment&lt;/li&gt;
&lt;li&gt;Test environment&lt;/li&gt;
&lt;li&gt;Machine&lt;/li&gt;
&lt;li&gt;Deployment target&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;may need to start from an empty database.&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing environment starts successfully
≠
Migration history is correct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was the biggest lesson from the incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Migrations Are About the Path, Not Only the Current Schema
&lt;/h3&gt;

&lt;p&gt;Even when:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current model
=
Current database schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that alone is not enough.&lt;/p&gt;

&lt;p&gt;The following path must also work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Empty database
        ↓
Apply every revision in order
        ↓
Reach the current schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A migration system is not only a description of the final schema.&lt;/p&gt;

&lt;p&gt;It is also the reproducible path used to reach that schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Modifying an Applied Revision Requires Careful Verification
&lt;/h3&gt;

&lt;p&gt;In this repair, I changed the &lt;code&gt;down_revision&lt;/code&gt; of an already applied migration.&lt;/p&gt;

&lt;p&gt;The migration graph was logically valid after the change.&lt;/p&gt;

&lt;p&gt;However, that was not enough evidence for me.&lt;/p&gt;

&lt;p&gt;I wanted to know how Alembic would treat a real existing database.&lt;/p&gt;

&lt;p&gt;That is why I tested it against a cloned database created from &lt;code&gt;pg_dump&lt;/code&gt;, instead of experimenting directly on the normal database.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test Both Empty and Existing Databases
&lt;/h3&gt;

&lt;p&gt;For migration repairs, I now think at least two paths should be tested:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
text
1. Empty DB → upgrade head

2. Already migrated DB → upgrade head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>flask</category>
      <category>postgres</category>
      <category>alembic</category>
      <category>docker</category>
    </item>
    <item>
      <title>🔨 XSS Fixed in 47 Seconds? Using VS Code Codex as a Veteran Inspector from Another Department</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Tue, 04 Aug 2026 02:25:56 +0000</pubDate>
      <link>https://dev.to/tosane932/xss-fixed-in-47-seconds-using-vs-code-codex-as-a-veteran-inspector-from-another-department-1dmb</link>
      <guid>https://dev.to/tosane932/xss-fixed-in-47-seconds-using-vs-code-codex-as-a-veteran-inspector-from-another-department-1dmb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmypon6zfned5p0wly0m3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmypon6zfned5p0wly0m3.png" alt="Using Codex as a veteran inspector to review a Flask application" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My name is tosane932. I work as a professional truck driver in logistics while teaching myself Python and web application development.&lt;/p&gt;

&lt;p&gt;I began studying on May 12, 2026, and my total learning time has now reached &lt;strong&gt;152 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This time, I used Codex in VS Code to perform a static review of a Flask application I am developing.&lt;/p&gt;

&lt;p&gt;The target was &lt;code&gt;sales_data_app&lt;/code&gt;, a sales management application for bakery stores.&lt;/p&gt;

&lt;p&gt;It currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product registration&lt;/li&gt;
&lt;li&gt;Daily sales quantity entry&lt;/li&gt;
&lt;li&gt;Sales rankings&lt;/li&gt;
&lt;li&gt;Chart.js visualizations&lt;/li&gt;
&lt;li&gt;Business advice through the Gemini API&lt;/li&gt;
&lt;li&gt;PostgreSQL data management&lt;/li&gt;
&lt;li&gt;Docker-based runtime environments&lt;/li&gt;
&lt;li&gt;Testing with pytest and GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had already reviewed the code myself several times.&lt;/p&gt;

&lt;p&gt;This time, however, I asked Codex to inspect the entire project and identify potential improvements.&lt;/p&gt;

&lt;p&gt;My conclusion was that Codex is extremely fast and highly capable at investigation.&lt;/p&gt;

&lt;p&gt;At the same time, asking it to fix every reported issue at once could damage existing features or the database.&lt;/p&gt;

&lt;p&gt;To me, Codex felt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An experienced veteran employee transferred from another department who does not yet understand how this particular workplace operates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Its inspection ability is excellent.&lt;/p&gt;

&lt;p&gt;However, a human who understands the actual workplace still needs to decide what is a defect and what is intentional behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Codex in VS Code
&lt;/h2&gt;

&lt;p&gt;I installed the official Codex extension in VS Code.&lt;/p&gt;

&lt;p&gt;Codex can inspect files in the currently opened project and help with tasks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explaining code&lt;/li&gt;
&lt;li&gt;Investigating problems&lt;/li&gt;
&lt;li&gt;Editing files&lt;/li&gt;
&lt;li&gt;Running tests&lt;/li&gt;
&lt;li&gt;Executing terminal commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, my first instruction prevented the investigation from moving forward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Please inspect the structure of this project.

Do not modify files or run terminal commands.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex responded that it had no way to inspect the file list or file contents without using terminal commands.&lt;/p&gt;

&lt;p&gt;I therefore revised the instruction to allow read-only commands while still prohibiting changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You may run read-only terminal commands required
to inspect the project structure.

However, the following actions are prohibited:

- Creating, modifying, or deleting files
- Installing packages
- Modifying the database
- Performing Git write operations
- Starting the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this change, Codex could inspect the entire project in read-only mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rules I Set for Codex
&lt;/h2&gt;

&lt;p&gt;Giving Codex unrestricted access felt dangerous, so I added explicit working rules to the custom instructions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Working rules:

- Do not begin making changes without my explicit permission.
- First explain the facts you can confirm, possible causes,
  proposed fixes, affected areas, and required tests.
- Separate investigation from implementation.
  In principle, handle only one issue at a time.
- Do not mix unrelated changes into the same task.
- Do not run Git commit, push, reset, or rebase
  without explicit permission.
- Confirm with me before creating or deleting databases,
  running migrations, or modifying data.
- Confirm with me before deleting files,
  updating dependencies, or performing actions
  that may affect production.
- Read-only investigation commands are allowed.
- After making changes, report the modified files,
  the reason for each change, the affected areas,
  and the verification results.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also configured Codex to request approval before performing operations instead of giving it full access.&lt;/p&gt;

&lt;p&gt;Custom instructions describe expected behavior, but they are not a hard access-control system.&lt;/p&gt;

&lt;p&gt;For that reason, I used both configuration restrictions and written instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performing a Static Review of the Entire Project
&lt;/h2&gt;

&lt;p&gt;I asked Codex to review the whole project from several perspectives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspect this entire project in read-only mode.

Report improvement candidates from the following perspectives:

- Obvious defects
- Security
- Maintainability
- Missing tests
- Unnecessary files or obsolete processing
- Differences between the README and implementation

Conditions:

- Do not create, modify, or delete files
- Do not modify the database
- Do not perform Git write operations
- Do not start the application or run tests yet
- Separate confirmed facts from assumptions
- Classify severity as high, medium, or low
- For each item, provide the relevant filename
  and location as evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex reported a total of &lt;strong&gt;18 improvement candidates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The main findings were as follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  High Severity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Migrations that might fail when building from an empty database&lt;/li&gt;
&lt;li&gt;Stored XSS in the dynamically rendered ranking section&lt;/li&gt;
&lt;li&gt;Public data modification without authentication&lt;/li&gt;
&lt;li&gt;Missing validation between sale dates, product dates, and sales status&lt;/li&gt;
&lt;li&gt;File permissions for &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Medium Severity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Repeated Gemini API execution without authentication&lt;/li&gt;
&lt;li&gt;Invalid input being treated as a server error or even a successful request&lt;/li&gt;
&lt;li&gt;No database-level prevention of duplicate sales for the same product and date&lt;/li&gt;
&lt;li&gt;Insufficient tests for important behavior&lt;/li&gt;
&lt;li&gt;Sales aggregation based only on product names&lt;/li&gt;
&lt;li&gt;Unpinned Chart.js version&lt;/li&gt;
&lt;li&gt;Containers running as the root user&lt;/li&gt;
&lt;li&gt;Development dependencies included in production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Low Severity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Year selection fixed to 2026&lt;/li&gt;
&lt;li&gt;Images and videos no longer referenced from the README&lt;/li&gt;
&lt;li&gt;Local build artifacts and outdated documents&lt;/li&gt;
&lt;li&gt;Placeholder URLs remaining in JSON-LD&lt;/li&gt;
&lt;li&gt;Unclear relationship between CI and Render deployment conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex organized the findings with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filenames&lt;/li&gt;
&lt;li&gt;Relevant lines&lt;/li&gt;
&lt;li&gt;Confirmed facts&lt;/li&gt;
&lt;li&gt;Possible effects&lt;/li&gt;
&lt;li&gt;Suggested improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The number of findings surprised me.&lt;/p&gt;

&lt;p&gt;However, asking Codex to fix all 18 issues at once would have been risky.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Not Accept AI Findings Without Verification
&lt;/h2&gt;

&lt;p&gt;In the first review, Codex classified the &lt;code&gt;.env&lt;/code&gt; file permissions as high severity.&lt;/p&gt;

&lt;p&gt;I asked it to investigate the issue again.&lt;/p&gt;

&lt;p&gt;Codex then checked additional facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/home/tosane&lt;/code&gt; had &lt;code&gt;0750&lt;/code&gt; permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; was excluded from Git&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; was not included in the Docker image&lt;/li&gt;
&lt;li&gt;Render did not directly use the local &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;The additional risk was small in a single-user environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this investigation, Codex revised its own severity rating from &lt;strong&gt;high&lt;/strong&gt; to &lt;strong&gt;low or medium&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This demonstrated why the first AI response should not automatically be accepted.&lt;/p&gt;

&lt;p&gt;Even when an AI answer is detailed and confidently written, it may still be wrong or incomplete.&lt;/p&gt;

&lt;p&gt;I used the following process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Perform a static review of the entire project&lt;/li&gt;
&lt;li&gt;Recheck only the high-severity findings&lt;/li&gt;
&lt;li&gt;Separate reproducibility conditions from current practical impact&lt;/li&gt;
&lt;li&gt;Confirm the required specification before making changes&lt;/li&gt;
&lt;li&gt;Select only one issue as the first fix&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Choosing Stored XSS as the First Fix
&lt;/h2&gt;

&lt;p&gt;Of the 18 findings, I selected stored XSS in the dashboard’s dynamic ranking display as the first issue to fix.&lt;/p&gt;

&lt;p&gt;I chose it because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The change could be limited to one file&lt;/li&gt;
&lt;li&gt;It did not affect the database or authentication design&lt;/li&gt;
&lt;li&gt;Normal product-name display could be preserved&lt;/li&gt;
&lt;li&gt;The security benefit was clear&lt;/li&gt;
&lt;li&gt;Manual verification would be straightforward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problematic code inserted a product name retrieved from an API directly into an HTML string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated HTML string was later assigned to &lt;code&gt;innerHTML&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rankContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;htmlContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;item[0]&lt;/code&gt; contained a product name entered by a user.&lt;/p&gt;

&lt;p&gt;If the name contained HTML or event attributes, the browser could interpret it as markup instead of plain text.&lt;/p&gt;

&lt;p&gt;Jinja2 auto-escaping protected the initial server-rendered page.&lt;/p&gt;

&lt;p&gt;However, after data was fetched through the Fetch API, the ranking was updated in JavaScript using &lt;code&gt;innerHTML&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That created a separate stored XSS risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Asking Codex for the Design Before Allowing Changes
&lt;/h2&gt;

&lt;p&gt;I did not immediately allow Codex to edit the file.&lt;/p&gt;

&lt;p&gt;First, I asked it to explain the proposed design.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Of the high-severity findings,
handle only the stored XSS issue
in the product-name ranking display.

Do not modify any files yet.

Explain the following:

1. The exact code location that requires modification
2. How to preserve the same display without using innerHTML
3. The files that would need to change
4. The impact on the existing ranking display
5. The expected behavior when product names contain
   Japanese text, symbols, or HTML-like strings
6. The tests required after the change

Do not handle the AI response display,
CSP, authentication, database,
or any other improvement in this task.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex proposed the following approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop using &lt;code&gt;innerHTML&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create elements with &lt;code&gt;document.createElement()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assign product names through &lt;code&gt;textContent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use a &lt;code&gt;DocumentFragment&lt;/code&gt; to append multiple rows efficiently&lt;/li&gt;
&lt;li&gt;Preserve the existing CSS classes and DOM structure&lt;/li&gt;
&lt;li&gt;Generate the empty-data message through the DOM API as well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It explained that the change could be completed entirely inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;templates/dashboard.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Allowing Codex to Implement the Fix
&lt;/h2&gt;

&lt;p&gt;After reviewing the scope, I gave Codex permission to implement only that fix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement only the stored XSS protection
for the dynamically generated product ranking.

Conditions:

- Modify only templates/dashboard.html
- Change only the ranking-update logic
- Do not touch the AI response, Chart.js,
  API, CSS, backend, or database
- Preserve existing class names and DOM structure
- Stop generating the ranking with innerHTML
- Use DOM APIs and textContent
- Do not run Git commit or push
- Do not run tests or start the application yet

After making the change, report:

1. The exact range changed
2. The difference before and after
3. Confirmation that no out-of-scope changes were made
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex completed the change in approximately &lt;strong&gt;47 seconds&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Updated Code
&lt;/h2&gt;

&lt;p&gt;The product name was assigned through &lt;code&gt;textContent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;textContent&lt;/code&gt; does not interpret the value as HTML.&lt;/p&gt;

&lt;p&gt;For example, even if the following product name is stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;b&amp;gt;Melon Bread&amp;lt;/b&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the browser displays it as text instead of creating a bold &lt;code&gt;b&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Each ranking element is now created with the DOM API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rankingItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;rankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ranking-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rankBadge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;rankBadge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rank-badge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rank&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rankBadge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`rank-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;rankBadge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quantity and unit are also created as separate elements instead of HTML strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productQuantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;productQuantity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod-qty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;productQuantity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productUnit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;productUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod-unit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;productUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;productQuantity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productUnit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the generated elements are appended together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;rankBadge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;productQuantity&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rankingItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous ranking content is removed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rankContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the existing CSS classes and DOM structure were preserved, the display logic became safer without changing the appearance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Manual Verification
&lt;/h2&gt;

&lt;p&gt;After the fix, I registered test products, entered sales quantities, and verified the results.&lt;/p&gt;

&lt;p&gt;The local environment and Render production environment use separate databases.&lt;/p&gt;

&lt;p&gt;For that reason, I registered the same test products again in Render and checked the behavior from my smartphone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal Product Name
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Melon Bread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It displayed normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Product Name Containing an HTML Tag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;b&amp;gt;Melon Bread&amp;lt;/b&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was not displayed in bold.&lt;/p&gt;

&lt;p&gt;The browser treated it as plain text rather than HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Product Name Containing Japanese Text and Symbols
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;あんぱん &amp;lt;限定&amp;gt; &amp;amp; コーヒー
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The characters &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;amp;&lt;/code&gt; displayed correctly.&lt;/p&gt;

&lt;p&gt;I found no problems with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product registration&lt;/li&gt;
&lt;li&gt;Sales quantity entry&lt;/li&gt;
&lt;li&gt;Dynamic ranking updates&lt;/li&gt;
&lt;li&gt;Dashboard charts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final Check on the Render Demo from a Smartphone
&lt;/h3&gt;

&lt;p&gt;After deployment, I operated the public Render demo from my smartphone.&lt;/p&gt;

&lt;p&gt;I registered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;b&amp;gt;メロンパン&amp;lt;/b&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;あんぱん &amp;lt;限定&amp;gt; &amp;amp; コーヒー&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I then entered daily sales quantities and confirmed that the products appeared correctly in both the ranking and chart.&lt;/p&gt;

&lt;p&gt;The HTML tags were not executed or rendered as formatting.&lt;/p&gt;

&lt;p&gt;The special characters &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;amp;&lt;/code&gt; were not lost.&lt;/p&gt;

&lt;p&gt;The existing sales-entry workflow, ranking, and charts also continued to work normally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reviewing the Diff
&lt;/h2&gt;

&lt;p&gt;Codex reported that only &lt;code&gt;templates/dashboard.html&lt;/code&gt; had changed.&lt;/p&gt;

&lt;p&gt;I also reviewed the change manually with &lt;code&gt;git diff&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--&lt;/span&gt; templates/dashboard.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;git diff&lt;/code&gt; output was displayed through the &lt;code&gt;less&lt;/code&gt; pager.&lt;/p&gt;

&lt;p&gt;To exit the pager, press:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q = quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not close the terminal.&lt;/p&gt;

&lt;p&gt;It only closes the diff viewer and returns to the normal command prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Can Codex Be Used in Real Development?
&lt;/h2&gt;

&lt;p&gt;In this case, Codex did much more than generate code.&lt;/p&gt;

&lt;p&gt;It handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspection of the entire project structure&lt;/li&gt;
&lt;li&gt;Comparison between the README and implementation&lt;/li&gt;
&lt;li&gt;Identification of security risks&lt;/li&gt;
&lt;li&gt;Identification of the relevant files and lines&lt;/li&gt;
&lt;li&gt;Separation of confirmed facts from assumptions&lt;/li&gt;
&lt;li&gt;Clarification of reproduction conditions&lt;/li&gt;
&lt;li&gt;Analysis of the impact on existing features&lt;/li&gt;
&lt;li&gt;Suggestions for required tests&lt;/li&gt;
&lt;li&gt;A narrowly scoped implementation&lt;/li&gt;
&lt;li&gt;Reporting of the final changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ability to perform all of this quickly felt extremely powerful.&lt;/p&gt;

&lt;p&gt;A few years ago, a person would have needed to open files one by one, record the relevant line numbers, and prepare a report manually.&lt;/p&gt;

&lt;p&gt;Static-analysis tools and linters have existed for a long time.&lt;/p&gt;

&lt;p&gt;However, explaining a problem in Japanese while considering project context, likely impact, and possible fixes would still have required substantial human effort.&lt;/p&gt;

&lt;p&gt;That said, asking Codex to fix all 18 findings in one operation would have been dangerous.&lt;/p&gt;

&lt;p&gt;Some findings required business decisions before any code change.&lt;/p&gt;

&lt;p&gt;Examples included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether edits to historical sales should be allowed&lt;/li&gt;
&lt;li&gt;Whether discontinued products should allow corrections to past records&lt;/li&gt;
&lt;li&gt;Whether anyone should be able to operate the public demo&lt;/li&gt;
&lt;li&gt;Whether authentication should be mandatory&lt;/li&gt;
&lt;li&gt;How to repair migrations for an empty database&lt;/li&gt;
&lt;li&gt;How migration changes could affect the existing Render database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A technically reasonable change could still break the purpose of the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Veteran Inspector from Another Department
&lt;/h2&gt;

&lt;p&gt;The best logistics analogy I found was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Codex is an experienced veteran employee transferred from another department who does not yet understand this workplace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can quickly identify dangerous equipment, questionable procedures, and suspicious code.&lt;/p&gt;

&lt;p&gt;Its reports are fast and detailed.&lt;/p&gt;

&lt;p&gt;However, it does not automatically know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why the current workflow exists&lt;/li&gt;
&lt;li&gt;Which requirements have priority&lt;/li&gt;
&lt;li&gt;Which exceptions are necessary&lt;/li&gt;
&lt;li&gt;What the public demo is intended to allow&lt;/li&gt;
&lt;li&gt;Which existing behaviors must be preserved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A human reviewing the findings still needs to decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this truly a defect?&lt;/li&gt;
&lt;li&gt;Is it an intentional specification?&lt;/li&gt;
&lt;li&gt;Is it a future improvement?&lt;/li&gt;
&lt;li&gt;Does it need to be fixed immediately?&lt;/li&gt;
&lt;li&gt;What scope can be changed safely?&lt;/li&gt;
&lt;li&gt;Could the fix break another workflow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before allowing Codex to modify the code, I followed this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the whole project in read-only mode&lt;/li&gt;
&lt;li&gt;Recheck the high-severity findings&lt;/li&gt;
&lt;li&gt;Correct the severity of an overestimated item&lt;/li&gt;
&lt;li&gt;Limit the first fix to one stored XSS issue&lt;/li&gt;
&lt;li&gt;Review the design and impact before implementation&lt;/li&gt;
&lt;li&gt;Limit the change to one file&lt;/li&gt;
&lt;li&gt;Allow Codex to implement the fix&lt;/li&gt;
&lt;li&gt;Review the diff with &lt;code&gt;git diff&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Register the test products locally and verify sales entry and dashboard display&lt;/li&gt;
&lt;li&gt;Commit the change and push it to GitHub&lt;/li&gt;
&lt;li&gt;Confirm completion of the Render deployment&lt;/li&gt;
&lt;li&gt;Register the same products again in Render because its database is separate&lt;/li&gt;
&lt;li&gt;Verify sales entry, rankings, and charts from a smartphone&lt;/li&gt;
&lt;li&gt;Confirm that both the local and production environments behaved normally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Codex changed the code in approximately 47 seconds.&lt;/p&gt;

&lt;p&gt;However, making those 47 seconds safe required a human to read the findings, understand the specification, and define the allowed scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Can Handle and What Humans Must Decide
&lt;/h2&gt;

&lt;p&gt;I organized the responsibilities as follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tasks That Are Easy to Delegate to Codex
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inspecting files in the project&lt;/li&gt;
&lt;li&gt;Identifying possible code issues&lt;/li&gt;
&lt;li&gt;Showing evidence and relevant locations&lt;/li&gt;
&lt;li&gt;Comparing possible fixes&lt;/li&gt;
&lt;li&gt;Making narrowly scoped code changes&lt;/li&gt;
&lt;li&gt;Reporting what changed&lt;/li&gt;
&lt;li&gt;Suggesting test cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Decisions That Still Belong to Humans
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The purpose of the application&lt;/li&gt;
&lt;li&gt;Business rules&lt;/li&gt;
&lt;li&gt;Priorities&lt;/li&gt;
&lt;li&gt;Acceptable risk&lt;/li&gt;
&lt;li&gt;Public demo operation policies&lt;/li&gt;
&lt;li&gt;Database and production-change decisions&lt;/li&gt;
&lt;li&gt;Which findings to adopt or postpone&lt;/li&gt;
&lt;li&gt;Final verification and release decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex works quickly when instructions are clear.&lt;/p&gt;

&lt;p&gt;However, giving it full access with vague instructions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fix everything.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could lead to a serious incident.&lt;/p&gt;

&lt;p&gt;The faster the worker, the more clearly the work area and stopping conditions must be defined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I used Codex in VS Code to perform a static review of a Flask application and fix one stored XSS issue.&lt;/p&gt;

&lt;p&gt;The final implementation changed only the dynamic ranking-generation logic in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;templates/dashboard.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removing ranking generation through &lt;code&gt;innerHTML&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creating DOM elements with &lt;code&gt;createElement()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assigning product names through &lt;code&gt;textContent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Preserving the existing CSS classes and DOM structure&lt;/li&gt;
&lt;li&gt;Manually testing Japanese text, symbols, and HTML-like product names&lt;/li&gt;
&lt;li&gt;Confirming that normal product registration, sales entry, and ranking display still worked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex demonstrated investigation and implementation speed that could be highly useful in real development.&lt;/p&gt;

&lt;p&gt;However, fixing every issue identified by AI is not automatically the correct decision.&lt;/p&gt;

&lt;p&gt;The important process is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Investigate&lt;br&gt;&lt;br&gt;
Recheck&lt;br&gt;&lt;br&gt;
Define the specification&lt;br&gt;&lt;br&gt;
Limit the change&lt;br&gt;&lt;br&gt;
Review the diff&lt;br&gt;&lt;br&gt;
Verify the actual behavior&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Codex is an extremely fast and capable worker.&lt;/p&gt;

&lt;p&gt;However, the decision about what to fix, what not to fix, and when the application is ready to ship still belongs to the human developer.&lt;/p&gt;

&lt;p&gt;My total learning time has now reached &lt;strong&gt;152 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I will continue using AI without handing over the entire project blindly.&lt;/p&gt;

&lt;p&gt;My goal is to remain able to explain the cause, affected area, and reason for each change in my own words.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>security</category>
      <category>codex</category>
    </item>
    <item>
      <title>From Two AI Apps to a Vision for “Frontline Support AI”</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Mon, 03 Aug 2026 11:11:00 +0000</pubDate>
      <link>https://dev.to/tosane932/from-two-ai-apps-to-a-vision-for-frontline-support-ai-1ph1</link>
      <guid>https://dev.to/tosane932/from-two-ai-apps-to-a-vision-for-frontline-support-ai-1ph1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3ukilfdz166ejdlbj2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3ukilfdz166ejdlbj2y.png" alt="Two AI applications leading to a frontline support AI vision" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I currently work as a logistics driver while teaching myself web application development, mainly with Python.&lt;/p&gt;

&lt;p&gt;I have been building personal projects around problems I have encountered in real workplaces and tools that made me think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It would be useful if something like this existed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The two main applications I currently have publicly available are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bakery sales management system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Puoppo&lt;/strong&gt;, a news summarization and analysis application powered by official RSS feeds and AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, I developed these as two completely separate applications.&lt;/p&gt;

&lt;p&gt;However, when I placed both services side by side in a single image, I realized that they were actually moving in the same direction.&lt;/p&gt;

&lt;p&gt;This article records why I built these two applications and the broader product vision that emerged from connecting them conceptually.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two AI Applications I Built
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Bakery Sales Management System
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2llq3ertek3pzj8pliqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2llq3ertek3pzj8pliqx.png" alt="Bakery sales management system" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bakery sales management system is a web application for recording daily sales data and reviewing sales totals and product rankings.&lt;/p&gt;

&lt;p&gt;Its main features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product registration&lt;/li&gt;
&lt;li&gt;Daily sales entry&lt;/li&gt;
&lt;li&gt;Sales aggregation by product&lt;/li&gt;
&lt;li&gt;Sales rankings&lt;/li&gt;
&lt;li&gt;Chart-based visualization&lt;/li&gt;
&lt;li&gt;AI-generated suggestions for improving sales&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why I Built It
&lt;/h3&gt;

&lt;p&gt;A store produces many numbers every day.&lt;/p&gt;

&lt;p&gt;However, recording numbers alone does not automatically lead to better decisions.&lt;/p&gt;

&lt;p&gt;Store operators still need to answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which products are actually selling?&lt;/li&gt;
&lt;li&gt;Which products are losing sales?&lt;/li&gt;
&lt;li&gt;What should be changed to improve revenue?&lt;/li&gt;
&lt;li&gt;Is there room to improve the product lineup or sales method?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I therefore wanted to build more than a simple sales-recording tool.&lt;/p&gt;

&lt;p&gt;My goal was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To transform sales data into practical improvement ideas that can be used in the workplace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Gemini API&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;pytest&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have also introduced testing and CI/CD.&lt;/p&gt;

&lt;p&gt;The project is no longer focused only on adding features. I am also building a development environment where I can continue improving the application safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. News Summarization and Analysis App: Puoppo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F327m5x3v2s5oknryg6ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F327m5x3v2s5oknryg6ko.png" alt="Puoppo news summarization and analysis app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puoppo is a web application that retrieves news from official RSS feeds and uses AI to summarize and analyze it.&lt;/p&gt;

&lt;p&gt;Its main features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieving news from official RSS feeds&lt;/li&gt;
&lt;li&gt;Keyword search&lt;/li&gt;
&lt;li&gt;News article summarization&lt;/li&gt;
&lt;li&gt;Comparison of multiple news stories&lt;/li&gt;
&lt;li&gt;Reducing the time required to collect information&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why I Built It
&lt;/h3&gt;

&lt;p&gt;A huge amount of information is published on the internet every day.&lt;/p&gt;

&lt;p&gt;However, finding the information you need, reading articles one by one, and organizing their contents takes a great deal of time.&lt;/p&gt;

&lt;p&gt;When information gathering must be done alongside work or study, reading every article is not realistic.&lt;/p&gt;

&lt;p&gt;I therefore developed Puoppo as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A system that collects relevant news and helps users understand it in a short amount of time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Puoppo uses reliable official RSS feeds as its information sources and applies AI to summarize and analyze the retrieved news.&lt;/p&gt;

&lt;p&gt;The goal is not merely to display a list of headlines.&lt;/p&gt;

&lt;p&gt;It is to reduce the time users need to understand the information and make their own decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Realized After Placing the Two Apps Side by Side
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl75ddeqijwr0gmqvsmkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl75ddeqijwr0gmqvsmkp.png" alt="The two applications shown together" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently placed the bakery sales management system and Puoppo together in a single promotional image.&lt;/p&gt;

&lt;p&gt;Seeing them side by side made the role of each application much clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bakery Sales Management System
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;An application for understanding data generated inside the store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It handles internal information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales&lt;/li&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;Rankings&lt;/li&gt;
&lt;li&gt;Daily records&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Puoppo
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;An application for understanding information generated outside the store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It handles external information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;News&lt;/li&gt;
&lt;li&gt;Industry trends&lt;/li&gt;
&lt;li&gt;Regulatory changes&lt;/li&gt;
&lt;li&gt;Economic information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relationship can be summarized like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Information Handled&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bakery sales management system&lt;/td&gt;
&lt;td&gt;Internal store sales data&lt;/td&gt;
&lt;td&gt;Understanding the current situation and identifying improvements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Puoppo&lt;/td&gt;
&lt;td&gt;External news and publicly available information&lt;/td&gt;
&lt;td&gt;Information gathering and situational awareness&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Until then, I had thought of them as separate personal projects.&lt;/p&gt;

&lt;p&gt;However, they ultimately share the same purpose:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Collect data and information, organize it with AI, and connect it to the next action.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Connecting “Inside the Store” with “Outside the Store”
&lt;/h2&gt;

&lt;p&gt;A store cannot fully understand its situation by looking only at its own sales data.&lt;/p&gt;

&lt;p&gt;Many external factors can affect sales.&lt;/p&gt;

&lt;p&gt;For a bakery, these could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The prices of flour, butter, and other ingredients&lt;/li&gt;
&lt;li&gt;Weather&lt;/li&gt;
&lt;li&gt;Local events&lt;/li&gt;
&lt;li&gt;Seasons and temperature&lt;/li&gt;
&lt;li&gt;Food industry trends&lt;/li&gt;
&lt;li&gt;New competing stores&lt;/li&gt;
&lt;li&gt;Subsidies or regulatory changes&lt;/li&gt;
&lt;li&gt;Consumer cost-saving behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining this external information with a store’s internal sales data could support more specific decisions.&lt;/p&gt;

&lt;p&gt;For example, a future system might make suggestions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;News about rising wheat prices has increased.&lt;br&gt;&lt;br&gt;
Consider reviewing the pricing of products with high ingredient costs.&lt;/p&gt;

&lt;p&gt;Tomorrow has a high chance of rain, so customer traffic may decrease.&lt;br&gt;&lt;br&gt;
Consider adjusting production volume to reduce waste.&lt;/p&gt;

&lt;p&gt;A local event is scheduled nearby.&lt;br&gt;&lt;br&gt;
Consider preparing more products that are easy to carry away.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal business data&lt;/li&gt;
&lt;li&gt;External news&lt;/li&gt;
&lt;li&gt;Weather and local information&lt;/li&gt;
&lt;li&gt;AI-powered analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the system could develop beyond simple sales management and begin supporting business decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SalesInsight AI Concept
&lt;/h2&gt;

&lt;p&gt;The bakery sales management system originally began under the project name &lt;code&gt;sales_data_app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As development continued, I started to see the possibility of applying its basic structure to more industries than bakeries.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Izakaya restaurants&lt;/li&gt;
&lt;li&gt;Teppanyaki restaurants&lt;/li&gt;
&lt;li&gt;Okonomiyaki restaurants&lt;/li&gt;
&lt;li&gt;Fish markets&lt;/li&gt;
&lt;li&gt;Greengrocers&lt;/li&gt;
&lt;li&gt;Retail stores&lt;/li&gt;
&lt;li&gt;Restaurants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core system could potentially be adapted by changing product data, terminology, and screen layouts for each industry.&lt;/p&gt;

&lt;p&gt;I am therefore considering the following future project name:&lt;/p&gt;

&lt;h2&gt;
  
  
  SalesInsight AI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77nh686jmg9k19q496zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77nh686jmg9k19q496zm.png" alt="SalesInsight AI concept" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SalesInsight AI would analyze sales data for different types of businesses and provide improvement suggestions that can be acted on in real workplaces.&lt;/p&gt;

&lt;p&gt;If it could also connect with Puoppo’s information-gathering features, it might develop into a broader business-support platform that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Turns workplace numbers and changes in the outside world into the next practical action through AI.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why I Connect GitHub and Qiita
&lt;/h2&gt;

&lt;p&gt;I publish not only completed applications, but also the development process through GitHub and Qiita.&lt;/p&gt;

&lt;p&gt;On GitHub, I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source code&lt;/li&gt;
&lt;li&gt;Changes&lt;/li&gt;
&lt;li&gt;Commit history&lt;/li&gt;
&lt;li&gt;The current state of each project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Qiita, I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Problems encountered during implementation&lt;/li&gt;
&lt;li&gt;Investigation steps&lt;/li&gt;
&lt;li&gt;Mistakes&lt;/li&gt;
&lt;li&gt;Fixes&lt;/li&gt;
&lt;li&gt;Lessons learned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Topics I have written about include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building Docker environments&lt;/li&gt;
&lt;li&gt;Multi-stage Docker builds&lt;/li&gt;
&lt;li&gt;Secure API key management&lt;/li&gt;
&lt;li&gt;Introducing pytest&lt;/li&gt;
&lt;li&gt;CI with GitHub Actions&lt;/li&gt;
&lt;li&gt;Near-misses involving Git operations&lt;/li&gt;
&lt;li&gt;Removing unnecessary code and dependencies&lt;/li&gt;
&lt;li&gt;Investigating errors in the development environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These details are not visible from completed screenshots alone.&lt;/p&gt;

&lt;p&gt;However, during real development, I have found that the following questions are often more important than the finished feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why did the error occur?&lt;/li&gt;
&lt;li&gt;How was the cause isolated?&lt;/li&gt;
&lt;li&gt;What was changed to prevent the same incident?&lt;/li&gt;
&lt;li&gt;Why was a particular technology selected?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I therefore use GitHub as a record of the deliverable and Qiita as a record of the problem-solving process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Personal Projects Become a Line, Not Just Separate Points
&lt;/h2&gt;

&lt;p&gt;When I first started personal development, I was focused only on completing one application.&lt;/p&gt;

&lt;p&gt;However, as I continued learning and building multiple applications, the experiences from each project began to connect.&lt;/p&gt;

&lt;p&gt;Through the bakery sales management system, I learned about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data entry&lt;/li&gt;
&lt;li&gt;Aggregation&lt;/li&gt;
&lt;li&gt;Analysis&lt;/li&gt;
&lt;li&gt;AI-generated recommendations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through Puoppo, I learned about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieving external information&lt;/li&gt;
&lt;li&gt;RSS&lt;/li&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Summarization&lt;/li&gt;
&lt;li&gt;AI analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The technologies and ideas learned in one application can be reused in another.&lt;/p&gt;

&lt;p&gt;Creating an image that placed the two apps side by side made me realize:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The things I have built were not unrelated products. They were intermediate steps toward the same broader concept.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each personal project may look like a small, separate point.&lt;/p&gt;

&lt;p&gt;But when projects are accumulated with a purpose, those points begin to form a line—and eventually a larger service vision.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Is Still Only a Concept
&lt;/h2&gt;

&lt;p&gt;SalesInsight AI and its potential integration with Puoppo are currently future ideas.&lt;/p&gt;

&lt;p&gt;The two existing applications still operate independently.&lt;/p&gt;

&lt;p&gt;I do not want to present features that have not been implemented as if they were already complete.&lt;/p&gt;

&lt;p&gt;However, I believe personal development is also about recording:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When an idea emerged and what I was thinking at that point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The concept may change in the future.&lt;/p&gt;

&lt;p&gt;Some parts may prove technically difficult.&lt;/p&gt;

&lt;p&gt;Even so, recording the direction I can currently see will allow me to look back later and understand how the project evolved.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Want to Work on Next
&lt;/h2&gt;

&lt;p&gt;While continuing to improve the stability of the existing applications, I would like to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industry-specific interfaces and product management&lt;/li&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Per-user data management&lt;/li&gt;
&lt;li&gt;Saved analysis history&lt;/li&gt;
&lt;li&gt;Improved AI recommendations&lt;/li&gt;
&lt;li&gt;Integration with external news&lt;/li&gt;
&lt;li&gt;Weather and local-information integration&lt;/li&gt;
&lt;li&gt;Better smartphone usability&lt;/li&gt;
&lt;li&gt;Broader test coverage&lt;/li&gt;
&lt;li&gt;Stronger security and operational safeguards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not plan to implement everything at once.&lt;/p&gt;

&lt;p&gt;I want to proceed through small experiments while avoiding damage to features that already work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Placing the bakery sales management system and Puoppo together in one image revealed the common purpose behind the two personal projects.&lt;/p&gt;

&lt;p&gt;The bakery sales management system handles information from inside the store.&lt;/p&gt;

&lt;p&gt;Puoppo handles information from outside the store.&lt;/p&gt;

&lt;p&gt;Connecting the two created the following vision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analyze internal business numbers together with external information and use AI to support the next practical action in the workplace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the moment, they are still two independent applications.&lt;/p&gt;

&lt;p&gt;However, by continuing personal development and recording the process on GitHub and Qiita, I hope to gradually turn this concept into something real.&lt;/p&gt;

&lt;p&gt;Rather than explaining the completed future only after it exists, I want to record the moment when the idea first emerged.&lt;/p&gt;

&lt;p&gt;This article is one part of that development record.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Bakery Sales Management System
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Puoppo
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/puoppo_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/puoppo_app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Demos
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Bakery Sales Management System — Render Demo
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Puoppo — Render Demo
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://puoppo.onrender.com/" rel="noopener noreferrer"&gt;https://puoppo.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Qiita
&lt;/h3&gt;

&lt;h4&gt;
  
  
  My Qiita Profile
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932" rel="noopener noreferrer"&gt;https://qiita.com/tosane932&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Bakery Sales Management System Articles
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/245152c844261e615641" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/245152c844261e615641&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/02de476fad8f0c1261e0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/02de476fad8f0c1261e0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/31bdab8ee2ab8bae2c50" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/31bdab8ee2ab8bae2c50&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Puoppo Articles
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/974e12e369eda378549b" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/974e12e369eda378549b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/92bcf28cd91d645596bd" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/92bcf28cd91d645596bd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>ai</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>🚛 A Five-Hour Inspection of a Working Flask App — Aligning Code, README, Docker, and Git with the Current Specification</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Mon, 03 Aug 2026 03:19:28 +0000</pubDate>
      <link>https://dev.to/tosane932/a-five-hour-inspection-of-a-working-flask-app-aligning-code-readme-docker-and-git-with-the-2pf4</link>
      <guid>https://dev.to/tosane932/a-five-hour-inspection-of-a-working-flask-app-aligning-code-readme-docker-and-git-with-the-2pf4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am a working truck driver teaching myself Python.&lt;/p&gt;

&lt;p&gt;I try to bring two habits from logistics into web application development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not leave any cargo behind&lt;/li&gt;
&lt;li&gt;Inspect everything before shipment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was Day 69 of my Python-learning journey, with a total of &lt;strong&gt;148 study hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This time, I did not add a new feature to my bakery sales management application, &lt;code&gt;sales_data_app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, I spent five hours on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository cleanup&lt;/li&gt;
&lt;li&gt;Full inspection&lt;/li&gt;
&lt;li&gt;Removal of obsolete code&lt;/li&gt;
&lt;li&gt;Synchronizing documentation and configuration with the current application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inspection covered much more than application code.&lt;/p&gt;

&lt;p&gt;I reviewed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;README files&lt;/li&gt;
&lt;li&gt;Screenshots&lt;/li&gt;
&lt;li&gt;Demo videos&lt;/li&gt;
&lt;li&gt;Thumbnails&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dockerignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Old Excel-related logic&lt;/li&gt;
&lt;li&gt;Unused imports&lt;/li&gt;
&lt;li&gt;Unnecessary dependencies&lt;/li&gt;
&lt;li&gt;Unused CSS&lt;/li&gt;
&lt;li&gt;Outdated UI text&lt;/li&gt;
&lt;li&gt;Git conflict markers&lt;/li&gt;
&lt;li&gt;pytest&lt;/li&gt;
&lt;li&gt;Synchronization with GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application itself was working.&lt;/p&gt;

&lt;p&gt;However, a working application and a repository that accurately represents the current specification are not the same thing.&lt;/p&gt;

&lt;p&gt;As development continues, small leftovers begin to accumulate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old specifications&lt;/li&gt;
&lt;li&gt;Outdated images&lt;/li&gt;
&lt;li&gt;Obsolete settings&lt;/li&gt;
&lt;li&gt;Unused CSS&lt;/li&gt;
&lt;li&gt;Dependencies that no longer have a purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In logistics terms, the delivery may have been completed successfully, while old materials and paperwork are still sitting in the corner of the truck.&lt;/p&gt;

&lt;p&gt;Before loading the next shipment, I decided to inspect and clear the entire cargo area.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Main idea&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article is not only about organizing files.&lt;/p&gt;

&lt;p&gt;A working application does not necessarily mean that the code, dependencies, UI, documentation, Docker configuration, and Git state all match the current specification.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What This Article Covers
&lt;/h2&gt;

&lt;p&gt;This article explains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to inspect an entire Flask repository&lt;/li&gt;
&lt;li&gt;A safe order for reviewing deletion candidates&lt;/li&gt;
&lt;li&gt;The difference between &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to find unused imports, dependencies, and CSS&lt;/li&gt;
&lt;li&gt;How to interpret an empty &lt;code&gt;grep&lt;/code&gt; result&lt;/li&gt;
&lt;li&gt;Why searching for Git conflict markers produced false positives&lt;/li&gt;
&lt;li&gt;How to decide whether changes belong in one commit&lt;/li&gt;
&lt;li&gt;Why I checked both pytest and &lt;code&gt;working tree clean&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A reusable repository inspection checklist&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a long article.&lt;/p&gt;

&lt;p&gt;You can read it from beginning to end, jump directly to a section that interests you, or use the repository inspection checklist near the end during your own maintenance work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Application
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sales_data_app&lt;/code&gt; is a web application that combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bakery product registration&lt;/li&gt;
&lt;li&gt;Daily sales entry&lt;/li&gt;
&lt;li&gt;Sales analysis&lt;/li&gt;
&lt;li&gt;Business advice generated through the Gemini API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main technology stack is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python 3.12
Flask
PostgreSQL
SQLAlchemy
Flask-Migrate / Alembic
Docker / Docker Compose
Gunicorn
Render
pytest
GitHub Actions
Gemini API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application supports the following workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Register products and prices
        ↓
Enter or update today's sales quantities
        ↓
Review sales rankings and charts
        ↓
Receive business advice from Gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Thinking About the Inspection in Five Layers
&lt;/h2&gt;

&lt;p&gt;Looking back, the inspection targets could be divided into five layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Runtime code
   Unused imports, obsolete logic, old settings, unused CSS

2. Dependencies
   Libraries that remained in requirements.txt unnecessarily

3. UI and documentation
   HTML wording, README files, screenshots, and demo assets

4. Delivery scope
   .gitignore and .dockerignore

5. Shipment status
   pytest, git diff, git status, and GitHub synchronization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dividing the repository into these five layers made the inspection scope much clearer than simply searching for “unnecessary files.”&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A repository inspection is not only about deleting files.&lt;/p&gt;

&lt;p&gt;It is the process of aligning the current specification with the code, dependencies, UI, documentation, and delivery state.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Work Completed During the Five Hours
&lt;/h2&gt;

&lt;p&gt;The main tasks were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Organize demo videos, thumbnails, and screenshots&lt;/li&gt;
&lt;li&gt;Correct image paths in the README&lt;/li&gt;
&lt;li&gt;Capture screenshots of the current UI&lt;/li&gt;
&lt;li&gt;Clean up &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clean up &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove obsolete Excel-related logic&lt;/li&gt;
&lt;li&gt;Remove unused imports&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;openpyxl&lt;/code&gt; dependency&lt;/li&gt;
&lt;li&gt;Remove unused CSS&lt;/li&gt;
&lt;li&gt;Correct outdated page titles&lt;/li&gt;
&lt;li&gt;Search for remnants of old specifications&lt;/li&gt;
&lt;li&gt;Search for Git conflict markers&lt;/li&gt;
&lt;li&gt;Commit changes by purpose&lt;/li&gt;
&lt;li&gt;Run pytest&lt;/li&gt;
&lt;li&gt;Verify synchronization with GitHub&lt;/li&gt;
&lt;li&gt;Record the work in two README files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following sections describe the actual order of work.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Organizing Images and Videos by Purpose
&lt;/h2&gt;

&lt;p&gt;I started by organizing the demo assets in the repository.&lt;/p&gt;

&lt;p&gt;The final structure became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales_data_app/
├── demo_mp4/
├── demo_thumbnail/
├── screenshot/
├── static/
├── templates/
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each directory now has a clear responsibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;demo_mp4/
└── Demo videos

demo_thumbnail/
└── YouTube thumbnails

screenshot/
└── Screenshots used in the README
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Previously, videos and images were mixed across the repository root and other locations.&lt;/p&gt;

&lt;p&gt;This did not directly affect application behavior.&lt;/p&gt;

&lt;p&gt;However, it made it difficult for another person to answer questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is this image used for?
Is this file still required?
Is it necessary to run the application?
Is it documentation material?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Organizing files by both directory name and location makes their purpose easier to understand without opening them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Folder organization is not merely cosmetic.&lt;/p&gt;

&lt;p&gt;It is part of the information architecture of the repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Update README Paths After Moving Files
&lt;/h3&gt;

&lt;p&gt;When files move into new directories, the README references must also change.&lt;/p&gt;

&lt;p&gt;For example, after moving a screenshot into the &lt;code&gt;screenshot&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Product master registration screen&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;screenshot/screen01.jpg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the files are moved but the README paths are not updated, the images stop appearing on GitHub.&lt;/p&gt;

&lt;p&gt;I checked the work in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Move the files
        ↓
Update the README paths
        ↓
Confirm that the images display on GitHub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Aligning the README with the Current UI
&lt;/h2&gt;

&lt;p&gt;Some screenshots in the README no longer matched the application’s current screens.&lt;/p&gt;

&lt;p&gt;I started the latest version of the application and captured new screenshots.&lt;/p&gt;

&lt;p&gt;The main updated screens were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product master registration&lt;/li&gt;
&lt;li&gt;Menu registration completion&lt;/li&gt;
&lt;li&gt;Daily sales entry&lt;/li&gt;
&lt;li&gt;Sales analysis dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;README screenshots are not merely decoration.&lt;/p&gt;

&lt;p&gt;When someone opens a repository, they may inspect the screenshots before reading the code.&lt;/p&gt;

&lt;p&gt;If the screenshots are outdated, an improved application may still appear to be using its old UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Outdated HTML Title
&lt;/h3&gt;

&lt;p&gt;While reviewing the screens, I also found an outdated title in the HTML.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Sales Aggregation Complete&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the current screen represents completed product-menu registration, not sales aggregation.&lt;/p&gt;

&lt;p&gt;I changed it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Menu Registration Complete&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser-tab title is also part of the application specification visible to users.&lt;/p&gt;

&lt;p&gt;Updating the page heading while leaving the tab title outdated still creates inconsistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even when the application works correctly, outdated wording may mislead users or developers reading the code.&lt;/p&gt;

&lt;p&gt;UI wording should be synchronized with the current specification just like application logic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Updating &lt;code&gt;.gitignore&lt;/code&gt; for the Current Development Environment
&lt;/h2&gt;

&lt;p&gt;Next, I reviewed files that Git does not need to track.&lt;/p&gt;

&lt;p&gt;The cleaned-up &lt;code&gt;.gitignore&lt;/code&gt; became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Python
.venv/
__pycache__/
*.pyc
.pytest_cache/

# Raw operational data and sensitive files
過去売上高/
*.xlsx
*.db
.env

# Other unnecessary files
*.bak
sales_data_app_android_demo.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Do Not Track Virtual Environments or Caches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.venv/
__pycache__/
*.pyc
.pytest_cache/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files are generated separately in each development environment.&lt;/p&gt;

&lt;p&gt;They can be recreated and do not need to be stored on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Not Track Secrets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;code&gt;.env&lt;/code&gt; file may contain sensitive values such as Gemini API keys.&lt;/p&gt;

&lt;p&gt;It should be explicitly excluded to reduce the risk of accidentally pushing secrets to a public repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Not Track Local Data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.db
*.xlsx
過去売上高/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production application uses PostgreSQL.&lt;/p&gt;

&lt;p&gt;Local SQLite files and old Excel documents do not need to be stored in GitHub.&lt;/p&gt;

&lt;p&gt;However, an important point is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A file looks old
≠
It is safe to delete immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked &lt;code&gt;local.db&lt;/code&gt; and confirmed that the configuration still referenced it.&lt;/p&gt;

&lt;p&gt;Therefore, I excluded it from Git tracking but did not immediately delete it from my local environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Using &lt;code&gt;.dockerignore&lt;/code&gt; to Decide What Goes into Production
&lt;/h2&gt;

&lt;p&gt;Files that Git should not track and files that Docker should not receive serve different purposes.&lt;/p&gt;

&lt;p&gt;I therefore reviewed &lt;code&gt;.dockerignore&lt;/code&gt; separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Git
.git
.gitignore

# Environment variables and virtual environments
.env
.venv/
venv/

# Python and test caches
__pycache__/
*.pyc
.pytest_cache/

# Local data and obsolete documents
*.db
*.xlsx
過去売上高/
*.bak

# Development and documentation files
README.md
test_prompts.py
demo_mp4/
demo_thumbnail/
screenshot/

# Operating-system and log files
*.log
.DS_Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;README files, screenshots, demo videos, and tests are valuable on GitHub.&lt;/p&gt;

&lt;p&gt;However, they were not required in this production Docker image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt; have different responsibilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt;: decides what Git does not track&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.dockerignore&lt;/code&gt;: decides what Docker does not send into the build context&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;In logistics terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents worth storing in the warehouse
≠
Cargo that belongs on the delivery truck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub may preserve development history and documentation.&lt;/p&gt;

&lt;p&gt;Docker should carry only what the running application needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Questions for Classifying Files
&lt;/h3&gt;

&lt;p&gt;When deciding where a file belongs, I used two questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Is this file worth preserving on GitHub?
2. Is this file needed when the production application runs?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;GitHub&lt;/th&gt;
&lt;th&gt;Docker&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application code&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Required to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML and CSS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Required for the UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;README&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshots&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Used for repository explanation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Demo videos&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Not required at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test files&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No in this image&lt;/td&gt;
&lt;td&gt;Used for CI and development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Sensitive information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Recreated per environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite database&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Local development data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach is more useful than copying ignore files from another project without understanding them.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Searching for Leftovers from the Old Excel Implementation
&lt;/h2&gt;

&lt;p&gt;The early version of this application used Excel.&lt;/p&gt;

&lt;p&gt;The architecture later moved toward PostgreSQL, but some Excel-era code remained.&lt;/p&gt;

&lt;p&gt;I found the following leftovers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Old Folder Configuration in &lt;code&gt;config.py&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PAST_FOLDER_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;過去売上高&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PAST_FOLDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PAST_FOLDER_NAME&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;h3&gt;
  
  
  Old Directory-Creation Logic in &lt;code&gt;app.py&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PAST_FOLDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;h3&gt;
  
  
  Unused Imports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_file&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.genai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Unnecessary Dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openpyxl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current application stores sales data in PostgreSQL.&lt;/p&gt;

&lt;p&gt;It no longer generates or sends Excel files.&lt;/p&gt;

&lt;p&gt;After confirming the references, I removed the obsolete code and dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Do Not Delete a Candidate Immediately
&lt;/h2&gt;

&lt;p&gt;An important part of the inspection was avoiding immediate deletion when something looked obsolete.&lt;/p&gt;

&lt;p&gt;I used the following sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search for the name
        ↓
Check where it is referenced
        ↓
Compare it with the current specification
        ↓
Delete it
        ↓
Search for the same name again
        ↓
Run tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, finding &lt;code&gt;openpyxl&lt;/code&gt; in &lt;code&gt;requirements.txt&lt;/code&gt; was not enough reason to remove it immediately.&lt;/p&gt;

&lt;p&gt;I first checked whether the code still used it.&lt;/p&gt;

&lt;p&gt;I applied the same process to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration variables&lt;/li&gt;
&lt;li&gt;Imports&lt;/li&gt;
&lt;li&gt;CSS classes&lt;/li&gt;
&lt;li&gt;Old folder names&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Deleting code because it “looks old” is risky.&lt;/p&gt;

&lt;p&gt;Treat the search before and after deletion as one complete verification process.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Final Diff: 23 Lines Removed and 2 Changed
&lt;/h2&gt;

&lt;p&gt;After removing the old Excel logic, unused imports, unnecessary dependencies, unused CSS, and outdated UI text, the diff looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.py                 | 10 +---------
config.py              |  4 ----
requirements.txt       |  1 -
static/style.css       |  8 --------
templates/success.html |  2 +-
5 files changed, 2 insertions(+), 23 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not add a large amount of new code.&lt;/p&gt;

&lt;p&gt;Instead, I removed 23 lines that were no longer needed by the current specification.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Development does not only mean increasing the amount of code.&lt;/p&gt;

&lt;p&gt;Reducing unused code with a clear reason is also an improvement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Less code is not automatically better.&lt;/p&gt;

&lt;p&gt;However, removing unnecessary code can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the current behavior easier to understand&lt;/li&gt;
&lt;li&gt;Reduce the risk of accidentally reusing obsolete logic&lt;/li&gt;
&lt;li&gt;Clarify the purpose of dependencies&lt;/li&gt;
&lt;li&gt;Reduce future maintenance targets&lt;/li&gt;
&lt;li&gt;Make it easier to keep the README and implementation consistent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Finding and Removing Unused CSS
&lt;/h2&gt;

&lt;p&gt;I used &lt;code&gt;grep&lt;/code&gt; to check whether CSS remained that was no longer used by HTML or JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Rni&lt;/span&gt; &lt;span class="s2"&gt;"ai-loading"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  templates &lt;span class="se"&gt;\&lt;/span&gt;
  static &lt;span class="se"&gt;\&lt;/span&gt;
  app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first search found only the CSS definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static/style.css:680:.input-page .ai-loading {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The class was not referenced by the templates or JavaScript.&lt;/p&gt;

&lt;p&gt;I removed the entire CSS block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.ai-loading&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Unused styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then ran the same search again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Rni&lt;/span&gt; &lt;span class="s2"&gt;"ai-loading"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  templates &lt;span class="se"&gt;\&lt;/span&gt;
  static &lt;span class="se"&gt;\&lt;/span&gt;
  app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result contained zero matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also removed an old &lt;code&gt;.btn-back&lt;/code&gt; style that the current dashboard no longer used.&lt;/p&gt;

&lt;h3&gt;
  
  
  A CSS Definition Does Not Prove That the Class Is Used
&lt;/h3&gt;

&lt;p&gt;A class existing in a stylesheet and a class being used by the application are different things.&lt;/p&gt;

&lt;p&gt;Both sides must be checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does the CSS definition exist?
Is it referenced by HTML or JavaScript?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When only the definition remains and there are no references, it becomes a deletion candidate.&lt;/p&gt;

&lt;p&gt;However, JavaScript may apply classes dynamically, so checking HTML alone is not sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. A Red Cross from &lt;code&gt;grep&lt;/code&gt; Did Not Mean an Error
&lt;/h2&gt;

&lt;p&gt;When I searched for old wording and class names, the command displayed no matches.&lt;/p&gt;

&lt;p&gt;However, my terminal showed a red cross.&lt;/p&gt;

&lt;p&gt;At first, I thought the command had failed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;grep&lt;/code&gt; exit statuses mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0: A matching line was found
1: No matching line was found
2 or higher: The command itself failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, exit code &lt;code&gt;1&lt;/code&gt; meant that the old text was not found.&lt;/p&gt;

&lt;p&gt;That was the expected result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No match
=
No obsolete remainder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Do not judge success or failure only from terminal colors or symbols.&lt;/p&gt;

&lt;p&gt;Check what the command’s exit status actually means.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Zero Search Results Are Still a Valid Result
&lt;/h3&gt;

&lt;p&gt;Searches are not only useful for finding problems.&lt;/p&gt;

&lt;p&gt;They can also verify that something no longer exists.&lt;/p&gt;

&lt;p&gt;After deletion, searching for the same name and receiving zero matches confirms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The deleted identifier no longer remains
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case, no output is the result.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You do not need to memorize every command in this article.&lt;/p&gt;

&lt;p&gt;What matters is understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What you are searching for&lt;/li&gt;
&lt;li&gt;What a match or no match means&lt;/li&gt;
&lt;li&gt;How to reuse the command later from your shell history or notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The regular expression used below can simply be understood as a condition that searches for Git conflict markers one complete line at a time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  10. Searching for Git Conflict Markers Produced Many CSS Matches
&lt;/h2&gt;

&lt;p&gt;I next searched for leftover Git conflict markers.&lt;/p&gt;

&lt;p&gt;When Git produces a merge conflict, files may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
Current content
=======
Other content
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; branch-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first search was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-RniE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;|=======|&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.git &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.venv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;__pycache__ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.pytest_cache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produced many matches from divider comments in &lt;code&gt;static/style.css&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* ========================================
Section heading
======================================== */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The search term &lt;code&gt;=======&lt;/code&gt; matched the decorative CSS separator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restricting the Search to the Start of a Line Was Still Not Enough
&lt;/h3&gt;

&lt;p&gt;I then restricted the pattern to the beginning of a line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-RniE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'^(&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; |=======|&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; )'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.git &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.venv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;__pycache__ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.pytest_cache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the CSS divider lines also began with equal signs.&lt;/p&gt;

&lt;p&gt;They were still detected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Matching Only the Actual Git Conflict Format
&lt;/h3&gt;

&lt;p&gt;Finally, I restricted the pattern using both the beginning and end of each line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-RniE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'^(&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; .+|=======$|&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; .+)$'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.git &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.venv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;__pycache__ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.pytest_cache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was zero matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that no Git conflict markers remained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Many Search Results Do Not Necessarily Mean Many Problems
&lt;/h3&gt;

&lt;p&gt;This experience showed that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Many search results
≠
Many actual problems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A broad pattern may match normal comments or code.&lt;/p&gt;

&lt;p&gt;In this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goal: Find Git conflict markers
        ↓
Search for lines containing =======
        ↓
Normal CSS divider comments also match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Before interpreting the results, ask:&lt;/p&gt;

&lt;p&gt;What normal code could also match this search condition?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Use Search Commands for Hypothesis Testing
&lt;/h2&gt;

&lt;p&gt;I did not use &lt;code&gt;grep&lt;/code&gt; only as a text-finding tool.&lt;/p&gt;

&lt;p&gt;I used it to test hypotheses such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old Excel logic may still remain
Unused CSS may still remain
Outdated UI wording may still remain
Git conflict markers may still remain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Form a hypothesis
        ↓
Search
        ↓
Inspect the results
        ↓
Decide whether they are real issues or false positives
        ↓
Adjust the code or search condition
        ↓
Search again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not memorizing the command.&lt;/p&gt;

&lt;p&gt;It is knowing what you are trying to verify.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Group Related Changes and Commit Before Another Purpose Gets Mixed In
&lt;/h2&gt;

&lt;p&gt;The changes affected five files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.py
config.py
requirements.txt
static/style.css
templates/success.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although several files changed, all changes served one purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Remove obsolete specifications and unused code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I therefore recorded them in one commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Remove old Excel logic and unused code"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Too Many Small Commits and One Huge Commit Are Both Hard to Read
&lt;/h3&gt;

&lt;p&gt;Creating a commit for every changed line can fragment the work unnecessarily.&lt;/p&gt;

&lt;p&gt;On the other hand, combining many unrelated changes makes the diff difficult to understand later.&lt;/p&gt;

&lt;p&gt;I used the following rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Group related changes
Commit before a different purpose becomes mixed in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In logistics terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cargo for the same destination
        ↓
Load it together

Cargo for a different destination
        ↓
Ship before the loads become mixed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checks Before Committing
&lt;/h3&gt;

&lt;p&gt;Before committing, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git status&lt;/code&gt; showed which files had changed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git diff --stat&lt;/code&gt; showed the approximate scale of the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.py                 | 10 +---------
config.py              |  4 ----
requirements.txt       |  1 -
static/style.css       |  8 --------
templates/success.html |  2 +-
5 files changed, 2 insertions(+), 23 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, I confirmed that no unrelated files had been included.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Running the Final Tests
&lt;/h2&gt;

&lt;p&gt;After committing and pushing the changes, I ran pytest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;================================================== test session starts ==================================================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/tosane/sales_data_app
plugins: anyio-4.13.0
collected 3 items

test_prompts.py ...                                                                                                [100%]

=================================================== 3 passed in 0.20s ===================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three tests passed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 passed in 0.20s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the current pytest suite consists of only three tests focused mainly on prompt-generation logic.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pytest passed
≠
Every part of the application is guaranteed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I combined the tests with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search verification&lt;/li&gt;
&lt;li&gt;Diff inspection&lt;/li&gt;
&lt;li&gt;UI inspection&lt;/li&gt;
&lt;li&gt;Git status checks&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Having tests and sufficiently protecting every important behavior are not the same thing.&lt;/p&gt;

&lt;p&gt;It is necessary to understand which parts of the application are actually covered.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  14. Checking &lt;code&gt;working tree clean&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, I checked the Git state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No uncommitted changes remained
The local main branch matched GitHub
No unfinished files remained
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fixing the Code Is Not the End of the Task
&lt;/h3&gt;

&lt;p&gt;For this inspection, I defined completion as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modify the code
        ↓
Review the diff
        ↓
Run the tests
        ↓
Commit the changes
        ↓
Push to GitHub
        ↓
Confirm synchronization with GitHub
        ↓
Confirm that no unfinished changes remain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The code was fixed
≠
The task is complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;working tree clean&lt;/code&gt; is not merely a status message.&lt;/p&gt;

&lt;p&gt;It means the next task can begin without mixing in unfinished changes from the previous task.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In logistics terms, the delivery is not finished until the truck returns and the cargo area is checked for anything left behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Recording the Work in the README Files
&lt;/h2&gt;

&lt;p&gt;After cleaning up the code and repository, I updated two README files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;sales_data_app&lt;/code&gt; README&lt;/li&gt;
&lt;li&gt;My GitHub profile README&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;sales_data_app&lt;/code&gt; README
&lt;/h3&gt;

&lt;p&gt;I added the following items to the update history:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organized demo videos, thumbnails, and screenshots&lt;/li&gt;
&lt;li&gt;Updated README image paths&lt;/li&gt;
&lt;li&gt;Cleaned up &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cleaned up &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Removed obsolete Excel processing&lt;/li&gt;
&lt;li&gt;Removed unused imports, dependencies, and CSS&lt;/li&gt;
&lt;li&gt;Updated an outdated page title&lt;/li&gt;
&lt;li&gt;Confirmed that all three pytest tests passed&lt;/li&gt;
&lt;li&gt;Confirmed that the Git working tree was clean&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  My GitHub Profile README
&lt;/h3&gt;

&lt;p&gt;I recorded the repository-wide inspection as a milestone for July 19, 2026.&lt;/p&gt;

&lt;p&gt;I also updated my total study time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;143 total hours
        ↓
148 total hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README update was recorded with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Document repository inspection and 148 study hours"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  16. Results of the Five-Hour Inspection
&lt;/h2&gt;

&lt;p&gt;The final results were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Work time              5 hours
Total study time       148 hours
Changed code files     5 files
Diff                   23 lines removed, 2 lines changed
Old specification      0 remaining matches
Git conflict markers   0 matches
pytest                 3 passed
Difference from GitHub None
Final state            working tree clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No new feature was added.&lt;/p&gt;

&lt;p&gt;However, I aligned all of the following with the current specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code
Dependencies
HTML text
CSS
README
Screenshots
Directory structure
.gitignore
.dockerignore
Git state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Working Applications Can Still Contain Old Parts
&lt;/h3&gt;

&lt;p&gt;Even if an application runs correctly, code and dependencies from older architectures may remain.&lt;/p&gt;

&lt;p&gt;In this project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Excel-centered architecture
        ↓
Migration to PostgreSQL
        ↓
Old Excel directory settings and openpyxl become unnecessary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When feature development continues without maintenance, old specifications can quietly remain.&lt;/p&gt;

&lt;p&gt;The repository should therefore be inspected periodically to confirm that it matches the current architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. README Files and Images Are Part of the Software
&lt;/h3&gt;

&lt;p&gt;Even when the code is current, outdated README text or screenshots present an outdated version of the project.&lt;/p&gt;

&lt;p&gt;In a public repository, all of the following are part of the deliverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code
README
Screenshots
Demo videos
Directory structure
Commit history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README is not an optional attachment outside the codebase.&lt;/p&gt;

&lt;p&gt;It is often the entry point through which users, recruiters, and other developers understand the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. GitHub Contents and Docker Contents Are Different
&lt;/h3&gt;

&lt;p&gt;README files and tests belong on GitHub.&lt;/p&gt;

&lt;p&gt;They may not belong in the production Docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub
└── Stores documentation, tests, and development history

Docker
└── Contains the files required to run the application in production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt; should not automatically contain the same rules.&lt;/p&gt;

&lt;p&gt;Each file must be evaluated according to its purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deletion Requires Evidence
&lt;/h3&gt;

&lt;p&gt;Deleting code merely because it appears unused is dangerous.&lt;/p&gt;

&lt;p&gt;I used the following sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search
↓
Inspect references
↓
Compare with the current specification
↓
Delete
↓
Search again
↓
Test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion is not simply the act of reducing code.&lt;/p&gt;

&lt;p&gt;It should be performed only after you can explain why the code is no longer required.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Zero Search Results Can Be Important
&lt;/h3&gt;

&lt;p&gt;When confirming that obsolete code no longer remains, no matches from &lt;code&gt;grep&lt;/code&gt; can be the expected result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search intended to find a problem
Matches found → Something needs inspection

Search intended to confirm removal
No matches → Expected state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meaning of a search result depends on the purpose of the search.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Search Conditions Can Produce False Positives
&lt;/h3&gt;

&lt;p&gt;Searching for &lt;code&gt;=======&lt;/code&gt; matched not only Git conflict markers but also CSS separator comments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Many search results
≠
Many problems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The search condition itself may be too broad.&lt;/p&gt;

&lt;p&gt;Do not judge only by the number of matches. Inspect why each result matched.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Divide Commits by Purpose, Not File Count
&lt;/h3&gt;

&lt;p&gt;The cleanup changed five files.&lt;/p&gt;

&lt;p&gt;However, every change served the same purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Removing obsolete specifications and unused code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I therefore grouped them into one commit.&lt;/p&gt;

&lt;p&gt;A useful commit boundary is based less on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many files changed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and more on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why were these files changed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. &lt;code&gt;working tree clean&lt;/code&gt; Is the Starting Condition for the Next Task
&lt;/h3&gt;

&lt;p&gt;Starting new work while uncommitted changes remain can mix unrelated purposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current changes
+
Next changes
=
A commit whose purpose is difficult to understand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By confirming &lt;code&gt;working tree clean&lt;/code&gt;, the next task can begin with an empty cargo area.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reusable Repository Inspection Checklist
&lt;/h2&gt;

&lt;p&gt;The following checklist summarizes the process in a form that can be reused in other projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Are there unused imports?&lt;/li&gt;
&lt;li&gt;[ ] Do settings from retired specifications remain?&lt;/li&gt;
&lt;li&gt;[ ] Are unused functions or processes still present?&lt;/li&gt;
&lt;li&gt;[ ] Do references to old directories or filenames remain?&lt;/li&gt;
&lt;li&gt;[ ] Are there unused CSS classes?&lt;/li&gt;
&lt;li&gt;[ ] Could any apparently unused class be added dynamically by JavaScript?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Are unused libraries still listed?&lt;/li&gt;
&lt;li&gt;[ ] Did you search the code before removing a dependency?&lt;/li&gt;
&lt;li&gt;[ ] Could another library depend on it indirectly?&lt;/li&gt;
&lt;li&gt;[ ] Does the application still run after removing it?&lt;/li&gt;
&lt;li&gt;[ ] Do the tests still pass?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  README and UI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Does the README match the current implementation?&lt;/li&gt;
&lt;li&gt;[ ] Are the screenshots current?&lt;/li&gt;
&lt;li&gt;[ ] Do image paths match the current directory structure?&lt;/li&gt;
&lt;li&gt;[ ] Does the demo video still match the interface?&lt;/li&gt;
&lt;li&gt;[ ] Do outdated UI labels or HTML &lt;code&gt;title&lt;/code&gt; values remain?&lt;/li&gt;
&lt;li&gt;[ ] Does the README describe any feature that no longer exists?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File Structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Are videos, images, and documents organized by purpose?&lt;/li&gt;
&lt;li&gt;[ ] Are unnecessary files scattered in the repository root?&lt;/li&gt;
&lt;li&gt;[ ] Do backup files remain?&lt;/li&gt;
&lt;li&gt;[ ] Can the purpose of a file be understood from its name?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Does &lt;code&gt;.gitignore&lt;/code&gt; exclude sensitive information?&lt;/li&gt;
&lt;li&gt;[ ] Are virtual environments and caches excluded?&lt;/li&gt;
&lt;li&gt;[ ] Are local databases and raw data excluded?&lt;/li&gt;
&lt;li&gt;[ ] Did you review the changed files with &lt;code&gt;git status&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] Did you review the diff with &lt;code&gt;git diff&lt;/code&gt; or &lt;code&gt;git diff --stat&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] Are unrelated changes mixed together?&lt;/li&gt;
&lt;li&gt;[ ] Do Git conflict markers remain?&lt;/li&gt;
&lt;li&gt;[ ] Did you push the changes to GitHub?&lt;/li&gt;
&lt;li&gt;[ ] Is the working tree clean?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Does &lt;code&gt;.dockerignore&lt;/code&gt; exclude &lt;code&gt;.git&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] Is &lt;code&gt;.env&lt;/code&gt; excluded from the build context?&lt;/li&gt;
&lt;li&gt;[ ] Are virtual environments and caches excluded?&lt;/li&gt;
&lt;li&gt;[ ] Are local databases and Excel files excluded?&lt;/li&gt;
&lt;li&gt;[ ] Are production-unnecessary README files, images, and videos excluded?&lt;/li&gt;
&lt;li&gt;[ ] Have you distinguished files that belong on GitHub from files required by Docker?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final Verification
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Did you search again for deleted names?&lt;/li&gt;
&lt;li&gt;[ ] Did you check whether search results were false positives?&lt;/li&gt;
&lt;li&gt;[ ] Did the tests pass?&lt;/li&gt;
&lt;li&gt;[ ] Do you understand what the tests cover?&lt;/li&gt;
&lt;li&gt;[ ] Did you visually inspect the necessary screens?&lt;/li&gt;
&lt;li&gt;[ ] Did you update the README with the work performed?&lt;/li&gt;
&lt;li&gt;[ ] Are the local repository and GitHub synchronized?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I did not add a new feature during these five hours.&lt;/p&gt;

&lt;p&gt;Even so, I brought the repository to the following state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code matches the current specification&lt;/li&gt;
&lt;li&gt;Obsolete dependencies have been removed&lt;/li&gt;
&lt;li&gt;The README matches the latest UI&lt;/li&gt;
&lt;li&gt;The purpose of videos and images is easier to understand&lt;/li&gt;
&lt;li&gt;Files stored on GitHub are organized&lt;/li&gt;
&lt;li&gt;Unnecessary documentation is not included in the production Docker image&lt;/li&gt;
&lt;li&gt;The tests pass&lt;/li&gt;
&lt;li&gt;The repository is synchronized with GitHub&lt;/li&gt;
&lt;li&gt;The working tree is clean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In logistics, loading cargo is not the only responsibility.&lt;/p&gt;

&lt;p&gt;The following also matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is unnecessary cargo mixed in?
Is the loading location easy to understand?
Do the cargo and shipping documents match?
Was anything left behind after delivery?
Can the next trip begin with an empty truck?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Software development was similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add code
        ↓
Unload unnecessary code
        ↓
Align the documentation with the current state
        ↓
Organize the files included in production
        ↓
Run tests
        ↓
Ship to GitHub
        ↓
Confirm working tree clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strongest lesson from this inspection was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A repository-wide inspection is not a task for deleting unnecessary files.&lt;/p&gt;

&lt;p&gt;It is the process of aligning the current specification with the code, dependencies, UI, documentation, and delivery state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Before loading the next feature, inspect the current cargo area.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These five hours became maintenance time that made the next stage of development safer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Original Japanese Article on Qiita
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932/items/02de476fad8f0c1261e0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/02de476fad8f0c1261e0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
    <item>
      <title>“Save” and “Update” Are Not the Same — How a Former Okonomiyaki Chef Removed Confusing UI from a Flask App</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 13:27:53 +0000</pubDate>
      <link>https://dev.to/tosane932/save-and-update-are-not-the-same-how-a-former-okonomiyaki-chef-removed-confusing-ui-from-a-1m3p</link>
      <guid>https://dev.to/tosane932/save-and-update-are-not-the-same-how-a-former-okonomiyaki-chef-removed-confusing-ui-from-a-1m3p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have now completed a total of &lt;strong&gt;143 hours of Python study&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;About 11 years ago, I worked as a Hiroshima-style okonomiyaki chef.&lt;/p&gt;

&lt;p&gt;I performed live cooking and sales demonstrations at major department stores and event spaces across Japan, including Iwate, the Kanto region, Kansai, and Kyushu.&lt;/p&gt;

&lt;p&gt;My responsibilities included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hiring temporary sales staff&lt;/li&gt;
&lt;li&gt;Estimating how many products we could sell&lt;/li&gt;
&lt;li&gt;Ordering ingredients without creating excessive waste&lt;/li&gt;
&lt;li&gt;Avoiding losses during limited event periods&lt;/li&gt;
&lt;li&gt;Thinking about profit and cost from a business-owner’s perspective&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had to move quickly while making decisions about both daily operations and profitability.&lt;/p&gt;

&lt;p&gt;You can also find my profile and projects here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/tosane932" rel="noopener noreferrer"&gt;https://github.com/tosane932/tosane932&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am currently developing a bakery sales management application with Flask.&lt;/p&gt;

&lt;p&gt;The application mainly supports the following workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a monthly product menu&lt;/li&gt;
&lt;li&gt;Enter the number of products sold each day&lt;/li&gt;
&lt;li&gt;View sales rankings and charts&lt;/li&gt;
&lt;li&gt;Receive improvement suggestions through the Gemini API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The features were already working.&lt;/p&gt;

&lt;p&gt;However, when I used the application while imagining that I was the manager of a real bakery, I noticed several small but important points of confusion.&lt;/p&gt;

&lt;p&gt;This work was not simply a color redesign.&lt;/p&gt;

&lt;p&gt;I reviewed the wording, colors, navigation, and input-state displays from the following perspective:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would a store manager hesitate when looking at this screen?&lt;br&gt;&lt;br&gt;
Could the wording cause an incorrect operation or misunderstanding?&lt;br&gt;&lt;br&gt;
Is the next action naturally clear?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I worked at department-store events, I cooked products myself, planned sales pitches, explained the products to customers, and completed each sale.&lt;/p&gt;

&lt;p&gt;I combined that experience with what I had previously learned about web design.&lt;/p&gt;

&lt;p&gt;My goal was to improve the application so that it could be used comfortably by people of different ages and levels of technical experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Design goal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The purpose of this improvement was not to make the application more decorative.&lt;/p&gt;

&lt;p&gt;The goal was to remove causes of confusion before the store manager encountered them.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Application
&lt;/h2&gt;

&lt;p&gt;The application I improved is a bakery sales management system built with Flask.&lt;/p&gt;

&lt;p&gt;The main technologies are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;SQLAlchemy&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Gemini API&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application has four main screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top page&lt;/li&gt;
&lt;li&gt;Product-registration completion page&lt;/li&gt;
&lt;li&gt;Daily sales-entry page&lt;/li&gt;
&lt;li&gt;Sales analysis dashboard&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problems I Noticed Before the Improvement
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. The Top Page Displayed a Future Year
&lt;/h2&gt;

&lt;p&gt;The top page is used to register the product menu for a particular month.&lt;/p&gt;

&lt;p&gt;However, when I opened the year-selection menu in 2026, it also displayed 2027.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2027
2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, I wondered whether I had accidentally registered products for 2027 in the database.&lt;/p&gt;

&lt;p&gt;After checking the application, I discovered that the database was not the cause.&lt;/p&gt;

&lt;p&gt;The year 2027 had been written directly into the HTML as an option.&lt;/p&gt;

&lt;p&gt;The product-menu registration page is mainly used to create the current operating year’s menu.&lt;/p&gt;

&lt;p&gt;Therefore, I changed the top page so that only the current year can be selected.&lt;/p&gt;

&lt;p&gt;The sales dashboard has a different purpose.&lt;/p&gt;

&lt;p&gt;It is used to review previous results, so I changed it to display the current year and past years only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% for y in range(current_year, current_year - 4, -1) %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In 2026, the options are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026
2025
2024
2023
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Future sales results do not exist yet, so there is no reason to show future years on the analysis screen.&lt;/p&gt;

&lt;p&gt;This was a small inconsistency, but it could make a user wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What will happen if I select 2027?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of waiting for an error to occur, I removed the contradiction before the user had to think about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Button Said “Save” Even Though the Operation Was an Update
&lt;/h2&gt;

&lt;p&gt;On the daily sales-entry page, the user enters how many units of each product were sold that day.&lt;/p&gt;

&lt;p&gt;The backend was already implemented like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qty_int&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;qty_int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When data for the same product and date already exists, the registered quantity is replaced with the new value.&lt;/p&gt;

&lt;p&gt;For example, when 14 units are already registered and the user enters 17, the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14 → 17
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14 + 17 = 31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Daily sales entry does not use an additive model.&lt;/p&gt;

&lt;p&gt;When 14 units are already registered and the user enters 17, the stored value changes from &lt;code&gt;14&lt;/code&gt; to &lt;code&gt;17&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, the original button text was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;💾 Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That wording did not tell the user whether the operation would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a new quantity&lt;/li&gt;
&lt;li&gt;Replace the current quantity with the entered value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when the backend behavior is correct, a mismatch between the actual operation and the UI wording can cause misunderstanding.&lt;/p&gt;

&lt;p&gt;I changed the button to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    💾 Update Today's Sales Quantities
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also changed the success message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% if success %}
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"success-msg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        ✅ Today's sales quantities were updated!
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the word “update” instead of “save,” the result of the operation became clearer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UI warning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Correct internal processing is not enough.&lt;/p&gt;

&lt;p&gt;When the visible wording does not match the actual behavior, users may misunderstand the operation.&lt;/p&gt;

&lt;p&gt;I replaced “Save” with “Update Today's Sales Quantities” to make the overwrite behavior explicit.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Users Could Forget Whether They Had Already Entered Today’s Sales
&lt;/h2&gt;

&lt;p&gt;The daily sales-entry page can be opened multiple times in one day.&lt;/p&gt;

&lt;p&gt;A store may not enter all data only once after closing.&lt;/p&gt;

&lt;p&gt;For example, the workflow might be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enter an interim quantity of 14&lt;/li&gt;
&lt;li&gt;Sell more products later&lt;/li&gt;
&lt;li&gt;Open the entry page again&lt;/li&gt;
&lt;li&gt;Correct the final quantity to 17&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before the improvement, the page did not show how many units were already stored in the database.&lt;/p&gt;

&lt;p&gt;This could make a manager wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did I already enter today’s sales for this product?&lt;br&gt;&lt;br&gt;
Was 14 already registered?&lt;br&gt;&lt;br&gt;
If I enter 17 now, will it become 31?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I therefore displayed the current registered quantity below each product name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Premium White Bread
🟢 Registered today: 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also placed the current quantity directly into the input field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input field: 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user changes it to 17 and submits the form, the database value is replaced.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retrieving Today’s Registered Quantities
&lt;/h2&gt;

&lt;p&gt;I added a helper function that converts the selected date’s sales records into a dictionary containing product IDs and quantities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_today_sales_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return registered sales quantities by product ID.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_date&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sale&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned value looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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 dictionary key is the product ID.&lt;/p&gt;

&lt;p&gt;The value is the registered sales quantity for that date.&lt;/p&gt;




&lt;h2&gt;
  
  
  Passing the Current Values to the Template
&lt;/h2&gt;

&lt;p&gt;The daily sales-entry route passes the current sales data to the template for both GET and POST requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;input_sales&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;date_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sale_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;date_str&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;quantities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sales data submission received &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;for date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;product_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;qty_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid quantity format skipped: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;qty_int&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Negative quantity skipped: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qty_int&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qty_int&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;sale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;qty_int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sales data successfully committed &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;for date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_get_current_products&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;today_sales&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_get_today_sales_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;today&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_get_current_products&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;today_sales&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_get_today_sales_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;today&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;After the POST request, the route retrieves the latest values from the database again.&lt;/p&gt;

&lt;p&gt;As a result, the page displayed after submission shows the newly updated quantities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Displaying Current Values in the Template
&lt;/h2&gt;

&lt;p&gt;I changed the product-entry section in &lt;code&gt;input.html&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% for product in products %}
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
            &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"product_id"&lt;/span&gt;
            &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ product.id }}"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-info"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                {{ product.name }}
            &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"registered-quantity"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                🟢 Registered today:
                {{ today_sales.get(product.id, 0) }}
            &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-price"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            ¥{{ product.price }}
        &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
            &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"quantity"&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"qty-input"&lt;/span&gt;
            &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
            &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ today_sales.get(product.id, 0) }}"&lt;/span&gt;
            &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When no registered data exists, the second argument of &lt;code&gt;get()&lt;/code&gt; displays zero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ today_sales.get(product.id, 0) }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same value is used as the input field’s initial value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;value="{{ today_sales.get(product.id, 0) }}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the user can understand the current registration status immediately after opening the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Did Not Make the Text Green
&lt;/h2&gt;

&lt;p&gt;At first, I considered displaying the entire “Registered today” message in green.&lt;/p&gt;

&lt;p&gt;However, changing the text color could reduce readability depending on the background or the user’s display environment.&lt;/p&gt;

&lt;p&gt;I kept the existing dark text color and used only a green circle as a visual marker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"registered-quantity"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    🟢 Registered today:
    {{ today_sales.get(product.id, 0) }}
&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS inherits the normal text color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.registered-quantity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.4&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;I used three signals together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A green circle&lt;/li&gt;
&lt;li&gt;The words “Registered today”&lt;/li&gt;
&lt;li&gt;Bold text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A user who has difficulty distinguishing colors can still understand the meaning from the text.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Accessibility note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Colors are used as a supporting cue.&lt;/p&gt;

&lt;p&gt;I do not rely on color alone. Icons, specific wording, and button shapes are also used so that the interface is less dependent on color perception or technical experience.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Improving the Spacing Between Products
&lt;/h2&gt;

&lt;p&gt;After adding the registered quantity, each row contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product name&lt;/li&gt;
&lt;li&gt;Current quantity&lt;/li&gt;
&lt;li&gt;Price&lt;/li&gt;
&lt;li&gt;Input field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The products began to look visually crowded.&lt;/p&gt;

&lt;p&gt;I added spacing and a divider between each product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.product-row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#f3ecde&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 product name and registered quantity are displayed vertically as one information group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.product-info&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;This made the boundary between products easier to recognize.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improving Navigation Between Screens
&lt;/h2&gt;

&lt;p&gt;Before the improvement, it was not always obvious how to move to the next task.&lt;/p&gt;

&lt;p&gt;I added navigation based on the actual store workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Register the product menu
        ↓
Enter daily sales
        ↓
Review sales analysis and rankings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a dashboard button to the daily sales-entry page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
    &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ url_for('dashboard') }}"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-submit btn-dashboard-link"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    📊 View the Store Health Report
    (Sales Analysis and Rankings)
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also replaced the simple text link to the top page with a button-shaped link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
    &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ url_for('index') }}"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"input-home-link"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    🍞 Return to the Top Page
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard received links to the daily entry page and the top page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard-actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
        &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ url_for('input_sales') }}"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard-action dashboard-input-link"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        📝 Enter Daily Sales
    &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
        &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ url_for('index') }}"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard-action dashboard-home-link"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        🍞 Return to the Top Page
    &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next business operation is now easier to reach from every screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Assigning a Meaning to Each Button Color
&lt;/h2&gt;

&lt;p&gt;Instead of choosing unrelated colors for each page, I assigned colors according to the type of action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Green
→ Product-menu registration

Honey
→ Daily sales entry and updates

Strawberry red
→ Sales analysis and rankings

Light cream
→ AI questions

White
→ Return to the top page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pistachio Green for Product Registration
&lt;/h2&gt;

&lt;p&gt;I used a pistachio- or leaf-inspired green for the product-menu registration button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.btn-menu-register&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#7fa45a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.btn-menu-register&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#688c47&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;h2&gt;
  
  
  Honey Color for Daily Sales Entry
&lt;/h2&gt;

&lt;p&gt;I used a warm color associated with bread and honey for daily sales entry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c9a063&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&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;h2&gt;
  
  
  Strawberry Red for Sales Analysis
&lt;/h2&gt;

&lt;p&gt;I used a strawberry-jam red for the sales analysis button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-dashboard-link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e85d5d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-dashboard-link&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#cf4646&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;This preserves the bakery theme while clearly separating analysis from daily input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Light Cream for AI Questions
&lt;/h2&gt;

&lt;p&gt;The “Ask for Today’s Message” button and the dashboard’s “Ask for Detailed Advice” button both call AI features.&lt;/p&gt;

&lt;p&gt;I therefore gave them the same light cream color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dashboard-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-ai-advice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f7eed6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b5a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dashboard-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-ai-advice&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ebdcb9&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;Using a consistent color makes it easier to recognize the same type of action across different pages.&lt;/p&gt;

&lt;p&gt;However, the interface does not depend on color alone.&lt;/p&gt;

&lt;p&gt;Icons and specific button text are also used.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving CSS Out of the HTML Files
&lt;/h2&gt;

&lt;p&gt;As part of this improvement, I moved CSS from the individual HTML files into &lt;code&gt;static/style.css&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales_data_app/
├── app.py
├── templates/
│   ├── index.html
│   ├── input.html
│   ├── dashboard.html
│   └── success.html
└── static/
    └── style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each HTML file loads the stylesheet as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt;
    &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;
    &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ url_for('static', filename='style.css') }}"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added page-specific classes to each &lt;code&gt;body&lt;/code&gt; element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"input-page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard-page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"success-page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS selectors begin with the page class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Applied only to the daily entry page */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dashboard-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Applied only to the dashboard */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even when two pages use the same &lt;code&gt;.btn-submit&lt;/code&gt; class name, this reduces the risk of one page’s styles affecting another page unexpectedly.&lt;/p&gt;




&lt;h2&gt;
  
  
  A CSS Side Effect That Actually Occurred
&lt;/h2&gt;

&lt;p&gt;While reorganizing the CSS, the dashboard’s filter button lost its rounded corners and gained a black border.&lt;/p&gt;

&lt;p&gt;The cause was the interaction between the shared &lt;code&gt;.btn-submit&lt;/code&gt; style and the dashboard-specific style.&lt;/p&gt;

&lt;p&gt;I explicitly defined the required appearance for the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dashboard-page&lt;/span&gt; &lt;span class="nc"&gt;.btn-submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b5a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&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;Centralizing CSS reduces duplicated code, but it also increases the number of elements that one change can affect.&lt;/p&gt;

&lt;p&gt;I learned that shared styles and page-specific styles need clearly defined responsibilities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CSS warning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shared CSS can reduce duplication, but changes may also affect other pages using the same class.&lt;/p&gt;

&lt;p&gt;Common styles and page-specific styles should be separated carefully, and every screen should be reviewed after a change.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  “Easy to Use for Everyone” as the Main Decision Rule
&lt;/h2&gt;

&lt;p&gt;An important concept in my application development is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An application that is easy to use for people of all ages and backgrounds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used that idea as the decision rule for this improvement.&lt;/p&gt;

&lt;p&gt;The changes included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replacing the ambiguous word “Save” with the specific word “Update”&lt;/li&gt;
&lt;li&gt;Displaying the currently registered quantity&lt;/li&gt;
&lt;li&gt;Placing the current value in the input field&lt;/li&gt;
&lt;li&gt;Removing confusing options such as future years&lt;/li&gt;
&lt;li&gt;Assigning consistent colors based on action type&lt;/li&gt;
&lt;li&gt;Using icons and text in addition to color&lt;/li&gt;
&lt;li&gt;Replacing small text links with clearly clickable buttons&lt;/li&gt;
&lt;li&gt;Adding navigation to the next business task on each page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I prioritized reducing thought and hesitation over making the interface visually flashy.&lt;/p&gt;




&lt;h2&gt;
  
  
  How My Okonomiyaki Sales Experience Helped the UI Improvement
&lt;/h2&gt;

&lt;p&gt;I previously performed live okonomiyaki cooking and sales demonstrations at department stores.&lt;/p&gt;

&lt;p&gt;I made the product myself, prepared the sales pitch, explained it to customers, and comple&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>css</category>
      <category>ux</category>
    </item>
    <item>
      <title>📝 Ending Product Sales Without Breaking Sales History — Implementing Soft Deletes and Reducing Gemini API Usage in Flask</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 13:05:26 +0000</pubDate>
      <link>https://dev.to/tosane932/ending-product-sales-without-breaking-sales-history-implementing-soft-deletes-and-reducing-11n5</link>
      <guid>https://dev.to/tosane932/ending-product-sales-without-breaking-sales-history-implementing-soft-deletes-and-reducing-11n5</guid>
      <description>&lt;h2&gt;
  
  
  🍞 Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am a professional truck driver teaching myself Python while developing a bakery sales management application with Flask, PostgreSQL, and the Gemini API.&lt;/p&gt;

&lt;p&gt;At the time of this work, I had completed approximately &lt;strong&gt;135 hours of programming study&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I also made a major update to the application and its README.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I added product-master editing, I encountered several connected problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding or replacing menu items could break their relationship with existing sales history&lt;/li&gt;
&lt;li&gt;Products with sales records could not be physically deleted&lt;/li&gt;
&lt;li&gt;Renaming an existing product could be treated as adding a completely different product&lt;/li&gt;
&lt;li&gt;Opening the dashboard consumed Gemini API requests automatically&lt;/li&gt;
&lt;li&gt;Users received unclear messages when the free API quota was exhausted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent approximately six hours reviewing the entire flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product updates
→ Product discontinuation
→ Database migration
→ Production startup
→ Gemini API execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article records the failed implementation and how I changed it into a safer structure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a long development record, so thank you for your patience.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.12&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;Flask-SQLAlchemy&lt;/li&gt;
&lt;li&gt;Flask-Migrate&lt;/li&gt;
&lt;li&gt;PostgreSQL in production&lt;/li&gt;
&lt;li&gt;SQLite locally&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Render&lt;/li&gt;
&lt;li&gt;Gunicorn&lt;/li&gt;
&lt;li&gt;Google Gemini API&lt;/li&gt;
&lt;li&gt;Chart.js&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;pytest&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The First Error
&lt;/h2&gt;

&lt;p&gt;When I updated the product master, the following error occurred:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psycopg2.errors.NotNullViolation:
null value in column "product_id" of relation "daily_sales"
violates not-null constraint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLAlchemy attempted to execute an update similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE daily_sales
SET product_id = NULL
WHERE daily_sales.id = ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;DailySales.product_id&lt;/code&gt; column was defined with &lt;code&gt;nullable=False&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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;Therefore, when I tried to delete a product connected to existing sales history, SQLAlchemy could not change the related &lt;code&gt;product_id&lt;/code&gt; to &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cause: I Deleted Existing Products and Registered Them Again
&lt;/h2&gt;

&lt;p&gt;The original product-master update logic deleted all products for the selected year and month, then recreated them from the submitted form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks simple when considering only the product-master table.&lt;/p&gt;

&lt;p&gt;However, the daily-sales table references &lt;code&gt;Product.id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products
└── id = 1

daily_sales
└── product_id = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deleting and recreating the product generates a different primary key even when the visible product name is unchanged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before deletion:
White Bread, id = 1

After re-registration:
White Bread, id = 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To a human, both records represent the same product.&lt;/p&gt;

&lt;p&gt;To the database, they are completely different rows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 1: Update Products by ID, Not by Name
&lt;/h2&gt;

&lt;p&gt;I added each existing product's &lt;code&gt;Product.id&lt;/code&gt; to the form as a hidden field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"product_id"&lt;/span&gt;
    &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ product.id }}"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New products added through JavaScript receive an empty ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"product_id"&lt;/span&gt;
    &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Flask route receives product IDs, names, and prices in matching order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;product_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;product_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;product_prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;products_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;product_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;product_prices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;products_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="p"&gt;),&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;Existing products are updated by primary key.&lt;/p&gt;

&lt;p&gt;Only products without an ID are inserted as new rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;existing_products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;existing_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;existing_products&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;existing_dict&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;existing_product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;existing_product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;existing_product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;existing_product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&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;Now a product name can be changed while preserving the same &lt;code&gt;Product.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The existing sales history remains connected to the same database record.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 2: Use Soft Deletes Instead of Physical Deletes
&lt;/h2&gt;

&lt;p&gt;For discontinued products, I decided not to delete the database row.&lt;/p&gt;

&lt;p&gt;Instead, I added a sales-status flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;true&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;I collect the IDs submitted by the form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;submitted_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products_data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any existing product that is no longer included in the form is marked as inactive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;existing_products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;submitted_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only active products are shown on the sales-entry screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides both behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discontinued product
→ Hidden from future sales entry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Past sales history
→ Still connected to the original Product.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product disappears from normal operation without deleting the historical record.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Soft Deletion Was the Better Fit
&lt;/h2&gt;

&lt;p&gt;Physical deletion means removing the row itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE FROM products ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Soft deletion means keeping the row and changing its state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;is_active = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a system that stores sales history, the product record is part of the historical evidence.&lt;/p&gt;

&lt;p&gt;Even when the product is no longer sold, past records still need to answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which product was sold?&lt;/li&gt;
&lt;li&gt;What was its name?&lt;/li&gt;
&lt;li&gt;What price was registered?&lt;/li&gt;
&lt;li&gt;Which sales records referred to it?&lt;/li&gt;
&lt;li&gt;When did it stop being active?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deleting the row would make that history difficult or impossible to interpret safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 3: Add the Column with Flask-Migrate
&lt;/h2&gt;

&lt;p&gt;I generated a migration locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db migrate &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add is_active to products"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated SQLite-oriented migration used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I changed it to a form that is also clear for PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;server_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;true&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then applied and checked the migration locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Render, I confirmed the following migration log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running upgrade -&amp;gt; 043c481b4069, add is_active to products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This showed that the production database had applied the new &lt;code&gt;is_active&lt;/code&gt; column.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important: &lt;code&gt;stamp&lt;/code&gt; and &lt;code&gt;upgrade&lt;/code&gt; Have Different Roles
&lt;/h2&gt;

&lt;p&gt;These two commands are not interchangeable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask db upgrade
→ Executes migrations and changes the database schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask db stamp head
→ Marks the database as current without executing migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;stamp&lt;/code&gt; runs automatically during every startup, an unapplied migration could be marked as already applied.&lt;/p&gt;

&lt;p&gt;That would create a dangerous mismatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alembic history
→ Says the migration is applied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actual database schema
→ Has not changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I therefore removed automatic &lt;code&gt;stamp&lt;/code&gt; logic from the application and used &lt;code&gt;upgrade&lt;/code&gt; in the startup command instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 4: Switch Production Startup to Gunicorn
&lt;/h2&gt;

&lt;p&gt;I replaced Flask's built-in development server with Gunicorn.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gunicorn==26.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production startup command became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [&lt;/span&gt;
    "sh",
    "-c",
    "flask db upgrade &amp;amp;&amp;amp; gunicorn --bind 0.0.0.0:${PORT:-5000} app:app"
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The startup flow is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apply pending migrations
        ↓
If successful, start Gunicorn
        ↓
Serve the Flask application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After deployment, the logs showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting gunicorn 26.0.0
Listening at: http://0.0.0.0:10000
Booting worker
Your service is live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Next Problem: I Exhausted the Gemini API Free Tier
&lt;/h2&gt;

&lt;p&gt;During testing, I encountered the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;429 RESOURCE_EXHAUSTED

Quota exceeded for metric:
generate_content_free_tier_requests

limit: 20
model: gemini-2.5-flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Originally, the application called the Gemini API simply by opening certain pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the dashboard
→ Generate AI business advice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the daily sales-entry page
→ Generate an AI greeting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change the selected period
→ Generate AI business advice again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a free API tier, this design consumed requests unnecessarily.&lt;/p&gt;

&lt;p&gt;A user could spend the daily allowance without intentionally asking for AI analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution 5: Run AI Only When the User Requests It
&lt;/h2&gt;

&lt;p&gt;I changed the default page behavior to display fixed text.&lt;/p&gt;

&lt;p&gt;The application now calls Gemini only when the user presses an AI button.&lt;/p&gt;

&lt;p&gt;I separated the AI analysis into its own API endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/ai-advice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api_ai_advice&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;year_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;month_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;target_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;year_param&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;target_month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;month_param&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;sales_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_get_sales_from_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;target_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;target_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ranked_sales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sales_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_advice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;_generate_ai_advice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;ranked_sales&lt;/span&gt;
            &lt;span class="p"&gt;)&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 browser calls the endpoint only when the button is pressed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadAiAdvice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selectYear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selectMonth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/ai-advice?year=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;month=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aiAdviceText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_advice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;);&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;I used the same principle for the daily greeting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the page
→ Gemini requests: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Press “Today's Message”
→ Gemini requests: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes API usage intentional instead of automatic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preventing Repeated Clicks
&lt;/h2&gt;

&lt;p&gt;While the AI request is running, I disable the button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;aiButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;aiButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyzing...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the request finishes, the button is restored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&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="nx"&gt;aiButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;aiButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🤖 Ask for detailed advice again&lt;/span&gt;&lt;span class="dl"&gt;"&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;This prevents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeated API requests&lt;/li&gt;
&lt;li&gt;Accidental double clicks&lt;/li&gt;
&lt;li&gt;Duplicate processing&lt;/li&gt;
&lt;li&gt;Faster consumption of the free quota&lt;/li&gt;
&lt;li&gt;Confusing overlapping responses&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Showing Different Messages for 429 and 503 Errors
&lt;/h2&gt;

&lt;p&gt;The user-facing meaning of these errors is different.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;429
→ Quota exceeded or rate limited
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;503
→ Service temporarily unavailable or overloaded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I separated the messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;error_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GenerateRequestsPerDayPerProjectPerModel-FreeTier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;error_text&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;☕【Today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s AI analysis limit has been reached】&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your sales data was saved and aggregated normally.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;429&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;error_text&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESOURCE_EXHAUSTED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;error_text&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;☕【The AI is taking a short break】&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;503&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;error_text&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNAVAILABLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;error_text&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🥐【The AI assistant is currently busy】&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detailed exception information is written to the logs instead of being displayed directly to the user.&lt;/p&gt;

&lt;p&gt;This separates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User message
→ Clear and understandable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application log
→ Detailed information for troubleshooting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  An Indentation Error During Deployment
&lt;/h2&gt;

&lt;p&gt;The final deployment also failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IndentationError:
expected an indented block after 'if' statement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type of problem can be detected before deployment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; py_compile app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added it to my pre-deployment inspection routine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git status
git diff
python -m py_compile app.py
pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One incorrect indentation level can prevent the entire production application from starting.&lt;/p&gt;

&lt;p&gt;A quick syntax check is therefore a useful final gate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Product-Master Behavior
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New product
→ INSERT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing product
→ UPDATE using Product.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product removed from the form
→ is_active = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Past sales history
→ Preserved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product name can change without breaking its relationship with existing sales records.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Gemini API Behavior
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open dashboard
→ 0 requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change period and load sales data
→ 0 requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Press “Ask for detailed advice”
→ 1 request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open daily sales-entry page
→ 0 requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Press “Today's Message”
→ 1 request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user now controls when the limited AI resource is consumed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Same Product Name Does Not Mean the Same Database Record
&lt;/h3&gt;

&lt;p&gt;When primary keys are different, the database treats the rows as different products.&lt;/p&gt;

&lt;p&gt;Updates should be based on stable IDs, not only visible names.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Historical Data Should Not Be Deleted Casually
&lt;/h3&gt;

&lt;p&gt;For products connected to sales history, soft deletion was more appropriate than physical deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Migrations Have Three Stages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate
→ Review
→ Apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically generated migrations still need human review, especially when both SQLite and PostgreSQL are involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Automatic AI Execution Is Not Always the Best Design
&lt;/h3&gt;

&lt;p&gt;Calling the AI only when requested improved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page speed&lt;/li&gt;
&lt;li&gt;Free-tier usage&lt;/li&gt;
&lt;li&gt;User understanding&lt;/li&gt;
&lt;li&gt;Operational predictability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Run a Syntax Check Before Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; py_compile app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A one-line indentation error can prevent the production application from starting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This work was much more than adding a delete button.&lt;/p&gt;

&lt;p&gt;The actual process was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product update
        ↓
Foreign-key error
        ↓
Switch to ID-based updates
        ↓
Introduce soft deletion
        ↓
Generate and review a migration
        ↓
Apply it to production
        ↓
Switch production startup to Gunicorn
        ↓
Reduce unnecessary Gemini API requests
        ↓
Improve 429 and 503 error messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal was not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make the product disappear from the screen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I needed to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales history&lt;/li&gt;
&lt;li&gt;Foreign-key relationships&lt;/li&gt;
&lt;li&gt;Production database migrations&lt;/li&gt;
&lt;li&gt;API quotas&lt;/li&gt;
&lt;li&gt;Repeated user input&lt;/li&gt;
&lt;li&gt;Production startup&lt;/li&gt;
&lt;li&gt;User-facing error messages&lt;/li&gt;
&lt;li&gt;Troubleshooting logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after checking all of those areas did the update feel complete.&lt;/p&gt;

&lt;p&gt;My next step is to expand pytest coverage for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product updates&lt;/li&gt;
&lt;li&gt;Soft deletion&lt;/li&gt;
&lt;li&gt;API endpoints&lt;/li&gt;
&lt;li&gt;Error-handling behavior&lt;/li&gt;
&lt;li&gt;Database relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🍞 GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Online Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>postgres</category>
      <category>sqlalchemy</category>
    </item>
    <item>
      <title>My Progress After Two Months and 129 Hours of Learning Python — Building a Bakery Sales Management System</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 07:16:14 +0000</pubDate>
      <link>https://dev.to/tosane932/my-progress-after-two-months-and-129-hours-of-learning-python-building-a-bakery-sales-management-dlk</link>
      <guid>https://dev.to/tosane932/my-progress-after-two-months-and-129-hours-of-learning-python-building-a-bakery-sales-management-dlk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I started learning Python on May 12, 2026.&lt;/p&gt;

&lt;p&gt;Approximately two months have passed, and my total study time has reached &lt;strong&gt;129 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To record my current progress, I have published a bakery sales management system that I developed while learning.&lt;/p&gt;

&lt;p&gt;GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🍞 What I Built
&lt;/h2&gt;

&lt;p&gt;I built a web-based sales management application for bakery stores.&lt;/p&gt;

&lt;p&gt;It is more than a simple sales-entry form.&lt;/p&gt;

&lt;p&gt;The system includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product master management&lt;/li&gt;
&lt;li&gt;Daily sales entry&lt;/li&gt;
&lt;li&gt;Data storage in PostgreSQL&lt;/li&gt;
&lt;li&gt;Sales rankings&lt;/li&gt;
&lt;li&gt;AI-powered sales analysis&lt;/li&gt;
&lt;li&gt;An AI staff assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try the deployed application here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The application runs on a free hosting plan, so it may take a little time to start if the server has been inactive.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📖 Why I Built It
&lt;/h2&gt;

&lt;p&gt;I currently work in logistics as a driver of large and medium-sized trucks.&lt;/p&gt;

&lt;p&gt;Before working in logistics, I also had experience selling food at department-store events.&lt;/p&gt;

&lt;p&gt;In those workplaces, I repeatedly encountered situations where sales management depended heavily on Excel.&lt;/p&gt;

&lt;p&gt;That made me wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could daily sales entry, aggregation, and analysis all be handled in one system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question became the starting point for this application.&lt;/p&gt;

&lt;p&gt;I wanted to build something that reflected actual workplace needs rather than creating a project only for programming practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚒️ Technology Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Python&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Flask&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SQLAlchemy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Flask-Migrate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HTML&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CSS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JavaScript&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fetch API&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Chart.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PostgreSQL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SQLite&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Google Gemini API&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Docker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Docker Compose&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Render&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing and Automation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GitHub Actions&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚙️ Implemented Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Product Registration
&lt;/h3&gt;

&lt;p&gt;Users can register bakery products and their prices for each month.&lt;/p&gt;

&lt;p&gt;The product information is then used by the daily sales-entry feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Daily Sales Entry
&lt;/h3&gt;

&lt;p&gt;Users can enter the number of units sold for each product.&lt;/p&gt;

&lt;p&gt;The page also displays a short seasonal message generated by AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales Analysis
&lt;/h3&gt;

&lt;p&gt;The application aggregates the stored sales data and displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product rankings&lt;/li&gt;
&lt;li&gt;Sales charts&lt;/li&gt;
&lt;li&gt;AI-generated business advice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not only to store numbers, but also to help users understand what the numbers may indicate.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 What I Focused On
&lt;/h2&gt;

&lt;p&gt;In logistics, we work with the assumption that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An accident may happen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tried to apply the same mindset to software development.&lt;/p&gt;

&lt;p&gt;Instead of assuming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The application will probably work correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I designed it around the possibility that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A user may enter invalid data, or an important setting may be missing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input validation&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;li&gt;Application logging&lt;/li&gt;
&lt;li&gt;Environment-variable management&lt;/li&gt;
&lt;li&gt;Fail-Fast behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted the system to detect problems early instead of depending only on users or developers behaving perfectly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defensive Design Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;

&lt;p&gt;The application checks values before saving them to the database.&lt;/p&gt;

&lt;p&gt;This reduces the risk of invalid sales data entering the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment-Variable Checks
&lt;/h3&gt;

&lt;p&gt;The application verifies that required configuration values are available.&lt;/p&gt;

&lt;p&gt;If a required value is missing, the system can stop before a user reaches the broken feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging
&lt;/h3&gt;

&lt;p&gt;Important startup events and errors are written to standard output.&lt;/p&gt;

&lt;p&gt;This makes it easier to investigate problems in Docker and Render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Migration Management
&lt;/h3&gt;

&lt;p&gt;I moved away from relying only on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and introduced Flask-Migrate to manage database schema changes through migration history.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ What Was Most Difficult
&lt;/h2&gt;

&lt;p&gt;The most difficult problem was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It works locally, but it does not work in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I encountered differences involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Render&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Python package versions&lt;/li&gt;
&lt;li&gt;Database connection URLs&lt;/li&gt;
&lt;li&gt;Persistent storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I investigated each issue separately instead of trying to change everything at once.&lt;/p&gt;

&lt;p&gt;For example, I checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the required package was installed&lt;/li&gt;
&lt;li&gt;Whether the version matched &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Whether &lt;code&gt;DATABASE_URL&lt;/code&gt; existed&lt;/li&gt;
&lt;li&gt;Whether the application was connected to SQLite or PostgreSQL&lt;/li&gt;
&lt;li&gt;Whether the migration command completed successfully&lt;/li&gt;
&lt;li&gt;Whether the production logs matched my expectations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process taught me that production problems are not always caused by the application code itself.&lt;/p&gt;

&lt;p&gt;Sometimes the code is correct, but the surrounding infrastructure is incomplete or configured differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚚 From Local Development to Automatic Deployment
&lt;/h2&gt;

&lt;p&gt;The current workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modify the application locally
        ↓
Run tests
        ↓
Push the changes to GitHub
        ↓
GitHub Actions runs pytest
        ↓
Render detects the update
        ↓
The application is built and deployed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is now configured so that a GitHub push triggers automated testing through GitHub Actions and deployment to Render.&lt;/p&gt;

&lt;p&gt;This does not mean the system is perfect.&lt;/p&gt;

&lt;p&gt;However, it is a significant improvement over manually updating the production environment without an automated check.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Testing
&lt;/h2&gt;

&lt;p&gt;I currently use &lt;code&gt;pytest&lt;/code&gt; to test the prompt-generation logic.&lt;/p&gt;

&lt;p&gt;For example, I verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales data is included in the generated prompt&lt;/li&gt;
&lt;li&gt;Important AI instructions are included&lt;/li&gt;
&lt;li&gt;The function returns a string&lt;/li&gt;
&lt;li&gt;The output does not lose required content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The number of tests is still limited.&lt;/p&gt;

&lt;p&gt;My next goal is to expand the test coverage to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database operations&lt;/li&gt;
&lt;li&gt;Input validation&lt;/li&gt;
&lt;li&gt;Flask routes&lt;/li&gt;
&lt;li&gt;API error handling&lt;/li&gt;
&lt;li&gt;Authentication-related behavior&lt;/li&gt;
&lt;li&gt;Integration between application components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also learned that simply writing tests is not enough.&lt;/p&gt;

&lt;p&gt;I need to deliberately break the relevant behavior and confirm that the tests actually detect it.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Current Application Structure
&lt;/h2&gt;

&lt;p&gt;The overall structure is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Flask application
   ↓
SQLAlchemy
   ↓
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For AI-related functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales data
   ↓
Prompt-generation logic
   ↓
Gemini API
   ↓
AI-generated advice
   ↓
Displayed in the browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub
   ↓
GitHub Actions
   ↓
Render
   ↓
Flask application and PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building these connected parts was much more difficult than creating a single page.&lt;/p&gt;

&lt;p&gt;However, it helped me understand how application code, databases, testing, and infrastructure work together.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚧 What I Want to Add Next
&lt;/h2&gt;

&lt;p&gt;I plan to continue developing the system with features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inventory management&lt;/li&gt;
&lt;li&gt;Purchase-order suggestions&lt;/li&gt;
&lt;li&gt;AI-powered stockout prediction&lt;/li&gt;
&lt;li&gt;Profit analysis&lt;/li&gt;
&lt;li&gt;Weekday and seasonal analysis&lt;/li&gt;
&lt;li&gt;More comprehensive automated tests&lt;/li&gt;
&lt;li&gt;Improved error messages&lt;/li&gt;
&lt;li&gt;Better mobile usability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My goal is to develop the project into a system that could genuinely help people working in a busy store.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned After 129 Hours
&lt;/h2&gt;

&lt;p&gt;After approximately two months of study, I have learned that building a working application requires more than writing Python code.&lt;/p&gt;

&lt;p&gt;It also involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding user needs&lt;/li&gt;
&lt;li&gt;Designing database structures&lt;/li&gt;
&lt;li&gt;Managing dependencies&lt;/li&gt;
&lt;li&gt;Handling failures&lt;/li&gt;
&lt;li&gt;Reading logs&lt;/li&gt;
&lt;li&gt;Testing assumptions&lt;/li&gt;
&lt;li&gt;Deploying the application&lt;/li&gt;
&lt;li&gt;Maintaining the production environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still have many areas to improve.&lt;/p&gt;

&lt;p&gt;However, I can now investigate problems by separating them into categories such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Data&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;User input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That change in how I approach problems may be one of the most important results of these 129 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  🍞 Conclusion
&lt;/h2&gt;

&lt;p&gt;Approximately two months and 129 hours after beginning Python, I was able to develop and publish this application.&lt;/p&gt;

&lt;p&gt;There are still many improvements I want to make and many topics I need to learn.&lt;/p&gt;

&lt;p&gt;However, I have tried not to make “studying programming” the final goal.&lt;/p&gt;

&lt;p&gt;Instead, I have focused on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning by building something that actually works.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want to continue adding features and improving the application little by little.&lt;/p&gt;

&lt;p&gt;When I read this article again several months from now, I hope I will be able to think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This was how far I had come at that time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bakery-salesdata.onrender.com/" rel="noopener noreferrer"&gt;https://bakery-salesdata.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🍞 &lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;sales_data_app on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>docker</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>【Render】Migrating from SQLite to PostgreSQL: Fixing the `postgres://` Connection Error and the “Still Using SQLite” Problem [3/3]</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 00:46:11 +0000</pubDate>
      <link>https://dev.to/tosane932/render-migrating-from-sqlite-to-postgresql-fixing-the-postgres-connection-error-and-the-1nn4</link>
      <guid>https://dev.to/tosane932/render-migrating-from-sqlite-to-postgresql-fixing-the-postgres-connection-error-and-the-1nn4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I am &lt;code&gt;tosane932&lt;/code&gt;, a professional truck driver working in logistics while teaching myself Python.&lt;/p&gt;

&lt;p&gt;At the time of writing, I had completed approximately &lt;strong&gt;127 hours of programming study&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the previous article, Part 2 of this three-part series, I removed the familiar &lt;code&gt;db.create_all()&lt;/code&gt; approach and established a database migration foundation using &lt;code&gt;Flask-Migrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This article records the next step in that work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Completely moving from SQLite in the local-style setup to PostgreSQL in the Render production environment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During this infrastructure transition, I encountered two unexpected problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A connection error caused by Render's &lt;code&gt;postgres://&lt;/code&gt; database URL format&lt;/li&gt;
&lt;li&gt;A strange situation where the code had been corrected, but the production logs still showed &lt;code&gt;SQLiteImpl&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I will share the actual sequence of events, including the mistake that turned out not to be in the code at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Background and Purpose of the Migration
&lt;/h2&gt;

&lt;p&gt;Until this point, the deployed application on Render had been using SQLite.&lt;/p&gt;

&lt;p&gt;SQLite is simple and convenient, but the application was running without persistent disk storage.&lt;/p&gt;

&lt;p&gt;Because of the behavior of the hosting environment, database data could be reset during events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redeployment&lt;/li&gt;
&lt;li&gt;Service restart&lt;/li&gt;
&lt;li&gt;Container replacement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was not suitable for a sales-management application that needed to preserve records.&lt;/p&gt;

&lt;p&gt;To build a more reliable production foundation, I decided to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a PostgreSQL database on Render&lt;/li&gt;
&lt;li&gt;Connect the Flask application to it&lt;/li&gt;
&lt;li&gt;Apply schema migrations automatically during deployment&lt;/li&gt;
&lt;li&gt;Preserve production data independently of the application container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was to establish a production database environment that could support actual data-driven operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Implementation Steps and Observed Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Add Database URL Conversion Logic in &lt;code&gt;config.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;DATABASE_URL&lt;/code&gt; provided by Render began with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres://
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the SQLAlchemy configuration I was using expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the URL without conversion caused a connection error.&lt;/p&gt;

&lt;p&gt;To handle the difference safely, I added the following logic to &lt;code&gt;config.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;


&lt;span class="n"&gt;raw_db_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Convert postgres:// into a format SQLAlchemy can interpret.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Use local SQLite when DATABASE_URL is not set.
&lt;/span&gt;    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;local.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration has two paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Path
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;DATABASE_URL&lt;/code&gt; exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL
→ Convert postgres:// if necessary
→ Connect to PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local Fallback Path
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;DATABASE_URL&lt;/code&gt; does not exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No DATABASE_URL
→ Use local.db
→ Connect to SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I updated the code, committed it, and pushed it to GitHub.&lt;/p&gt;

&lt;p&gt;However, production still did not behave as expected.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Run Migrations Automatically During Container Startup
&lt;/h3&gt;

&lt;p&gt;I wanted the production database schema to be upgraded automatically whenever a new version was deployed.&lt;/p&gt;

&lt;p&gt;I therefore changed the Docker startup command to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before starting the Flask application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; flask db upgrade &amp;amp;&amp;amp; python app.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended startup flow became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start the container
        ↓
Apply all pending database migrations
        ↓
Start the Flask application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduced the risk of deploying application code that expected a newer schema while the database was still on an older revision.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: The Invisible Problem—Production Still Reported SQLite
&lt;/h3&gt;

&lt;p&gt;After completing both changes, I deployed the application with confidence.&lt;/p&gt;

&lt;p&gt;However, the production logs showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application was still using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQLiteImpl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even though I had added the PostgreSQL URL-conversion logic.&lt;/p&gt;

&lt;p&gt;I first suspected that the code itself was wrong.&lt;/p&gt;

&lt;p&gt;To confirm the actual contents of the file, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected conversion logic was present.&lt;/p&gt;

&lt;p&gt;I then suspected that I might have forgotten to commit the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Fix: Replace postgres:// with postgresql:// for Render PostgreSQL connection"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nothing to commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;confirmed that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code had already been committed&lt;/li&gt;
&lt;li&gt;The commit had already been pushed&lt;/li&gt;
&lt;li&gt;The working tree matched the remote repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem was not an uncommitted code change.&lt;/p&gt;

&lt;p&gt;At that point, I changed my perspective.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the connection code wrong?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the production PostgreSQL database actually exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question revealed the real cause.&lt;/p&gt;

&lt;p&gt;When I checked the Render dashboard, I discovered that I had never created the PostgreSQL database itself.&lt;/p&gt;

&lt;p&gt;I had prepared the connection code before preparing the destination.&lt;/p&gt;

&lt;p&gt;The application had no production &lt;code&gt;DATABASE_URL&lt;/code&gt;, so the fallback logic worked exactly as written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No DATABASE_URL
        ↓
Use SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQLite log was not evidence that the new code had failed.&lt;/p&gt;

&lt;p&gt;It was evidence that the fallback path was functioning.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Create the PostgreSQL Database on Render
&lt;/h3&gt;

&lt;p&gt;I created a new PostgreSQL database from the Render dashboard using the free plan.&lt;/p&gt;

&lt;p&gt;I waited until its status changed to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render then provided an:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Database URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copied that value into the web service's environment variables as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saving the environment variable triggered a new deployment automatically.&lt;/p&gt;

&lt;p&gt;This time, the logs changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
==&amp;gt; Your service is live 🎉
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgresqlImpl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application received &lt;code&gt;DATABASE_URL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The connection URL was accepted&lt;/li&gt;
&lt;li&gt;Alembic connected to PostgreSQL&lt;/li&gt;
&lt;li&gt;The migration process ran against PostgreSQL&lt;/li&gt;
&lt;li&gt;The web service started successfully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application was no longer using the SQLite fallback in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;

&lt;p&gt;The problem looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I added PostgreSQL connection code
        ↓
Production still used SQLite
        ↓
I suspected the code and Git history
        ↓
Both were correct
        ↓
The PostgreSQL database had never been created
        ↓
DATABASE_URL did not exist
        ↓
The application correctly fell back to SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code was not ignoring PostgreSQL.&lt;/p&gt;

&lt;p&gt;There was simply no PostgreSQL environment available for it to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the SQLite Fallback Made the Problem Harder to Notice
&lt;/h2&gt;

&lt;p&gt;The fallback was designed to make local development convenient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;local.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When no &lt;code&gt;DATABASE_URL&lt;/code&gt; was available, the application still started successfully.&lt;/p&gt;

&lt;p&gt;That was useful locally.&lt;/p&gt;

&lt;p&gt;However, in production, it also meant that a missing database configuration did not cause startup to fail.&lt;/p&gt;

&lt;p&gt;Instead, the application quietly used SQLite.&lt;/p&gt;

&lt;p&gt;This creates a trade-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantage
&lt;/h3&gt;

&lt;p&gt;Local development remains easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No PostgreSQL required
→ Application starts with SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Risk
&lt;/h3&gt;

&lt;p&gt;A missing production environment variable may remain hidden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL missing in production
→ Application starts anyway
→ SQLite is used unexpectedly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stricter production configuration could fail immediately instead of falling back silently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;


&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APP_ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;raw_db_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DATABASE_URL must be configured in production.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_db_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;local.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would make the intended behavior explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development
→ SQLite fallback is allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production
→ Missing DATABASE_URL stops startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That follows the Fail-Fast principle more closely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automatically Applying Migrations During Startup
&lt;/h2&gt;

&lt;p&gt;The Docker command used was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; flask db upgrade &amp;amp;&amp;amp; python app.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the application starts only after the migration command succeeds.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run the second command only if the first command succeeds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask db upgrade succeeds
→ Start app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask db upgrade fails
→ Do not start app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a basic deployment safety mechanism.&lt;/p&gt;

&lt;p&gt;The application is not started against a database schema that failed to upgrade.&lt;/p&gt;

&lt;p&gt;However, running migrations during application startup also requires care.&lt;/p&gt;

&lt;p&gt;Possible concerns include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple application instances attempting the same migration&lt;/li&gt;
&lt;li&gt;Long-running migrations delaying startup&lt;/li&gt;
&lt;li&gt;Schema changes that lock production tables&lt;/li&gt;
&lt;li&gt;Failed migrations preventing the service from becoming available&lt;/li&gt;
&lt;li&gt;Destructive migrations affecting existing data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small application running as a single service, this approach may be practical.&lt;/p&gt;

&lt;p&gt;For a larger system, a separate migration job or release command may be safer.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQLite and PostgreSQL Behave Differently
&lt;/h2&gt;

&lt;p&gt;The log output also showed an important database difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQLite
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Will assume non-transactional DDL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Will assume transactional DDL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DDL refers to schema-changing operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating tables&lt;/li&gt;
&lt;li&gt;Altering tables&lt;/li&gt;
&lt;li&gt;Adding columns&lt;/li&gt;
&lt;li&gt;Creating indexes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL can handle many schema operations inside transactions.&lt;/p&gt;

&lt;p&gt;That can make it possible to roll back certain failed migration operations.&lt;/p&gt;

&lt;p&gt;SQLite has different limitations and behavior.&lt;/p&gt;

&lt;p&gt;This is one reason why a migration tested only against SQLite should not automatically be considered proven for PostgreSQL.&lt;/p&gt;

&lt;p&gt;The production database engine must also be tested.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Verified the Migration
&lt;/h2&gt;

&lt;p&gt;The production log gave several useful pieces of evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context impl PostgresqlImpl.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proved that Alembic was connected using the PostgreSQL implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Will assume transactional DDL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This showed that Alembic recognized the database as PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your service is live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that the startup process completed after the migration command.&lt;/p&gt;

&lt;p&gt;However, a complete verification should also include checks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirming that the expected tables exist&lt;/li&gt;
&lt;li&gt;Confirming that the current Alembic revision is recorded&lt;/li&gt;
&lt;li&gt;Confirming that existing application operations work&lt;/li&gt;
&lt;li&gt;Creating and reading actual records&lt;/li&gt;
&lt;li&gt;Confirming that records remain after redeployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful commands include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db &lt;span class="nb"&gt;history&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PostgreSQL database also contains an Alembic version table used to track the applied revision.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three-Part Environment Modernization Process
&lt;/h2&gt;

&lt;p&gt;This series covered three related improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Synchronizing Dependencies
&lt;/h3&gt;

&lt;p&gt;I discovered a mismatch between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai 2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requirements.txt: google-genai 2.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I aligned the versions and verified the application behavior with pytest and manual testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: Introducing Schema History Management
&lt;/h3&gt;

&lt;p&gt;I removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask-Migrate&lt;/li&gt;
&lt;li&gt;Alembic&lt;/li&gt;
&lt;li&gt;Migration history managed through Git&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 3: Moving Production to PostgreSQL
&lt;/h3&gt;

&lt;p&gt;I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a PostgreSQL database on Render&lt;/li&gt;
&lt;li&gt;Configured &lt;code&gt;DATABASE_URL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Converted &lt;code&gt;postgres://&lt;/code&gt; when necessary&lt;/li&gt;
&lt;li&gt;Ran migrations during startup&lt;/li&gt;
&lt;li&gt;Confirmed &lt;code&gt;PostgresqlImpl&lt;/code&gt; in the deployment logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these changes created a more reproducible and production-oriented foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Through this infrastructure migration, I established the following improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Environment Synchronization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Corrected dependency-version mismatches&lt;/li&gt;
&lt;li&gt;Updated &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verified behavior with pytest&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Modernized Database Operations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Removed &lt;code&gt;db.create_all()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Introduced Flask-Migrate&lt;/li&gt;
&lt;li&gt;Added migration history management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. More Reliable Production Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Migrated from SQLite to PostgreSQL&lt;/li&gt;
&lt;li&gt;Added dynamic URL conversion&lt;/li&gt;
&lt;li&gt;Configured automatic migration execution&lt;/li&gt;
&lt;li&gt;Confirmed the database implementation through logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important lesson was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Correct code and existing infrastructure are separate requirements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The connection logic can be perfect.&lt;/p&gt;

&lt;p&gt;However, if the database has not been created and the environment variable has not been configured, there is nothing to connect to.&lt;/p&gt;

&lt;p&gt;The application code and production infrastructure must fit together.&lt;/p&gt;

&lt;p&gt;Only then can the system provide persistent data and predictable deployment behavior.&lt;/p&gt;

&lt;p&gt;This work moved the project closer to a practical development and operations cycle based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declared dependencies&lt;/li&gt;
&lt;li&gt;Version-controlled schema changes&lt;/li&gt;
&lt;li&gt;Production database persistence&lt;/li&gt;
&lt;li&gt;Automated deployment&lt;/li&gt;
&lt;li&gt;Log-based verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This is Part 3 of a three-part series.&lt;/p&gt;

&lt;p&gt;Part 1 covered dependency synchronization and pytest verification.&lt;/p&gt;

&lt;p&gt;Part 2 covered moving from &lt;code&gt;db.create_all()&lt;/code&gt; to Flask-Migrate.&lt;/p&gt;

&lt;p&gt;Part 3 covered the production migration from SQLite to Render PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🍞 &lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;sales_data_app on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>postgres</category>
      <category>render</category>
    </item>
    <item>
      <title>🧪 Moving Beyond `db.create_all()`: Managing Database Schema History with Flask-Migrate [2/3]</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 00:22:18 +0000</pubDate>
      <link>https://dev.to/tosane932/moving-beyond-dbcreateall-managing-database-schema-history-with-flask-migrate-23-4n3l</link>
      <guid>https://dev.to/tosane932/moving-beyond-dbcreateall-managing-database-schema-history-with-flask-migrate-23-4n3l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;Updated on July 14, 2026: Revised Section 2, “Implementation Steps and Observed Results.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I am &lt;code&gt;tosane932&lt;/code&gt;, a professional truck driver working in logistics while teaching myself Python.&lt;/p&gt;

&lt;p&gt;In the previous article—the first delivery in this three-part series—I fixed a &lt;code&gt;google-genai&lt;/code&gt; version mismatch and verified the existing behavior with &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This time, I moved on to the main task:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modernizing the way my application manages its database schema.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Until now, I had relied on the convenient command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I decided to remove that approach and switch to &lt;code&gt;Flask-Migrate&lt;/code&gt;, which uses Alembic underneath.&lt;/p&gt;

&lt;p&gt;This article records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why I stopped relying on &lt;code&gt;db.create_all()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How I introduced Flask-Migrate&lt;/li&gt;
&lt;li&gt;How I initialized the migration environment&lt;/li&gt;
&lt;li&gt;What happened when Alembic compared my models with the existing database&lt;/li&gt;
&lt;li&gt;How I removed the old automatic table-creation logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete project is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Previously, my Flask application created database tables using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I replaced that approach with schema migration management using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask-Migrate&lt;/li&gt;
&lt;li&gt;Alembic&lt;/li&gt;
&lt;li&gt;SQLAlchemy&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was to move from simple table creation toward a system that records and applies database schema changes over time.&lt;/p&gt;

&lt;p&gt;This article covers the transition through the initial migration setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why I Introduced Flask-Migrate
&lt;/h2&gt;

&lt;p&gt;Until this point, the application executed &lt;code&gt;db.create_all()&lt;/code&gt; during startup.&lt;/p&gt;

&lt;p&gt;Based on the SQLAlchemy models defined in the project, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Product&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DailySales&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the command created any missing tables automatically.&lt;/p&gt;

&lt;p&gt;This was convenient during the early stages of development.&lt;/p&gt;

&lt;p&gt;However, it had several limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  It Does Not Manage Changes to Existing Tables
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;db.create_all()&lt;/code&gt; can create a table that does not yet exist.&lt;/p&gt;

&lt;p&gt;However, it does not manage later schema changes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a column&lt;/li&gt;
&lt;li&gt;Removing a column&lt;/li&gt;
&lt;li&gt;Renaming a column&lt;/li&gt;
&lt;li&gt;Changing a data type&lt;/li&gt;
&lt;li&gt;Adding an index&lt;/li&gt;
&lt;li&gt;Changing a constraint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, adding a new field to a SQLAlchemy model does not automatically update an existing production table in a controlled way.&lt;/p&gt;

&lt;h3&gt;
  
  
  It Does Not Record Schema History
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;db.create_all()&lt;/code&gt;, there is no migration history showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What changed&lt;/li&gt;
&lt;li&gt;When it changed&lt;/li&gt;
&lt;li&gt;Why it changed&lt;/li&gt;
&lt;li&gt;Which revision is currently applied&lt;/li&gt;
&lt;li&gt;How to reproduce the same change in another environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  It Can Create Differences Between Environments
&lt;/h3&gt;

&lt;p&gt;If database changes are handled manually, the following environments may gradually become inconsistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local development&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;CI&lt;/li&gt;
&lt;li&gt;Render PostgreSQL&lt;/li&gt;
&lt;li&gt;Another developer's environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model definitions in the source code may represent one schema while the actual database contains another.&lt;/p&gt;

&lt;p&gt;To reduce that risk, I introduced Flask-Migrate so database changes could be recorded as source-controlled migration files.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Implementation Steps and Observed Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Install and Configure Flask-Migrate
&lt;/h3&gt;

&lt;p&gt;I installed Flask-Migrate and added it to &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I then integrated it into &lt;code&gt;app.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask_migrate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Migrate&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DailySales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SQLALCHEMY_TRACK_MODIFICATIONS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SQLALCHEMY_TRACK_MODIFICATIONS&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important addition was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This connects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Flask application&lt;/li&gt;
&lt;li&gt;The SQLAlchemy database object&lt;/li&gt;
&lt;li&gt;Flask-Migrate&lt;/li&gt;
&lt;li&gt;Alembic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this configuration, Flask's CLI can access migration commands such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db init
flask db migrate
flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: Initialize the Migration Environment
&lt;/h3&gt;

&lt;p&gt;From the project root in my local development environment, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated the migration environment and configuration files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actual Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creating directory /home/tosane/sales_data_app/migrations ...  done
Creating directory /home/tosane/sales_data_app/migrations/versions ...  done
Generating /home/tosane/sales_data_app/migrations/env.py ...  done
Generating /home/tosane/sales_data_app/migrations/README ...  done
Generating /home/tosane/sales_data_app/migrations/alembic.ini ...  done
Generating /home/tosane/sales_data_app/migrations/script.py.mako ...  done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following structure was created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;migrations/
├── README
├── alembic.ini
├── env.py
├── script.py.mako
└── versions/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In logistics terms, this directory became the database's maintenance record system.&lt;/p&gt;

&lt;p&gt;It provides a place to store future schema-change instructions.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Compare the Models with the Existing Database
&lt;/h3&gt;

&lt;p&gt;Next, I attempted to generate the initial migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db migrate &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial migration"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alembic compared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The current SQLAlchemy model definitions in &lt;code&gt;models.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The existing tables in the local SQLite database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Actual Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.plugins] setting up autogenerate plugin...
INFO  [alembic.env] No changes in schema detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key message was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No changes in schema detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This meant that Alembic did not find a schema difference between the current SQLAlchemy models and the existing local database.&lt;/p&gt;

&lt;p&gt;In other words, the model definitions and the database structure were already aligned at that moment.&lt;/p&gt;

&lt;p&gt;Because no difference was detected, there was no schema change for Alembic to record in a new migration revision.&lt;/p&gt;

&lt;p&gt;This was the expected result for the existing local environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;code&gt;No Changes in Schema Detected&lt;/code&gt; Actually Means
&lt;/h2&gt;

&lt;p&gt;This message does not mean that Flask-Migrate failed.&lt;/p&gt;

&lt;p&gt;It means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model metadata
=
Current database schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at the time of comparison.&lt;/p&gt;

&lt;p&gt;Alembic's autogeneration process looks for differences such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A model column that does not exist in the database&lt;/li&gt;
&lt;li&gt;A database table that no longer exists in the models&lt;/li&gt;
&lt;li&gt;A changed nullable setting&lt;/li&gt;
&lt;li&gt;A new index&lt;/li&gt;
&lt;li&gt;A changed data type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If no relevant difference exists, there is nothing to generate.&lt;/p&gt;

&lt;p&gt;The migration environment was initialized successfully, but the current database did not require a schema-changing migration.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Remove the Old &lt;code&gt;db.create_all()&lt;/code&gt; Logic
&lt;/h3&gt;

&lt;p&gt;After introducing Flask-Migrate, I removed the automatic table-creation code from &lt;code&gt;app.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The deleted code was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database tables initialized successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to initialize database tables: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exc&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;Previously, this block attempted to create missing tables every time the application started.&lt;/p&gt;

&lt;p&gt;After the migration system was introduced, schema changes were no longer supposed to happen implicitly during application startup.&lt;/p&gt;

&lt;p&gt;Instead, they would be handled explicitly through migration commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db migrate
flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a clearer separation of responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application startup
→ Start and serve the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Migration operation
→ Change the database schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation makes database changes more visible and intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Removed &lt;code&gt;db.create_all()&lt;/code&gt; Instead of Keeping Both
&lt;/h2&gt;

&lt;p&gt;It would have been possible to keep &lt;code&gt;db.create_all()&lt;/code&gt; while also introducing Flask-Migrate.&lt;/p&gt;

&lt;p&gt;However, that would create two separate systems attempting to manage the database schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.create_all()
+
Flask-Migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That could make it difficult to determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which process created a table&lt;/li&gt;
&lt;li&gt;Whether a migration had actually been applied&lt;/li&gt;
&lt;li&gt;Why one environment differed from another&lt;/li&gt;
&lt;li&gt;Whether startup behavior was hiding a missing migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted one clear source of truth for schema changes.&lt;/p&gt;

&lt;p&gt;For that reason, I removed the old startup-based creation logic after introducing the migration framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Database Management Flow
&lt;/h2&gt;

&lt;p&gt;Before the change, the process was roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start the Flask application
        ↓
Run db.create_all()
        ↓
Create missing tables
        ↓
Continue startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the change, the intended process became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change a SQLAlchemy model
        ↓
Generate a migration revision
        ↓
Review the generated migration file
        ↓
Commit it to Git
        ↓
Apply the migration
        ↓
Start the application using the updated schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical commands will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db migrate &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"describe the schema change"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated migration files can then be reviewed and stored in Git together with the application code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Migration Files Must Be Reviewed
&lt;/h2&gt;

&lt;p&gt;Flask-Migrate and Alembic can detect many schema differences automatically.&lt;/p&gt;

&lt;p&gt;However, automatically generated migration files should not be treated as unquestionable output.&lt;/p&gt;

&lt;p&gt;Before applying a migration, I should confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which tables will be changed&lt;/li&gt;
&lt;li&gt;Which columns will be added or removed&lt;/li&gt;
&lt;li&gt;Whether any data could be lost&lt;/li&gt;
&lt;li&gt;Whether SQLite and PostgreSQL behave differently&lt;/li&gt;
&lt;li&gt;Whether the downgrade operation is valid&lt;/li&gt;
&lt;li&gt;Whether existing production data needs transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask db migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;generates a proposal based on detected differences.&lt;/p&gt;

&lt;p&gt;It does not replace human review.&lt;/p&gt;

&lt;p&gt;That is similar to a pre-departure inspection sheet.&lt;/p&gt;

&lt;p&gt;The existence of the document is useful, but someone still needs to inspect what it says before departure.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQLite and PostgreSQL Require Extra Attention
&lt;/h2&gt;

&lt;p&gt;My local environment and production environment do not necessarily use the same database engine.&lt;/p&gt;

&lt;p&gt;The project has used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLite locally&lt;/li&gt;
&lt;li&gt;PostgreSQL in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A migration that behaves correctly in SQLite may not behave exactly the same way in PostgreSQL.&lt;/p&gt;

&lt;p&gt;Possible differences include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supported data types&lt;/li&gt;
&lt;li&gt;Constraint behavior&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ALTER TABLE&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;Default values&lt;/li&gt;
&lt;li&gt;Index behavior&lt;/li&gt;
&lt;li&gt;Transaction handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that reason, the migration process must eventually be verified against the production-style PostgreSQL environment as well.&lt;/p&gt;

&lt;p&gt;The local migration setup is only the foundation.&lt;/p&gt;

&lt;p&gt;It does not automatically guarantee that every future migration will be safe in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Result
&lt;/h2&gt;

&lt;p&gt;After this work, the application reached the following state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flask-Migrate was installed&lt;/li&gt;
&lt;li&gt;The dependency was added to &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Flask-Migrate was connected to the Flask application&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;migrations&lt;/code&gt; directory was initialized&lt;/li&gt;
&lt;li&gt;Alembic compared the current models with the existing SQLite schema&lt;/li&gt;
&lt;li&gt;No schema differences were detected&lt;/li&gt;
&lt;li&gt;The old &lt;code&gt;db.create_all()&lt;/code&gt; startup logic was removed&lt;/li&gt;
&lt;li&gt;Future schema changes can now be represented as migration files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database-management process is now more explicit and easier to reproduce.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This work replaced startup-time table creation with a migration-based database management foundation.&lt;/p&gt;

&lt;p&gt;The main lessons were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;db.create_all()&lt;/code&gt; is useful for creating missing tables, but it does not manage schema history&lt;/li&gt;
&lt;li&gt;Existing table changes require a migration system&lt;/li&gt;
&lt;li&gt;Flask-Migrate connects Flask and SQLAlchemy to Alembic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flask db init&lt;/code&gt; creates the migration environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flask db migrate&lt;/code&gt; compares model metadata with the current database schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;No changes in schema detected&lt;/code&gt; means no relevant schema difference was found&lt;/li&gt;
&lt;li&gt;Migration generation and migration application are separate operations&lt;/li&gt;
&lt;li&gt;Automatically generated migration files still require human review&lt;/li&gt;
&lt;li&gt;Running both &lt;code&gt;db.create_all()&lt;/code&gt; and migrations can create unclear ownership of the schema&lt;/li&gt;
&lt;li&gt;Local SQLite verification does not automatically prove PostgreSQL compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By removing &lt;code&gt;db.create_all()&lt;/code&gt; and introducing Flask-Migrate, I established the foundation for managing database changes through Git.&lt;/p&gt;

&lt;p&gt;The next step is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Applying migrations safely in the Render PostgreSQL production environment.&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This is Part 2 of a three-part series.&lt;/p&gt;

&lt;p&gt;Part 1 covered the &lt;code&gt;google-genai&lt;/code&gt; version mismatch and pytest verification.&lt;/p&gt;

&lt;p&gt;Part 3 will cover production deployment and automatic migration execution on Render.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🍞 &lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;sales_data_app on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>sqlalchemy</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🐍 Fixing a `google-genai` Version Mismatch and Verifying the Behavior with pytest [1/3]</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Sun, 02 Aug 2026 00:00:57 +0000</pubDate>
      <link>https://dev.to/tosane932/fixing-a-google-genai-version-mismatch-and-verifying-the-behavior-with-pytest-13-3okn</link>
      <guid>https://dev.to/tosane932/fixing-a-google-genai-version-mismatch-and-verifying-the-behavior-with-pytest-13-3okn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I am &lt;code&gt;tosane932&lt;/code&gt;, a professional truck driver working in logistics while teaching myself Python.&lt;/p&gt;

&lt;p&gt;In my previous article, I tested a Docker multi-stage build and measured the actual change in image size.&lt;/p&gt;

&lt;p&gt;At the end of that article, I said that I would write next about &lt;code&gt;pytest&lt;/code&gt; and CI/CD.&lt;/p&gt;

&lt;p&gt;This article was supposed to be the practical follow-up.&lt;/p&gt;

&lt;p&gt;However, while preparing for that work, I encountered an unexpected side issue.&lt;/p&gt;

&lt;p&gt;I only intended to introduce Flask-Migrate.&lt;/p&gt;

&lt;p&gt;Instead, the pip installation logs revealed that the version of a library in my local development environment had been changed without me noticing.&lt;/p&gt;

&lt;p&gt;The library was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, I went through the following process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the version mismatch&lt;/li&gt;
&lt;li&gt;Restore the version that had already been tested locally&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Manually verify the Gemini API functionality&lt;/li&gt;
&lt;li&gt;Run pytest to check for regressions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article records that process without hiding the inconvenient parts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;While installing Flask-Migrate, I noticed a mismatch between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The version of &lt;code&gt;google-genai&lt;/code&gt; installed in my local development environment&lt;/li&gt;
&lt;li&gt;The version declared in &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The local environment had been using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai 2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;code&gt;requirements.txt&lt;/code&gt; still specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai==2.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pip followed the configuration file and replaced the newer local version with the older declared version.&lt;/p&gt;

&lt;p&gt;This article explains how I discovered the issue, synchronized the environments, and verified the application behavior with automated tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Problem and Its Background
&lt;/h2&gt;

&lt;p&gt;I was preparing to introduce Flask-Migrate.&lt;/p&gt;

&lt;p&gt;During that work, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installation log contained the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attempting uninstall: google-genai
    Found existing installation: google-genai 2.10.0
    Uninstalling google-genai-2.10.0:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That message caught my attention.&lt;/p&gt;

&lt;p&gt;After checking the environment, I discovered that I had been developing and testing the application locally with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai 2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;code&gt;requirements.txt&lt;/code&gt; still contained the older version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai==2.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because pip treats &lt;code&gt;requirements.txt&lt;/code&gt; as the declared source of dependency versions, it removed version &lt;code&gt;2.10.0&lt;/code&gt; and installed version &lt;code&gt;2.4.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In other words, my local environment had been unintentionally downgraded.&lt;/p&gt;

&lt;p&gt;The application had appeared stable before the installation command, but the dependency configuration and the actual environment were not synchronized.&lt;/p&gt;

&lt;p&gt;This created several possible risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A method available in &lt;code&gt;2.10.0&lt;/code&gt; might not exist in &lt;code&gt;2.4.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;API behavior may differ between versions&lt;/li&gt;
&lt;li&gt;A production or CI environment could reproduce the older version&lt;/li&gt;
&lt;li&gt;The local environment might behave differently from deployment&lt;/li&gt;
&lt;li&gt;A future installation could silently change the application's behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I therefore decided that I needed to do more than simply reinstall the newer package.&lt;/p&gt;

&lt;p&gt;I needed to make the declared configuration and the real environment match exactly.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Investigation and Recovery Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Confirm the Installed Version
&lt;/h3&gt;

&lt;p&gt;First, I checked the currently installed package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip show google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command displays information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package name&lt;/li&gt;
&lt;li&gt;Installed version&lt;/li&gt;
&lt;li&gt;Installation location&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The log had already shown that version &lt;code&gt;2.10.0&lt;/code&gt; existed before the downgrade, but I wanted to confirm the current state directly.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Restore the Previously Tested Version
&lt;/h3&gt;

&lt;p&gt;I treated version &lt;code&gt;2.10.0&lt;/code&gt; as the correct version because it was the version I had already used during local development and manual testing.&lt;/p&gt;

&lt;p&gt;I reinstalled it explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;google-genai&lt;span class="o"&gt;==&lt;/span&gt;2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifying the version made the intended state clear.&lt;/p&gt;

&lt;p&gt;Instead of allowing pip to choose a version implicitly, I restored the exact version that had already been tested.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Update &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Restoring the local package was not enough.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;requirements.txt&lt;/code&gt; still specified &lt;code&gt;2.4.0&lt;/code&gt;, the same downgrade would happen again in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clean virtual environment&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Docker builds&lt;/li&gt;
&lt;li&gt;Render deployments&lt;/li&gt;
&lt;li&gt;Another developer's machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I therefore updated the dependency declaration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai==2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point was to align these two states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actual local environment
=
Declared dependency configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that alignment, the application could behave differently depending on where it was installed.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Manual Application Verification
&lt;/h2&gt;

&lt;p&gt;After restoring the package version, I manually tested the application features that use the Gemini API.&lt;/p&gt;

&lt;p&gt;I checked the AI-generated advice on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The daily sales input page&lt;/li&gt;
&lt;li&gt;The dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gemini-2.5-flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I confirmed that the AI advice was generated without missing content or unexpected errors.&lt;/p&gt;

&lt;p&gt;This manual check was important because dependency installation can succeed even when application-level behavior has changed.&lt;/p&gt;

&lt;p&gt;The following command only proves that the package can be installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;google-genai&lt;span class="o"&gt;==&lt;/span&gt;2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not prove that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API client is initialized correctly&lt;/li&gt;
&lt;li&gt;The model request works&lt;/li&gt;
&lt;li&gt;The response format is compatible&lt;/li&gt;
&lt;li&gt;The application still handles the result correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that reason, I verified the actual user-facing functionality through the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Verifying the Logic with pytest
&lt;/h2&gt;

&lt;p&gt;In addition to manual testing, I ran the prompt-related automated tests that I had already created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest test_prompts.py &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose was to check whether restoring the library version had caused any regression in the existing application logic.&lt;/p&gt;

&lt;p&gt;The test suite covered three behaviors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sales data is included in the generated prompt&lt;/li&gt;
&lt;li&gt;The required instructions are included&lt;/li&gt;
&lt;li&gt;The function returns a string&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three tests completed successfully.&lt;/p&gt;

&lt;p&gt;This provided an additional level of confirmation that the prompt-generation logic remained intact after the dependency correction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Test Result Proved—and What It Did Not
&lt;/h2&gt;

&lt;p&gt;The passing tests confirmed that the behaviors defined in &lt;code&gt;test_prompts.py&lt;/code&gt; still worked.&lt;/p&gt;

&lt;p&gt;However, they did not directly prove that the Gemini API itself was functioning.&lt;/p&gt;

&lt;p&gt;The prompt tests check local logic such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They do not necessarily make a real external API request.&lt;/p&gt;

&lt;p&gt;That is why I used both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual browser verification for the Gemini API integration&lt;/li&gt;
&lt;li&gt;pytest for the local prompt-building logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two checks covered different parts of the system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Manual verification
→ External API integration and visible application behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pytest
→ Local logic and expected output structure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A passing unit test should not be treated as proof of every part of the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the pip Log Was Important
&lt;/h2&gt;

&lt;p&gt;The issue was not discovered through an application crash.&lt;/p&gt;

&lt;p&gt;It was discovered through the installation log.&lt;/p&gt;

&lt;p&gt;The key message was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attempting uninstall: google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I had ignored the output and only checked whether the installation command completed successfully, I might not have noticed that the package had been downgraded.&lt;/p&gt;

&lt;p&gt;The installation itself was successful.&lt;/p&gt;

&lt;p&gt;The environment change was still unintended.&lt;/p&gt;

&lt;p&gt;This taught me that a successful command can still contain important warnings or state changes.&lt;/p&gt;

&lt;p&gt;The final line of a command is not the only useful information.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local Environments Can Hide Configuration Problems
&lt;/h2&gt;

&lt;p&gt;My local environment had accumulated packages and versions from earlier development work.&lt;/p&gt;

&lt;p&gt;As a result, it had reached a state that was not fully represented by &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The mismatch looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local environment:
google-genai 2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requirements.txt:
google-genai 2.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application could appear to work locally because the newer package was already installed.&lt;/p&gt;

&lt;p&gt;However, a clean environment would follow the dependency file and install the older version.&lt;/p&gt;

&lt;p&gt;This is the same type of problem behind the phrase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It worked on my machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem is not always the application code.&lt;/p&gt;

&lt;p&gt;Sometimes the undeclared environment itself is the hidden dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Exact Version Pinning Helped
&lt;/h2&gt;

&lt;p&gt;By writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai==2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made the intended version explicit.&lt;/p&gt;

&lt;p&gt;This improves reproducibility because pip is instructed to install the same version in every environment.&lt;/p&gt;

&lt;p&gt;However, exact pinning also creates a maintenance responsibility.&lt;/p&gt;

&lt;p&gt;The version will not automatically move to a newer release.&lt;/p&gt;

&lt;p&gt;That means I must periodically review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security updates&lt;/li&gt;
&lt;li&gt;Deprecation notices&lt;/li&gt;
&lt;li&gt;API changes&lt;/li&gt;
&lt;li&gt;Compatibility with the selected Gemini model&lt;/li&gt;
&lt;li&gt;Changes to transitive dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exact pinning reduces accidental changes.&lt;/p&gt;

&lt;p&gt;It does not remove the need for future updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Safer Verification Flow
&lt;/h2&gt;

&lt;p&gt;After this incident, I understood the value of the following sequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inspect the Current Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip show google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Compare It with the Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"google-genai"&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Install the Intended Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;google-genai&lt;span class="o"&gt;==&lt;/span&gt;2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Update the Dependency File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-genai==2.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Verify the Installed State Again
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip show google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Run Automated Tests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest test_prompts.py &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Test the Actual API-Related Feature
&lt;/h3&gt;

&lt;p&gt;Open the application and confirm that the Gemini-generated advice still works.&lt;/p&gt;

&lt;p&gt;This sequence checks both configuration and behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;After completing the work, the following states were established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pip installation log revealed the dependency mismatch&lt;/li&gt;
&lt;li&gt;The local environment was restored to &lt;code&gt;google-genai 2.10.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requirements.txt&lt;/code&gt; was updated to the same version&lt;/li&gt;
&lt;li&gt;The Gemini API feature worked during manual testing&lt;/li&gt;
&lt;li&gt;All three prompt-related pytest cases passed&lt;/li&gt;
&lt;li&gt;The local environment and dependency configuration were synchronized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provided a safer foundation for the next step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introducing Flask-Migrate and managing database schema history.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The main lessons from this work were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installation logs can reveal unintended package changes&lt;/li&gt;
&lt;li&gt;A successful pip command does not mean the environment changed exactly as intended&lt;/li&gt;
&lt;li&gt;Local packages and &lt;code&gt;requirements.txt&lt;/code&gt; can drift apart&lt;/li&gt;
&lt;li&gt;A clean CI or production environment follows the declared dependency configuration&lt;/li&gt;
&lt;li&gt;Restoring a package locally is not enough; the configuration file must also be updated&lt;/li&gt;
&lt;li&gt;Manual API verification and unit tests protect different parts of the application&lt;/li&gt;
&lt;li&gt;Exact version pinning improves reproducibility but requires maintenance&lt;/li&gt;
&lt;li&gt;Environment synchronization should be confirmed before introducing another major dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I originally intended only to prepare for Flask-Migrate.&lt;/p&gt;

&lt;p&gt;Instead, I discovered and corrected a hidden dependency mismatch.&lt;/p&gt;

&lt;p&gt;This was an unexpected detour, but it prevented the next stage of development from being built on an inconsistent environment.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This is Part 1 of a three-part series.&lt;/p&gt;

&lt;p&gt;The next article will cover the introduction of Flask-Migrate and database migration management.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🍞 &lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;sales_data_app on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>pytest</category>
      <category>gemini</category>
    </item>
    <item>
      <title>✅️ A Truck Driver Built a “Pre-Departure Inspection Gate” — Then Found a Hole in His Tests</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Thu, 30 Jul 2026 19:32:03 +0000</pubDate>
      <link>https://dev.to/tosane932/a-truck-driver-built-a-pre-departure-inspection-gate-then-found-a-hole-in-his-tests-585a</link>
      <guid>https://dev.to/tosane932/a-truck-driver-built-a-pre-departure-inspection-gate-then-found-a-hole-in-his-tests-585a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🍞 What Went Wrong When I Introduced pytest and GitHub Actions into &lt;code&gt;sales_data_app&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I am &lt;code&gt;tosane932&lt;/code&gt;, a professional truck driver working in logistics while teaching myself Python.&lt;/p&gt;

&lt;p&gt;In my previous article, I tested a Docker multi-stage build and measured the actual image-size difference.&lt;/p&gt;

&lt;p&gt;At the end of that article, I said that my next topic would be &lt;code&gt;pytest&lt;/code&gt; and CI/CD.&lt;/p&gt;

&lt;p&gt;This is the practical follow-up.&lt;/p&gt;

&lt;p&gt;I will share the conclusion first:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wrote some tests, felt satisfied, and then discovered that they were allowing an important inconsistency to pass unnoticed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article records what happened and what I learned from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Current Deployment Flow—and Its Risk
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sales_data_app&lt;/code&gt; is connected to Render.&lt;/p&gt;

&lt;p&gt;Whenever I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render automatically builds and deploys the latest version.&lt;/p&gt;

&lt;p&gt;This is convenient.&lt;/p&gt;

&lt;p&gt;However, without an automated test stage, buggy code can also travel directly into production.&lt;/p&gt;

&lt;p&gt;In logistics terms, it was like sending a truck onto the road without performing a pre-departure inspection.&lt;/p&gt;

&lt;p&gt;I decided to add an inspection gate using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was to test the code automatically before treating a push as safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing My First pytest Tests
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sales_data_app&lt;/code&gt; contains a function called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;build_sales_prompt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function creates the prompt sent to the Gemini API.&lt;/p&gt;

&lt;p&gt;I started with three simple tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_prompts.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_sales_prompt&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_includes_sales_summary&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;sample_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monday: 50 melon buns, 30 red bean buns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;sample_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_includes_key_instructions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3 suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bullet points&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_returns_string&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ran the tests, all three passed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_prompts.py::test_build_sales_prompt_includes_sales_summary PASSED
test_prompts.py::test_build_sales_prompt_includes_key_instructions PASSED
test_prompts.py::test_build_sales_prompt_returns_string PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, the function appeared to be protected.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Deliberately Broke the Code—and Found a Hole in the Test
&lt;/h2&gt;

&lt;p&gt;The prompt-generation function contained two instructions using the same number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prompts.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are a management consultant with expertise in food-industry
trends and consumer behavior.

Based on the bakery sales data below, provide three specific
suggestions that staff can immediately apply in the workplace.

[Sales Data]
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sales_summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

[Analysis Points]
- Recent food trends and changes in competing businesses
- Needs of different customer groups
- Potential for promotion on social media
- Hidden demand based on weekdays, seasons, and holidays

[Output Format]
Provide three bullet-point suggestions.
Keep each suggestion to one or two sentences.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number &lt;code&gt;three&lt;/code&gt; appeared in two different contexts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“Provide three specific suggestions”&lt;/li&gt;
&lt;li&gt;“Provide three bullet-point suggestions”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted to know whether my test was actually protecting both instructions.&lt;/p&gt;

&lt;p&gt;So I deliberately changed the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Change Only the Lower Instruction
&lt;/h3&gt;

&lt;p&gt;I changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provide three bullet-point suggestions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provide five bullet-point suggestions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upper instruction still requested three suggestions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inconsistency passed through the test.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pattern 2: Change Both Instructions to Five
&lt;/h3&gt;

&lt;p&gt;I changed both instructions from three to five.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test detected the change.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pattern 3: Keep the Upper Instruction at Five and Restore the Lower One to Three
&lt;/h3&gt;

&lt;p&gt;The prompt now contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provide five specific suggestions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provide three bullet-point suggestions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, the inconsistent prompt passed through the test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Original Test Failed to Protect the Prompt
&lt;/h2&gt;

&lt;p&gt;The problem was this assertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3 suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test only asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the relevant text appear somewhere in the complete prompt?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As long as one matching phrase remained, the assertion passed.&lt;/p&gt;

&lt;p&gt;It did not check whether both instructions were correct or whether they contradicted each other.&lt;/p&gt;

&lt;p&gt;The test was verifying the existence of a fragment, not the meaning of the prompt structure.&lt;/p&gt;

&lt;p&gt;This taught me an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Writing a test and protecting the intended behavior are not the same thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A green test result may only mean that the assertion was too weak to detect the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improving the Test
&lt;/h2&gt;

&lt;p&gt;Once I understood the problem, I changed the test to check each instruction in its full context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_includes_key_instructions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provide three specific suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provide three bullet-point suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the test protects both locations independently.&lt;/p&gt;

&lt;p&gt;When I repeated the same deliberate changes, the inconsistent versions failed correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AssertionError:
assert 'provide three bullet-point suggestions'
in '...provide five bullet-point suggestions...'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The failure message also became more useful.&lt;/p&gt;

&lt;p&gt;It clearly showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the test expected&lt;/li&gt;
&lt;li&gt;What the prompt actually contained&lt;/li&gt;
&lt;li&gt;Which instruction had changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test now had a more specific purpose.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Mutation Testing Taught Me
&lt;/h2&gt;

&lt;p&gt;I did not use a formal mutation-testing tool in this experiment.&lt;/p&gt;

&lt;p&gt;However, the basic idea was similar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deliberately change the production code&lt;/li&gt;
&lt;li&gt;Run the tests&lt;/li&gt;
&lt;li&gt;Check whether the tests detect the change&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the tests still pass after an important behavior is broken, the test suite may not be protecting that behavior.&lt;/p&gt;

&lt;p&gt;This was much more revealing than simply running the tests against correct code.&lt;/p&gt;

&lt;p&gt;A test that passes only tells me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The current code satisfies the current assertion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A deliberately broken version can reveal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whether the assertion would notice the failure I actually care about.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Building an Automatic Inspection Gate with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;After improving the test, I created a GitHub Actions workflow so the tests would run automatically on every push and pull request.&lt;/p&gt;

&lt;p&gt;I created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/workflows/test.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run pytest&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;pytest test_prompts.py -v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended flow was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Push code to GitHub
        ↓
GitHub creates a clean virtual environment
        ↓
Install dependencies
        ↓
Run pytest
        ↓
Display success or failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was my software version of a pre-departure inspection gate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First GitHub Actions Run Failed
&lt;/h2&gt;

&lt;p&gt;I pushed the workflow and waited for the result.&lt;/p&gt;

&lt;p&gt;The job failed.&lt;/p&gt;

&lt;p&gt;The error included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit code 127
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This usually means that the requested command could not be found.&lt;/p&gt;

&lt;p&gt;The cause became clear quickly.&lt;/p&gt;

&lt;p&gt;I had installed &lt;code&gt;pytest&lt;/code&gt; directly in my local environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I had not added it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub Actions starts from a clean environment every time.&lt;/p&gt;

&lt;p&gt;It only installs what is explicitly listed in the project configuration.&lt;/p&gt;

&lt;p&gt;My local computer knew about &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The CI environment did not.&lt;/p&gt;

&lt;p&gt;This was another example of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It works locally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It can be reproduced anywhere.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Adding pytest to the Dependencies
&lt;/h2&gt;

&lt;p&gt;I added &lt;code&gt;pytest&lt;/code&gt; to &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pytest"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I committed and pushed the change again.&lt;/p&gt;

&lt;p&gt;This time, GitHub Actions displayed a green check mark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automatic inspection gate was now running successfully.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Clean CI Environment Revealed
&lt;/h2&gt;

&lt;p&gt;The local environment had accumulated packages from previous development work.&lt;/p&gt;

&lt;p&gt;Because of that, missing dependency declarations could remain hidden.&lt;/p&gt;

&lt;p&gt;GitHub Actions exposed the problem by creating a clean environment.&lt;/p&gt;

&lt;p&gt;The difference looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local environment:
pytest was already installed
        ↓
The command worked
        ↓
The missing dependency declaration stayed hidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions:
Clean environment
        ↓
Install only requirements.txt
        ↓
pytest was missing
        ↓
Command not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The failure was inconvenient, but useful.&lt;/p&gt;

&lt;p&gt;It proved that CI is not only a place to run tests.&lt;/p&gt;

&lt;p&gt;It also checks whether the project can be reconstructed from its declared configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Test Gate Is Only as Good as Its Inspection Items
&lt;/h2&gt;

&lt;p&gt;At first, I imagined the GitHub Actions workflow itself as the safety gate.&lt;/p&gt;

&lt;p&gt;However, this experiment showed that the gate does not decide what is dangerous.&lt;/p&gt;

&lt;p&gt;The test code defines the inspection items.&lt;/p&gt;

&lt;p&gt;If the test checks only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3 suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the gate only confirms that the phrase appears somewhere.&lt;/p&gt;

&lt;p&gt;It does not know that two related instructions must remain consistent.&lt;/p&gt;

&lt;p&gt;In logistics terms, it would be like creating a vehicle inspection gate that checks only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are there tires on the truck?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;while ignoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tire pressure&lt;/li&gt;
&lt;li&gt;Damage&lt;/li&gt;
&lt;li&gt;Loose wheel nuts&lt;/li&gt;
&lt;li&gt;Uneven wear&lt;/li&gt;
&lt;li&gt;Whether all tires are the correct type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The existence of an inspection process does not guarantee the quality of the inspection.&lt;/p&gt;

&lt;p&gt;The checklist itself matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Passing Tests Can Create False Confidence
&lt;/h2&gt;

&lt;p&gt;Before deliberately breaking the prompt, I saw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and felt reassured.&lt;/p&gt;

&lt;p&gt;However, the tests were only as strong as their assertions.&lt;/p&gt;

&lt;p&gt;A passing test can create false confidence when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The assertion is too broad&lt;/li&gt;
&lt;li&gt;Only the happy path is tested&lt;/li&gt;
&lt;li&gt;Important context is ignored&lt;/li&gt;
&lt;li&gt;Multiple conditions are combined into one weak check&lt;/li&gt;
&lt;li&gt;The test confirms output type but not output meaning&lt;/li&gt;
&lt;li&gt;The test suite does not cover production behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean that tests are useless.&lt;/p&gt;

&lt;p&gt;It means that tests are executable assumptions.&lt;/p&gt;

&lt;p&gt;If the assumption is vague, the protection is vague.&lt;/p&gt;




&lt;h2&gt;
  
  
  More Robust Test Design
&lt;/h2&gt;

&lt;p&gt;For this function, stronger tests could include several independent checks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_sales_prompt&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_contains_input_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;sample_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monday: 50 melon buns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;sample_data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_requests_three_suggestions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provide three specific suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_requires_three_bullet_points&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provide three bullet-point suggestions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_returns_string&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_build_sales_prompt_is_not_empty&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_sales_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separating the assertions makes the purpose of each test clearer.&lt;/p&gt;

&lt;p&gt;When one fails, the test name also explains which contract was broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Deployment and the Inspection Gate
&lt;/h2&gt;

&lt;p&gt;There is another important point.&lt;/p&gt;

&lt;p&gt;Running GitHub Actions on every push does not automatically guarantee that Render waits for the tests before deploying.&lt;/p&gt;

&lt;p&gt;The complete safe flow should ideally be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Push or pull request
        ↓
Run automated tests
        ↓
Tests pass
        ↓
Merge or deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the deployment platform automatically deploys every push independently of the GitHub Actions result, the application may still be deployed even when the test workflow fails.&lt;/p&gt;

&lt;p&gt;That means a real deployment gate may require additional configuration, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying only from a protected branch&lt;/li&gt;
&lt;li&gt;Requiring status checks before merging&lt;/li&gt;
&lt;li&gt;Using pull requests instead of pushing directly to &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Triggering deployment only after the test job succeeds&lt;/li&gt;
&lt;li&gt;Configuring branch protection rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating the tests is one layer.&lt;/p&gt;

&lt;p&gt;Connecting the test result to deployment permission is another layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Warning I Also Noticed
&lt;/h2&gt;

&lt;p&gt;During the workflow run, I also saw a warning related to the Node.js runtime used by a GitHub Action.&lt;/p&gt;

&lt;p&gt;It did not affect the pass or fail result of my tests.&lt;/p&gt;

&lt;p&gt;However, I recorded it as a future maintenance item.&lt;/p&gt;

&lt;p&gt;CI configurations also age.&lt;/p&gt;

&lt;p&gt;Even if the application code does not change, workflow actions and runtime environments may eventually require updates.&lt;/p&gt;

&lt;p&gt;That is another reason to review automation logs instead of looking only at the final green check mark.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Here is what I learned from this work.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Writing a Test Is Not Enough
&lt;/h3&gt;

&lt;p&gt;You cannot know what a test truly protects until you deliberately break the relevant behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Weak String Checks Can Miss Contradictions
&lt;/h3&gt;

&lt;p&gt;When the same value appears in multiple contexts, checking only whether the value exists somewhere is not enough.&lt;/p&gt;

&lt;p&gt;The surrounding meaning must also be tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Local and CI Environments Are Different
&lt;/h3&gt;

&lt;p&gt;A package installed only on a developer's machine does not exist in a clean CI environment.&lt;/p&gt;

&lt;p&gt;Dependencies must be declared explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. CI Failures Are Part of the Learning Process
&lt;/h3&gt;

&lt;p&gt;An error such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit code 127
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;provides information.&lt;/p&gt;

&lt;p&gt;The failure itself becomes a troubleshooting clue.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. A Green Check Mark Is Not Absolute Proof
&lt;/h3&gt;

&lt;p&gt;It only proves that the configured jobs and assertions passed.&lt;/p&gt;

&lt;p&gt;It does not prove that every important behavior has been tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Tests and Deployment Gates Are Separate Concerns
&lt;/h3&gt;

&lt;p&gt;Running tests on push does not automatically mean a deployment platform will refuse failing code.&lt;/p&gt;

&lt;p&gt;The workflow must be connected to branch and deployment rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was the second project in a row where the result differed from what I expected.&lt;/p&gt;

&lt;p&gt;In the previous experiment, I introduced a Docker multi-stage build and reduced the image by only 3 MB.&lt;/p&gt;

&lt;p&gt;In this experiment, I introduced tests and discovered that the first version of those tests was weaker than I thought.&lt;/p&gt;

&lt;p&gt;That is exactly why I believe these records are worth writing.&lt;/p&gt;

&lt;p&gt;Real development rarely follows the perfect story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learn a technique
        ↓
Implement it once
        ↓
Everything works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual process is more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Learn a technique
        ↓
Implement it
        ↓
Misunderstand part of it
        ↓
Observe an unexpected result
        ↓
Investigate
        ↓
Improve the design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unexpected parts often contain the most useful lessons.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article is based on actual development logs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  sales_data_app Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/ac18b633c8c87b9807bb" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/ac18b633c8c87b9807bb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/f0aeed574d39c5fa5093" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/f0aeed574d39c5fa5093&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/e19ed4a2ffe27f53faf0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/c1609f17cddf842f1e7c" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/c1609f17cddf842f1e7c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9b6576c1fda3d3a76d2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-Production Inspection Trilogy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/7a15e942745545a37b6a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/7a15e942745545a37b6a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/3db0e915439048624a40" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/3db0e915439048624a40&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/b9f32a1b03b99405ad0a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Post-Deployment Maintenance Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/cb1156c04c0402b7f7ce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/tosane932/items/4825452f4bb73fd90ba8" rel="noopener noreferrer"&gt;https://qiita.com/tosane932/items/4825452f4bb73fd90ba8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🍞 &lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;sales_data_app on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pytest</category>
      <category>githubactions</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🚚 Three Coding Near Misses I Experienced as a Truck Driver</title>
      <dc:creator>tosane932</dc:creator>
      <pubDate>Thu, 30 Jul 2026 17:25:57 +0000</pubDate>
      <link>https://dev.to/tosane932/three-coding-near-misses-i-experienced-as-a-truck-driver-4j1j</link>
      <guid>https://dev.to/tosane932/three-coding-near-misses-i-experienced-as-a-truck-driver-4j1j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello from Japan! 🇯🇵&lt;/p&gt;

&lt;p&gt;I am &lt;code&gt;tosane932&lt;/code&gt;, a professional truck driver working in logistics while teaching myself Python.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In workplace safety, there is a concept commonly known as &lt;strong&gt;Heinrich’s accident triangle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It describes a relationship in which one serious accident is associated with many smaller incidents and an even larger number of &lt;strong&gt;near misses&lt;/strong&gt;—situations where no accident occurred, but something dangerous almost happened.&lt;/p&gt;

&lt;p&gt;Recently, while introducing &lt;code&gt;pytest&lt;/code&gt; and GitHub Actions into &lt;code&gt;sales_data_app&lt;/code&gt;, I experienced &lt;strong&gt;three coding near misses in a row&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, none of them caused actual damage.&lt;/p&gt;

&lt;p&gt;However, precisely because nothing serious happened, I believe they are worth reviewing.&lt;/p&gt;

&lt;p&gt;This article records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happened&lt;/li&gt;
&lt;li&gt;Why it happened&lt;/li&gt;
&lt;li&gt;How I noticed it&lt;/li&gt;
&lt;li&gt;What habit prevented it from becoming a real problem&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🐧 Near Miss 1: Terminal Output Became File Names
&lt;/h2&gt;

&lt;p&gt;I had left the output from a &lt;code&gt;docker build&lt;/code&gt; command visible in my terminal so I could review it.&lt;/p&gt;

&lt;p&gt;The log contained lines such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=&amp;gt; [internal] load build definition from Dockerfile
=&amp;gt; =&amp;gt; naming to docker.io/library/sales-after:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, I casually ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unexpected files appeared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Untracked files:
        =
        CACHED
        [internal]
        exporting
        naming
        resolve
        transferring
        unpacking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After thinking about the cause, I realized that characters such as &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;=&amp;gt;&lt;/code&gt; have a special meaning to the shell.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;gt;&lt;/code&gt; character is used for &lt;strong&gt;output redirection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At some point while copying or pasting the log, part of the text appears to have been interpreted as a shell command.&lt;/p&gt;

&lt;p&gt;Words surrounding the redirection symbols were then created as file names.&lt;/p&gt;

&lt;p&gt;The actual damage was small.&lt;/p&gt;

&lt;p&gt;Only eight empty junk files were created, and they had not yet been added to Git.&lt;/p&gt;

&lt;p&gt;However, if I had run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without checking first, those meaningless files could have entered the repository history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Always run &lt;code&gt;git status&lt;/code&gt; before using a broad command such as &lt;code&gt;git add .&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A single confirmation step can prevent unrelated files from being committed.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐧 Near Miss 2: A New &lt;code&gt;.gitignore&lt;/code&gt; Pattern Joined the Previous Line
&lt;/h2&gt;

&lt;p&gt;I wanted to exclude backup files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from Git tracking.&lt;/p&gt;

&lt;p&gt;I added the following pattern to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.bak"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I checked the result, I found this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales_data_app_android_demo.mp4*.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two patterns should have been on separate lines.&lt;/p&gt;

&lt;p&gt;Instead, they had been joined together.&lt;/p&gt;

&lt;p&gt;The cause was that the final line of &lt;code&gt;.gitignore&lt;/code&gt; did not end with a newline.&lt;/p&gt;

&lt;p&gt;When I appended text using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.bak"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the new pattern was attached directly to the existing final line.&lt;/p&gt;

&lt;p&gt;Git then interpreted the entire string as one strange pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales_data_app_android_demo.mp4*.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, neither the &lt;code&gt;.mp4&lt;/code&gt; file nor the &lt;code&gt;.bak&lt;/code&gt; files were ignored correctly.&lt;/p&gt;

&lt;p&gt;When I ran &lt;code&gt;git status&lt;/code&gt;, the &lt;code&gt;.mp4&lt;/code&gt; file appeared as untracked even though I expected Git to ignore it.&lt;/p&gt;

&lt;p&gt;That confirmed that the pattern was not working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;After appending text with &lt;code&gt;echo &amp;gt;&amp;gt;&lt;/code&gt;, always inspect the file using &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not assume that a text file already ends with a newline.&lt;/p&gt;

&lt;p&gt;A safer way to guarantee separation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\n*.bak\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, even when using a safer command, checking the final contents is still important.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐧 Near Miss 3: I Almost Overwrote the Wrong File
&lt;/h2&gt;

&lt;p&gt;This was the most frightening of the three incidents.&lt;/p&gt;

&lt;p&gt;I was improving my &lt;code&gt;pytest&lt;/code&gt; test code.&lt;/p&gt;

&lt;p&gt;The file I needed to edit was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_prompts.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, I also had the production file open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompts.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the file names were similar, I accidentally overwrote the production &lt;code&gt;prompts.py&lt;/code&gt; file with the contents of the test code.&lt;/p&gt;

&lt;p&gt;Fortunately, immediately after saving it, I felt that something was wrong.&lt;/p&gt;

&lt;p&gt;I noticed the mistake and restored the correct contents.&lt;/p&gt;

&lt;p&gt;No actual damage occurred.&lt;/p&gt;

&lt;p&gt;However, what would have happened if I had not noticed and had pushed the change?&lt;/p&gt;

&lt;p&gt;Possible consequences included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Gemini prompt-generation logic used in production could have been replaced by test code&lt;/li&gt;
&lt;li&gt;The application could have stopped working&lt;/li&gt;
&lt;li&gt;The application could have behaved unexpectedly&lt;/li&gt;
&lt;li&gt;The broken production file could have been deployed&lt;/li&gt;
&lt;li&gt;GitHub Actions might still have passed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final point is especially important.&lt;/p&gt;

&lt;p&gt;The test file itself was not necessarily broken.&lt;/p&gt;

&lt;p&gt;The mistake affected a production module that may not have been fully protected by the existing tests.&lt;/p&gt;

&lt;p&gt;In other words, this was the type of error that could have passed through the test gate I had just introduced.&lt;/p&gt;

&lt;p&gt;That is why this incident frightened me more than the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;When editing similarly named files, confirm the file name immediately before saving.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After editing, also use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to confirm that only the intended files changed.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;shows a compact list of changed files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--&lt;/span&gt; prompts.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;shows exactly what changed inside a specific file.&lt;/p&gt;

&lt;p&gt;The important question is not only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the code look correct?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is also:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did I modify the file I actually intended to modify?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Tests Alone Could Not Prevent This Mistake
&lt;/h2&gt;

&lt;p&gt;Introducing automated tests improves safety, but tests cannot detect every type of human error.&lt;/p&gt;

&lt;p&gt;Tests only protect behavior that has been explicitly covered.&lt;/p&gt;

&lt;p&gt;If I accidentally replace a production file and the test suite does not verify that module correctly, the CI pipeline may still show a green result.&lt;/p&gt;

&lt;p&gt;That means software safety requires several layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automated tests&lt;/li&gt;
&lt;li&gt;Static analysis&lt;/li&gt;
&lt;li&gt;Code review&lt;/li&gt;
&lt;li&gt;Git diffs&lt;/li&gt;
&lt;li&gt;File-name confirmation&lt;/li&gt;
&lt;li&gt;Deployment checks&lt;/li&gt;
&lt;li&gt;Runtime logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A passing test result does not mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every possible mistake has been prevented.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The behaviors currently covered by the tests passed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction became very clear during this near miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐧 Summary
&lt;/h2&gt;

&lt;p&gt;None of these three incidents caused actual damage.&lt;/p&gt;

&lt;p&gt;However, they all shared one important characteristic:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I had noticed them slightly later, they could have become real problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like near misses in logistics, these events should not end with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nothing happened, so it was fine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The valuable questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why did the mistake occur?&lt;/li&gt;
&lt;li&gt;Why did I notice it?&lt;/li&gt;
&lt;li&gt;Which habit prevented actual damage?&lt;/li&gt;
&lt;li&gt;How can I reduce the chance of repeating it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case:&lt;/p&gt;

&lt;h3&gt;
  
  
  Near Miss 1
&lt;/h3&gt;

&lt;p&gt;I noticed the junk files because I had made &lt;code&gt;git status&lt;/code&gt; a habit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Near Miss 2
&lt;/h3&gt;

&lt;p&gt;I noticed the broken &lt;code&gt;.gitignore&lt;/code&gt; pattern because I checked the file after appending to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Near Miss 3
&lt;/h3&gt;

&lt;p&gt;I noticed the wrong overwrite because I felt something was unusual immediately after saving.&lt;/p&gt;

&lt;p&gt;None of these required an advanced technical skill.&lt;/p&gt;

&lt;p&gt;The difference was whether I performed one additional check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Small Checks Are the Final Safety Barrier
&lt;/h2&gt;

&lt;p&gt;In truck driving, major accidents are often prevented by ordinary actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking mirrors again&lt;/li&gt;
&lt;li&gt;Confirming the loading area&lt;/li&gt;
&lt;li&gt;Inspecting the cargo&lt;/li&gt;
&lt;li&gt;Looking behind the vehicle&lt;/li&gt;
&lt;li&gt;Leaving enough following distance&lt;/li&gt;
&lt;li&gt;Performing a final check before departure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software development has similar actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reading the target file name&lt;/li&gt;
&lt;li&gt;Checking &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reviewing staged changes&lt;/li&gt;
&lt;li&gt;Running tests&lt;/li&gt;
&lt;li&gt;Inspecting deployment logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These actions are not exciting.&lt;/p&gt;

&lt;p&gt;They do not look like major technical achievements.&lt;/p&gt;

&lt;p&gt;However, they often form the final barrier between a small mistake and a production incident.&lt;/p&gt;

&lt;p&gt;The more I learn programming, the more I feel that coding and truck driving share the same basic safety principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not depend on everything going perfectly.&lt;br&gt;&lt;br&gt;
Build habits that help you notice when something is wrong.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Practical Pre-Commit Check
&lt;/h2&gt;

&lt;p&gt;After these incidents, I began thinking of the following sequence as a simple pre-departure inspection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;--short&lt;/span&gt;
git diff
git diff &lt;span class="nt"&gt;--staged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are only the intended files changed?&lt;/li&gt;
&lt;li&gt;Are any strange untracked files present?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;.gitignore&lt;/code&gt; work?&lt;/li&gt;
&lt;li&gt;Did I accidentally modify a production file?&lt;/li&gt;
&lt;li&gt;Are secrets or temporary files included?&lt;/li&gt;
&lt;li&gt;Does the staged diff match the commit message?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after that do I commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &amp;lt;specific-files&amp;gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever possible, I prefer adding specific files instead of immediately using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes a little longer, but it makes the cargo being committed more visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;These incidents were harmless.&lt;/p&gt;

&lt;p&gt;That does not make them meaningless.&lt;/p&gt;

&lt;p&gt;A near miss is valuable because it reveals a weakness before the weakness causes real damage.&lt;/p&gt;

&lt;p&gt;This time, I learned that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminal text can be interpreted as shell syntax&lt;/li&gt;
&lt;li&gt;Appending text can fail when the previous line has no newline&lt;/li&gt;
&lt;li&gt;Similar file names can cause edits to land in the wrong place&lt;/li&gt;
&lt;li&gt;Automated tests cannot replace basic confirmation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git status&lt;/code&gt; and &lt;code&gt;git diff&lt;/code&gt; are simple but powerful safety checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing code and driving a truck are different jobs.&lt;/p&gt;

&lt;p&gt;However, the last line of defense against human error is often the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A series of small, unglamorous checks.&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This article is based on actual work logs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tosane932/sales_data_app" rel="noopener noreferrer"&gt;https://github.com/tosane932/sales_data_app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Qiita
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://qiita.com/tosane932" rel="noopener noreferrer"&gt;https://qiita.com/tosane932&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
