Andrea Petrucci · July 2026 · EN + IT
🇬🇧 English below · 🇮🇹 Versione italiana più in basso
KitchenMetrics is an Android app for professional kitchens: dish costing, deep-fryer oil safety, cook-time estimation, a fat/oil reference database, and a recipe book that re-costs itself when ingredient prices change. Five tools, three languages (EN/IT/ES), fully offline.
It exists because I cook for a living and none of the existing apps model how a kitchen actually works — most food-cost calculators, for example, apply a single “yield” percentage, when in reality there are two sequential ones (butchering yield AP→EP, then cooking loss EP→plated) and conflating them systematically under-prices dishes.
Status: live on Google Play in closed testing since 15 July 2026
(com.kitchenmetrics.app, targetSdk 36, minSdk 26, ~2.4 MB AAB, Play App Signing).
The journey, for context: two and a half months ago this was a Python exercise written while learning to program. It grew into a web app, then an Android app, then a signed release on Google Play — built by a working cook, with AI assistants as tutors and reviewers, and every formula and legal limit verified against published sources. This document describes what came out the other end, developer to developer.
@JavascriptInterface bridge for the two things a WebView can’t do:
persistence (SharedPreferences) and printing. Everything else is plain HTML/CSS/JS in assets/.onclick handlers that resolve against global scope; type="module" would silently break
all of them. A smoke-test suite exists specifically to catch that class of failure.core.js): AndroidBridge → SharedPreferences on device,
localStorage fallback in a browser. The whole app runs unchanged in desktop Chrome,
which is where most development and testing happens.The interesting work is in the models, and each one is sourced:
Fryer safety compares oil degradation against a per-country TPC registry of 12 jurisdictions, each entry carrying its named source and a status saying whether that source is law: 4 statutory (27% CH, 25% ES, 25% AR, 25% CL — all four primary texts read at source), 2 disputed (IT, AU), and 6 with no limit found at all (MX, UK, IE, US, CA, NZ). The app always applies the stricter of the oil’s chemistry limit and the local figure.
Corrected 19 July 2026: this paragraph used to read “a registry of statutory TPC limits
(24% DE, 25% IT/ES/FR, 27% CH…)”. Germany and France have no entry in the registry —
they never did — and Germany’s 24% is a DGF recommendation, not a statute, which is
precisely the error SOURCES.md spends a section warning against. Two files in the same
repository disagreed, and the marketing-shaped one was the wrong one.
What is unusual here is not the limits but the status column: competitors cite “EFSA/FDA thresholds” that, on verification, don’t exist (neither body sets any TPC limit).
12,703 assertions across thirty-six suites, all green, run after every change
(21 July 2026). The figure is regenerated by tests/run_all.js rather than
remembered — it had drifted twice before that runner existed.
| Suite | Assertions | Guards |
|---|---|---|
| i18n | 3,212 | key parity, duplicate keys (silently dropped by JS object literals) |
| db_i18n | 920 | fat DB translation coverage + cross-registry agreement |
| tpc_limits | 173 | legal registry integrity per jurisdiction, incl. confidence |
| thermal_geometry | 164 | 1-D eigenvalues vs Çengel; product solution for finite bodies (identities and limits, no published example yet) |
| smoke | 70 | app wiring; asserts the harness script list matches index.html |
| acrylamide | 55 | Reg. (EU) 2017/2158 frying cap |
| db_contradictions | 68 | pathogen-model flags; pins the two egg custards that must keep theirs |
| cooking_temps | 50 | per-jurisdiction core-temperature registry |
| load_factor | 90 | Choi-Okos c_p vs published water and lean-beef values |
| cost_engine | 31 | costing model + regression pins for 6 fixed bugs |
| data_integrity | 45 | no row cites a source that does not exist; bounded known defects |
| thermal_radiation | 23 | radiative term, dry-heat modes only |
| privacy.manifest | 18 | zero-permission guarantee, asserted against the manifest |
| vault | 15 | per-client data scoping (cross-contamination regression) |
| recipes.cycle | 9 | circular sub-recipe detection |
Browser suites run headless in Chrome; module suites run on Node. The smoke suite reads the
app’s real index.html script list instead of keeping its own copy — a harness that
duplicates the app’s wiring stays green while the app is broken, which happened once and
won’t again.
The app requests zero permissions and has no INTERNET permission — the privacy claim is enforced by the OS, not promised by a policy. An earlier USDA nutrition lookup was removed after concluding it couldn’t scale (per-key rate limit shared by all installs) and couldn’t keep its API key secret client-side; the embedded Choi–Okos profiles already carried the same data. No account, no ads, no analytics. Data Safety declaration is trivially true.
window.print() is a no-op in a
WebView) is verified on the emulator but not yet across OEM print stacks.KitchenMetrics è un’app Android per cucine professionali: food cost dei piatti, sicurezza dell’olio di frittura, stima dei tempi di cottura, un database di riferimento di grassi e oli, e un ricettario che ricalcola i costi da solo quando cambia il prezzo di un ingrediente. Cinque strumenti, tre lingue (EN/IT/ES), completamente offline.
Esiste perché cucino di mestiere e nessuna delle app esistenti modella come funziona davvero una cucina — la maggior parte dei calcolatori di food cost, per esempio, applica una singola percentuale di “resa”, quando in realtà le rese sono due e in sequenza (resa di lavorazione AP→EP, poi calo di cottura EP→piatto servito) e confonderle porta sistematicamente a sottoprezzare i piatti.
Stato: su Google Play in closed testing dal 15 luglio 2026
(com.kitchenmetrics.app, targetSdk 36, minSdk 26, AAB ~2,4 MB, Play App Signing).
Il percorso, per contesto: due mesi e mezzo fa questo era un esercizio Python scritto mentre imparavo a programmare. È diventato una web app, poi un’app Android, poi una release firmata su Google Play — costruita da un cuoco in attività, con le AI come tutor e revisori, e ogni formula e limite di legge verificati su fonti pubblicate. Questo documento descrive cosa è uscito dall’altra parte, da sviluppatore a sviluppatore.
@JavascriptInterface per le due sole cose
che una WebView non sa fare: persistenza (SharedPreferences) e stampa. Tutto il resto è
HTML/CSS/JS puro dentro assets/.onclick inline che risolvono nello scope globale; type="module" li romperebbe
tutti in silenzio. Esiste una suite di smoke test pensata proprio per intercettare quella
classe di guasti.core.js): AndroidBridge → SharedPreferences sul
dispositivo, fallback su localStorage nel browser. L’intera app gira identica in Chrome
desktop, che è dove avviene gran parte di sviluppo e test.Il lavoro interessante è nei modelli, e ognuno ha le sue fonti:
Corretto il 19 luglio 2026: questo paragrafo indicava erroneamente un registro di limiti statutari “(24% DE, 25% IT/ES/FR, 27% CH…)”. Germania e Francia non hanno alcuna voce nel registro — non l’hanno mai avuta — e il 24% della Germania è una raccomandazione DGF, non una legge, che è precisamente l’errore contro cui SOURCES.md mette in guardia. Due file nello stesso repository non coincidevano, e quello con l’impostazione più commerciale era quello sbagliato.
Ciò che è insolito qui non sono i limiti, ma la colonna dello stato: i concorrenti citano “soglie EFSA/FDA” che, a verifica fatta, non esistono (nessuno dei due enti fissa un limite TPC).
12.703 asserzioni in trentasei suite, tutte verdi, eseguite dopo ogni modifica (31 luglio 2026). La cifra viene rigenerata da tests/run_all.js anziché essere ricordata a memoria — era andata fuori sincrono due volte prima dell’esistenza di questo runner.
| Suite | Asserzioni | Cosa protegge / Controlli |
|---|---|---|
| i18n | 3.212 | parità delle chiavi, chiavi duplicate (scartate in silenzio dagli object literal JS) |
| db_i18n | 920 | copertura traduzioni del DB grassi + concordanza tra registri |
| tpc_limits | 173 | integrità del registro legale per giurisdizione, inclusa la confidence |
| thermal_geometry | 164 | autovalori 1-D rispetto a Çengel; soluzione prodotto per corpi finiti (identità e limiti, non ancora un esempio pubblicato) |
| smoke | 70 | cablaggio dell’app; verifica che la lista script del harness coincida con index.html |
| acrylamide | 55 | limite di frittura Reg. (UE) 2017/2158 |
| db_contradictions | 68 | flag del modello patogeni; blocca i due custards (creme all’uovo) che devono mantenerli |
| cooking_temps | 50 | registro delle temperature al cuore per giurisdizione |
| load_factor | 90 | c_p di Choi-Okos rispetto ai valori pubblicati di acqua e carne bovina magra |
| cost_engine | 31 | modello di costing + pin di regressione per 6 bug corretti |
| data_integrity | 45 | nessuna riga cita una fonte inesistente; difetti noti circoscritti |
| thermal_radiation | 23 | termine radiativo, solo modalità a calore secco |
| privacy.manifest | 18 | garanzia di zero permessi, verificata rispetto al manifest |
| vault | 15 | isolamento dei dati per cliente (regressione anti-contaminazione) |
| recipes.cycle | 9 | rilevamento di sotto-ricette circolari |
Le suite browser girano headless in Chrome; le suite a moduli su Node. La smoke suite legge la lista script del VERO index.html invece di tenerne una copia propria — un harness che duplica il cablaggio dell’app resta verde mentre l’app è rotta: è successo una volta, non succederà più.
L’app richiede zero permessi e non ha il permesso INTERNET — la promessa di privacy è imposta dal sistema operativo, non dichiarata da una policy. Una precedente lookup nutrizionale USDA è stata rimossa dopo aver concluso che non poteva scalare (rate limit per chiave condiviso da tutte le installazioni) e non poteva tenere segreta la API key lato client; i profili Choi–Okos incorporati contenevano già gli stessi dati. Nessun account, nessuna pubblicità, nessuna analytics. La dichiarazione Data Safety è banalmente vera.
window.print() in una WebView è un
no-op) è verificato sull’emulatore ma non ancora sugli stack di stampa dei vari OEM.