- Risk scoring doesn't rely on AI — it's a pure-TypeScript tag sum: ≥4 high risk, ≥2 medium risk, <2 low risk, fully explainable.
- Food lookup / recommendations / FAQ / knowledge center are usable without login; diet diary, AI analysis, PDF reports, and favorites require Firebase login.
- The full stack runs on the free tiers of Cloudflare Pages + Workers + D1 and Firebase, keeping the monthly cost at $0.
Table of Contents
Nutrition Guard is a dietary risk management system targeting a “multi-population, multi-condition” scenario, aimed at four patient groups: gout, high cholesterol, diabetes, and hypertension. Its core isn’t a fancy ML model but an explainable, auditable tag scoring engine: each food carries a number of risk tags for each condition, each tag has a score, and the sum lands in one of three levels — red / yellow / green. The entire backend runs on the free tiers of Cloudflare and Firebase, keeping the monthly cost at $0.
Why Build “Multi-Condition” Scoring
The real pain point for chronic disease patients isn’t “not being able to find a taboo list for a single condition” — it’s that the same meal often has to account for several conditions at once. Someone with both gout and high cholesterol can hardly tell at a glance, from a static nutrition table, whether a given food is okay to eat. Nutrition Guard breaks this down into a “food × condition × tag” data structure, so once users select their own set of conditions, they immediately get corresponding real-time risk scores across 140 foods.
Features and Permission Tiers
The product deliberately makes “lookup-type” features usable without login to lower the barrier to entry; only features that need to save personal data require signing in:
| Feature | Login Required | Description |
|---|---|---|
| Food Lookup | No | Search 140 foods and view real-time risk scores per condition |
| Dietary Recommendations | No | Lists foods to avoid / safe to eat by condition |
| Knowledge Center | Reading is login-free | Markdown articles; after login you can add / edit your own articles |
| FAQ | No | 12 common questions, filterable by condition |
| Diet Diary | Yes | Log daily meals, visualized as a 7-day bar chart |
| AI Diet Analysis | Yes | Workers AI generates personalized advice from the last 7 days of records |
| PDF Report Export | Yes | A4 report with charts + AI advice, one-click download |
| My Favorites | Yes | Save frequently used foods, synced to display current condition risk |
Tech Stack
The frontend is Vite 5 + React 18 + TypeScript, styled with TailwindCSS v3, routed via React Router v6, with state managed by Zustand and persisted to localStorage. The risk engine is pure TypeScript (evaluate(tags, condition) → FoodRisk in src/engine/riskEngine.ts) with no AI dependency whatsoever.
The backend is a single Cloudflare Worker serving the food API, with data stored in Cloudflare D1 (SQLite, 140 foods). Only the personalized recommendations that require reasoning are handed off to Cloudflare Workers AI (llama-3.1-8b-instruct). PDFs are generated on the frontend via @react-pdf/renderer, lazy-loaded and embedding the Noto Sans SC font to correctly output Chinese. User authentication goes through Firebase Auth (Google OAuth), and user data (diet diary, favorites, custom articles) is stored in Firebase Firestore. The entire site is hosted on Cloudflare Pages.
The Worker’s public API is minimal:
GET /api/foods: food lookupGET /api/foods/:id: single foodGET /api/stats: statisticsPOST /api/analyze: calls Workers AI for diet analysis
Note: The Workers AI binding can only run in the Cloudflare edge environment, so local development of
POST /api/analyzemust usewrangler dev --remoteto work.
Architecture
graph LR
User["User"] --> FE["React 18 SPA<br/>(Cloudflare Pages)"]
FE -->|"Google OAuth"| Auth["Firebase Auth"]
FE -->|"diary / favorites / articles"| FS[("Firestore")]
FE -->|"food lookup / AI analysis"| Worker["Cloudflare Worker"]
Worker --> D1[("Cloudflare D1<br/>140 foods / ~300 tags")]
Worker -->|"POST /api/analyze"| AI["Workers AI<br/>(llama-3.1-8b-instruct)"]
The Core: Explainable Tag Scoring
The design priority of the scoring system is explainability — no black-box model, but every risk factor explicitly laid out as tags and scores. The database expresses “which condition tags a food carries” with two tables:
CREATE TABLE foods (
id TEXT PRIMARY KEY,
name_zh TEXT NOT NULL,
name_en TEXT NOT NULL,
category TEXT NOT NULL -- meat | seafood | vegetable | fruit | drink | grain | dairy | other
);
CREATE TABLE food_tags (
food_id TEXT NOT NULL REFERENCES foods(id),
condition TEXT NOT NULL, -- gout | high cholesterol | diabetes | hypertension
tag TEXT NOT NULL
);
Each tag is scored by severity — for example, gout’s high_purine, organ_meat, and alcohol are 3 points each, seafood_high_risk is 2, and moderate_purine is 1; high cholesterol’s trans_fat and high_saturated_fat are 3 each; diabetes’s high_sugar, refined_carbs, and sweetened_drink are 3 each; hypertension’s high_sodium is 3, and processed_food / canned_food / pickled_food are 2 each. For the selected condition, the matched tag scores are summed and graded:
flowchart TD
A(["Select condition + query food"]) --> B["Pull food_tags from D1 → sum tags"]
B --> C{Total score}
C -- "≥ 4" --> D["🔴 High risk"]
C -- "≥ 2" --> E["🟡 Medium risk"]
C -- "< 2" --> F["🟢 Low risk"]
D & E & F --> G{Logged in?}
G -- Yes --> H["Log diet diary → 7-day chart"]
H --> I["Workers AI personalized advice"]
I --> J["PDF report export"]
G -- No --> Z(["End"])
J --> Z
The benefit of this design is that when a user sees “high risk,” they can directly tell which specific tags were matched, rather than being brushed off by a single confidence score. Adding foods or adjusting scores also just means editing seed.sql and the tag scores — no need to retrain any model.
Data Permissions
Firestore’s security rules separate “public read, login to write” from “users can only touch their own data”: knowledge center articles are readable by anyone, writable only after login, and editable / deletable only by the author; diet diary and favorites under users/{uid} are strictly limited to request.auth.uid == uid for read and write.
Deployment and Cost
The deployment flow is the classic full Cloudflare bundle: use wrangler d1 create to create D1, apply schema.sql and seed.sql to load the 140 foods, worker:deploy to ship the API, while the frontend is handed to Cloudflare Pages linked to GitHub for automatic builds (build command npm run build, output dist). On the Firebase side, just enable Google login and Firestore and apply the security rules. Since D1, Workers, Workers AI, Pages, and Firebase all fall within the free tiers, the overall monthly cost is $0 — which is also the most important constraint in this project’s technical choices.
Summary
Nutrition Guard demonstrates a pragmatic tradeoff: whatever can be solved with rules shouldn’t be thrown at a model. Core logic like risk scoring — which needs to be trusted and explained — is handled with a pure tag-summing function; only the part that genuinely requires natural language generation, like “writing a paragraph of personalized advice from the last 7 days of records,” calls on Workers AI. Combined with the free tiers of Cloudflare + Firebase, the whole product delivers a complete lookup, logging, analysis, and report-export experience at zero operating cost.
References
Answers come from this article only. Click any prompt below or open the chat at the bottom right.
🇺🇸 English
Ever tried to plan a meal for someone who has both gout and high cholesterol? It sounds simple until you actually try it. You pull up a nutrition table, and it tells you about purines, or it tells you about saturated fat — but almost never both at once, in a way you can act on in the two seconds you have before ordering. That's the exact gap that a project called Nutrition Guard set out to fill. And here's the headline that makes it interesting for us as engineers: the entire backend runs on free tiers, and the monthly cost is zero dollars. Not "cheap." Zero.
So let's talk about what it actually is. Nutrition Guard is a dietary risk system built for four groups of chronic patients: people managing gout, high cholesterol, diabetes, and hypertension. Now, the temptation with a project like this is to reach for a fancy machine learning model. The team deliberately did the opposite. At the heart of it sits something they call a tag scoring engine — and the whole point of it is that it's explainable and auditable. Every food carries a set of risk tags for each condition, every tag has a point value, you add them up, and the total lands in one of three buckets: red, yellow, or green. That's it. No neural network deciding your dinner.
Why go multi-condition? Because that's the real pain point. It's not that a person with gout can't find a list of foods to avoid — those lists are everywhere. It's that a single meal has to satisfy several conditions simultaneously, and no static table does that math for you. So Nutrition Guard restructures the whole thing into a "food times condition times tag" data shape. You pick your conditions, and instantly you get real-time risk scores across a hundred and forty foods, tuned to your specific combination.
Let me walk you through what you can actually do with it, because the design here is thoughtful. The team split features by whether they need your data or not. Anything that's just looking something up — you can use without logging in. Searching those hundred and forty foods and seeing per-condition risk? No login. Dietary recommendations, the "avoid this, this is safe" lists? No login. Reading the knowledge center articles, browsing the twelve most common FAQs filtered by your condition? All open. That lowers the barrier to walking in the door.
Then there's the stuff that needs to remember who you are. Keeping a diet diary — logging your meals and seeing them as a seven-day bar chart — that needs login. The AI diet analysis, where the system reads your last seven days and writes you personalized advice, needs login. Exporting a PDF report with your charts and that advice baked in, one click, needs login. And saving your favorite foods so they track your current condition's risk — login again. The line is clean: look things up freely, but the moment it's *your* data, you sign in.
Now the tech stack, because this is where the "zero cost" magic lives. On the front end it's Vite, React 18, and TypeScript, styled with Tailwind, routed with React Router, and state handled by Zustand with everything persisted to local storage. The risk engine itself is pure TypeScript — a single function that takes a food's tags and a condition and hands back a risk rating. No AI anywhere in that path. It's just arithmetic you can read and trust.
The backend is a single Cloudflare Worker serving the food API, with the data — those hundred and forty foods — living in Cloudflare D1, which is essentially SQLite at the edge. The *only* place they call an actual language model is the personalized recommendation, where they hand off to Cloudflare Workers AI running Llama 3.1. PDFs get generated right in the browser, lazy-loaded, embedding a Noto Sans font so Chinese characters render correctly. Login runs through Firebase Auth with Google sign-in, and your personal data — diary, favorites, custom articles — sits in Firestore. The whole site is hosted on Cloudflare Pages.
One nice practical footnote for anyone building on this: the Workers AI binding only runs in Cloudflare's actual edge environment. So if you're developing that analyze endpoint locally, a plain local dev server won't cut it — you have to run wrangler in remote mode to get it talking to the real AI.
Let me describe the overall flow, since there's no picture to look at. The user hits the React app on Cloudflare Pages. From there, three things branch out. Google sign-in goes to Firebase Auth. Anything personal — diary, favorites, articles — reads and writes to Firestore. And food lookups plus AI analysis go to the Cloudflare Worker. The Worker, in turn, reads from D1 for the food data, and when a request comes in for analysis, it reaches over to Workers AI. Clean separation: Firebase holds *you*, Cloudflare holds the *knowledge*.
Now, the core of the whole thing — the scoring — deserves a closer look, because the design philosophy is the real lesson here. The top priority was explainability. No black box. Every risk factor is spelled out as a tag with a score. In the database it's two simple tables. One table is just the foods: an id, the name, and a category — meat, seafood, vegetable, fruit, drink, grain, dairy, other. The second table maps each food to its risk tags, and crucially, each tag is stamped with which condition it applies to.
The scoring is severity-weighted, and once you hear a few examples it clicks. For gout, the heavy hitters — high purine, organ meat, alcohol — are three points each. High-risk seafood is two. Moderate purine is just one. For high cholesterol, trans fat and high saturated fat are three points apiece. For diabetes, high sugar, refined carbs, and sweetened drinks — three each. For hypertension, high sodium is three, while processed, canned, and pickled foods are two each. When you query a food for your condition, the engine grabs the matching tags, sums the points, and grades it: four or more is red, high risk; two or three is yellow, medium; below two is green, low. And if you're logged in, that result feeds forward — into your diary, into the seven-day chart, into the AI advice, and finally into the exportable PDF.
Here's why that structure is genuinely better than a slick model. When the app tells you a food is high risk, it can show you *exactly which tags* triggered it — "this scored red because it's high purine and it's an organ meat" — instead of shrugging at you with a confidence percentage. And maintenance is trivial. Want to add a food or retune a score? You edit a seed file and the tag values. No retraining, no pipeline, no GPU bill.
Quick word on permissions, because they got this right too. Firestore's security rules draw two lines. First: public read, but login to write — anyone can read a knowledge article, but only signed-in users can create one, and only the author can edit or delete it. Second: your private data is locked to you. The diary and favorites under your user id can only be read and written when the requester's authenticated id matches. Nobody's browsing your meals.
And the cost story ties it all together. Deployment is the classic full-Cloudflare bundle: spin up D1, load the schema and the hundred and forty foods, deploy the Worker, and point Cloudflare Pages at your GitHub repo for automatic builds. On the Firebase side, just switch on Google login and Firestore and apply those rules. Because D1, Workers, Workers AI, Pages, and Firebase all live comfortably inside their free tiers, the total operating cost stays at zero — and honestly, that constraint was the compass for every technical choice in the project.
So what do I want you to take away from this one?
First and biggest: whatever you can solve with rules, don't hand to a model. The risk scoring — the part that has to be trusted and explained — is a plain tag-summing function. The model only shows up for the one job that genuinely needs natural language: writing a paragraph of advice from your week of records. That's the discipline. Reach for AI where AI earns its keep, and nowhere else.
Second: explainability isn't a nice-to-have here, it's the feature. Because scoring is transparent tags and points, users see *why* something is risky, and the team can extend the system by editing a file instead of retraining anything. Trust and maintainability, from the same design decision.
And third: this whole thing — lookup, logging, analysis, PDF reports — runs at zero monthly cost. That's not an accident, it's an architecture. When you treat "free tier only" as a real constraint from day one, it quietly pushes you toward simpler, more explainable, more maintainable choices. Sometimes the cheapest system is also the clearest one. That's the trick worth stealing.
🇹🇼 中文
Nutrition Guard 這個專案,鎖定的是一個蠻真實的痛點:慢性病患者的飲食管理。而且它不是只顧一種病,而是一次照顧痛風、高血脂、糖尿病、高血壓這四種狀況。有意思的是,它的核心完全沒有用花俏的機器學習模型,而是一套可以解釋、可以審計的「標籤評分引擎」。整個後端跑在 Cloudflare 跟 Firebase 的免費額度上,月費維持在零元。
先說為什麼要做「多病症」這件事。你想想,一個同時有痛風又有高血脂的人,看那種靜態的營養成分表,其實很難一眼判斷一份食物到底能不能吃。因為每種病的禁忌邏輯不一樣。所以 Nutrition Guard 的作法,是把問題拆成「食物、病症、標籤」這樣一個三維的資料結構。使用者先選定自己的病症條件,接著就能對一百四十種食物,即時拿到對應的風險分數。
在功能設計上,它做了一個很聰明的權限分層。凡是「查詢類」的功能,一律免登入就能用,把使用門檻壓到最低——像是食物查詢、飲食建議、看知識文章、還有常見問答,這些都不用登入。只有需要保存個人資料的功能才要求登入,比如飲食日記、AI 飲食分析、PDF 報告匯出、還有我的收藏。這個切法很直覺:讓人先無痛用起來,真的要存東西了再登入。
技術選型的部分。前端是 Vite 加 React 18 加 TypeScript,樣式用 Tailwind,狀態管理用 Zustand,還持久化到 localStorage。重點來了——風險引擎是純 TypeScript 寫的一個函式,輸入標籤跟病症條件,回傳風險結果,完全不碰 AI。後端是一支 Cloudflare Worker 提供食物 API,資料存在 Cloudflare D1,也就是邊緣的 SQLite,裝了一百四十種食物。只有需要推理的個人化建議,才會交給 Workers AI,用的是 llama-3.1-8b。PDF 是在前端用 lazy load 的方式生成,還內嵌了 Noto Sans 字體,才能正確輸出中文。使用者驗證走 Firebase 的 Google 登入,個人資料存在 Firestore。
這裡有一個開發上要注意的坑:Workers AI 的 binding 只能在 Cloudflare 的邊緣環境跑。所以你本機開發那個 analyze 的 API,必須加上 remote 參數,用 wrangler dev remote 模式,才跑得起來。
再來講整個系統的骨架。使用者打開的是一個 React 的單頁應用,host 在 Cloudflare Pages 上。登入走 Firebase Auth;日記、收藏、文章這些個人資料進 Firestore;而食物查詢跟 AI 分析則是打到 Cloudflare Worker,Worker 背後接 D1 資料庫,裡面大概是一百四十種食物、三百多個標籤,需要生成建議的時候再往 Workers AI 送。
好,現在來到最核心、也是我覺得最值得聊的部分:可解釋的標籤評分。它的設計重點就是「可解釋性」——不用黑盒模型,而是把每一個風險因子,明確地列成標籤跟分數。資料庫用兩張表:一張 foods 存食物本身,一張 food_tags 記錄「哪個食物、對哪個病症、掛了哪些標籤」。
每個標籤會依照嚴重程度給分。舉例來說,痛風這邊,高普林、內臟類、酒精,各給三分;高風險海鮮兩分;中普林一分。高血脂的話,反式脂肪跟高飽和脂肪各三分。糖尿病是高糖、精緻碳水、含糖飲料各三分。高血壓則是高鈉三分,加工食品、罐頭、醃漬類各兩分。
計算方式很單純:針對你選定的病症,把命中的標籤分數加總。總分大於等於四,就是紅燈高風險;大於等於二,是黃燈中風險;低於二,就是綠燈低風險。如果你有登入,還可以把這筆記進飲食日記,累積成七天的長條圖,再讓 Workers AI 生成個人化建議,最後匯出成 PDF 報告。
這種設計的好處非常明顯:當使用者看到「高風險」三個字的時候,他能直接知道是因為哪幾個標籤被命中——是因為高普林?還是因為酒精?而不是被一個冷冰冰的信心分數打發掉。而且要新增食物、或調整評分,你只要改 seed 檔跟標籤分數就好,完全不用重新訓練任何模型。
資料權限的部分也處理得很乾淨。Firestore 的安全規則把兩件事分開:一個是「公開可讀、登入才能寫」,知識文章任何人都能看,但只有作者本人能改能刪;另一個是使用者自己的日記跟收藏,嚴格限定只有本人的 uid 才能讀寫。
成本這塊,因為 D1、Workers、Workers AI、Pages 加上 Firebase,全部都落在免費額度裡,所以整體月費就是零元。而這個「零成本」,其實正是這個專案在技術選型上最重要的那條約束線。
那我幫大家收斂三個重點。第一,這個專案示範了一個很務實的取捨:能用規則解決的,就不要丟給模型。風險評分這種需要被信任、被解釋的核心邏輯,用標籤加總的純函式來處理最合適。第二,AI 只用在刀口上——只有「根據近七天記錄,寫出一段自然語言的個人化建議」這種真正需要生成能力的環節,才動用 Workers AI。第三,靠著可解釋的標籤架構加上 Cloudflare 跟 Firebase 的免費額度,它在零營運成本下,完整提供了查詢、記錄、分析到報告匯出的體驗。這是一個很好的提醒:很多時候,一套設計良好的規則引擎,比一個模型更值得信任。
Tags
Related Articles
a920604a Labs: A Dual-Cloud Full-Stack Playground Integrating Four Tools with a pnpm Monorepo
A pnpm monorepo integrating four tool SPAs: a to-do list, a habit tracker, an ebook reader, and a resignation stamp collector. apps/root is the sole build entry point; the four modules are library-only workspace packages bundled together by root's Vite, sharing @a920604a/auth and @a920604a/ui, deployed across Firebase + Cloudflare dual cloud.
Live English Tutor: Building a Real-Time Voice AI English Tutor with LiveKit + Gemini Native Audio
A real-time-voice-first AI English tutoring system: students converse with the AI teacher Emma via microphone (optionally with video/screen sharing), the system corrects mistakes in real time, and generates a post-class report in Chinese. The technical core is LiveKit (Self-hosted WebRTC) + Google Gemini 2.5 Flash Native Audio, with a FastAPI backend handling auth, courses, and data persistence.
arXiv Knowledge Assistant: Automated Paper Retrieval and a Bilingual RAG Q&A Platform
A microservice platform orchestrated with Docker Compose: it crawls arXiv papers daily, builds a Qdrant vector index, and delivers bilingual RAG Q&A through hybrid search + re-ranking + Ollama, with email subscriptions and Grafana monitoring.