<?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: Subarna Maity</title>
    <description>The latest articles on DEV Community by Subarna Maity (@dronzer2code).</description>
    <link>https://dev.to/dronzer2code</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%2F3868147%2Fd292e2fd-5c3c-44fe-8f45-335aefbd074a.jpeg</url>
      <title>DEV Community: Subarna Maity</title>
      <link>https://dev.to/dronzer2code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dronzer2code"/>
    <language>en</language>
    <item>
      <title>The System Never Checked If You Slept. Ours Reads Your Pulse First.</title>
      <dc:creator>Subarna Maity</dc:creator>
      <pubDate>Fri, 25 Sep 2026 20:43:24 +0000</pubDate>
      <link>https://dev.to/dronzer2code/the-system-never-checked-if-you-slept-ours-reads-your-pulse-first-2hai</link>
      <guid>https://dev.to/dronzer2code/the-system-never-checked-if-you-slept-ours-reads-your-pulse-first-2hai</guid>
      <description>&lt;h2&gt;
  
  
  Let's Start With Hackathon Scene
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/e3_p4j_nm9E" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Main video is made by the Organizers of Hacktropica this my edited version on it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right now, while you read this, your face is changing colour.&lt;/p&gt;

&lt;p&gt;Not by much. Every heartbeat pushes a pulse of blood into the capillaries under your skin. Haemoglobin soaks up green light a little more than the tissue around it, so your cheeks darken and fade, darken and fade, roughly once a second. The change is far too small for anyone looking at you to notice.&lt;/p&gt;

&lt;p&gt;A phone camera notices it anyway.&lt;/p&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%2F5v86ei28kz6ryl2rn0bc.jpg" 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%2F5v86ei28kz6ryl2rn0bc.jpg" alt="Robust blood pressure measurement from facial videos in diverse environments" width="762" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have never seen it happen. It has happened every second of your life.&lt;/p&gt;

&lt;p&gt;Keep that in mind, because this story runs on three clocks, and they do not run at the same speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLOCK 1  THE SCAN      10 seconds    a face, a front camera, a pulse
CLOCK 2  THE BUILD     36 hours      four people, one repo, one prize
CLOCK 3  THE READING   5 months      one line in the docs we never read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fyjvbs2a3ov3jg8kg8zwn.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%2Fyjvbs2a3ov3jg8kg8zwn.png" alt="Three horizontal bars labelled The Scan (10 seconds), The Build (36 hours) and The Reading (5 months later), the last one quoting Presage's docs: " width="800" height="327"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The first clock is the product. The second is how it got made. The third is the reason this post exists, and it does not show up until near the end. When it does, it changes what the first two mean.&lt;/p&gt;
&lt;h2&gt;
  
  
  The System never checked if you slept
&lt;/h2&gt;

&lt;p&gt;In Solo Leveling, a mysterious System hands Sung Jinwoo the same daily quest every single day. 100 push-ups, 100 sit-ups, 100 squats, a 10 km run. Didn't sleep? The System doesn't care. Nearly died yesterday? The System doesn't care.&lt;/p&gt;

&lt;p&gt;Every fitness app is that System. It asks how you feel, lets you tap a smiley face, and then serves the plan it was always going to serve.&lt;/p&gt;

&lt;p&gt;But your face is already telling the camera how you feel. Heart rate and breathing rate are sitting right there in the colour of your skin.&lt;/p&gt;

&lt;p&gt;So the question is not "what workout should I do today?" It is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the System measured you before it assigned the quest?&lt;/strong&gt;&lt;/p&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%2Fetbxb5lbw4k4g85jpy7y.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%2Fetbxb5lbw4k4g85jpy7y.png" alt="Why Its So Linked With Solo Leveling Anime" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is SoloFit ("Arise. Train. Conquer."), a React Native app that reads heart rate from the phone camera, turns it into a readiness score, and only then asks an LLM to write your week. Our team, Kolkatar Rosogollas, built it in 36 hours at HackTropica 2K26, and it won &lt;strong&gt;MLH: Best Use of Presage SDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is the whole pipeline, with the real numbers from the code:&lt;/p&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%2Fw903kzb7ptbhzf6rjyv6.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%2Fw903kzb7ptbhzf6rjyv6.png" alt="Eight-step pipeline: 10 s video, S3 multipart upload, Presage rPPG, HR and RR, readiness 0-100, 3 bands, Gemini 2.5 Flash, 7 hard rules" width="799" height="510"&gt;&lt;/a&gt;&lt;br&gt;
&lt;code&gt;1 scan -&amp;gt; 2 vitals -&amp;gt; 1 score -&amp;gt; 3 bands -&amp;gt; 7 days -&amp;gt; 7 rules&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One thing up front, because it changes how you read everything below
&lt;/h2&gt;

&lt;p&gt;SoloFit has two ways of getting your vitals, and you need to know which is which before any number in this post means anything.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measured.&lt;/strong&gt; The video goes to Presage, comes back as heart-rate and breathing-rate samples, and the result is tagged &lt;code&gt;source: "presage_api"&lt;/code&gt;. The scan screen shows a green badge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimated.&lt;/strong&gt; If anything fails (no API key, no video file, upload error, a 100-second timeout), the service does not throw. It returns &lt;strong&gt;randomly generated vitals&lt;/strong&gt;: heart rate 65-95, breathing 13-20, tagged &lt;code&gt;mock_no_key&lt;/code&gt;, &lt;code&gt;mock_no_video&lt;/code&gt; or &lt;code&gt;mock_api_fallback&lt;/code&gt;. The scan screen shows an amber badge that says &lt;strong&gt;"Estimated Vitals."&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fallback was a deliberate hackathon decision. The code comment says so plainly: &lt;code&gt;Fallback is expected in some environments; keep this non-error in UI/logs.&lt;/code&gt; A demo that crashes on bad venue Wi-Fi is not a demo.&lt;/p&gt;

&lt;p&gt;What that fallback does &lt;em&gt;downstream&lt;/em&gt; is a different story, and the third clock is where I tell it. For now, remember the amber badge.&lt;/p&gt;

&lt;p&gt;And one line that applies to the whole post: Presage's own documentation says its metrics are &lt;strong&gt;for general wellness and informational purposes only, have not been cleared by the FDA, and may not be used for medical diagnosis or treatment.&lt;/strong&gt; SoloFit is a fitness app. It is not a medical device, and nothing below is medical advice.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLOCK 1 · THE SCAN · 10 SECONDS&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[Daily Quest]&lt;/strong&gt; Look at the camera for ten seconds. Reward: a plan that knows how you slept.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most fitness apps give you a plan built from your goals and your equipment, and the plan is the same on the morning after a full night's sleep as it is the morning after an all-nighter. The README puts it in one line I still like: &lt;em&gt;"Most fitness apps ask the user how they feel. Solofit measures it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The app runs on three beats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measure, don't assume.&lt;/strong&gt; A 3-second countdown, then a 10-second front-camera recording. Presage reads your heart rate and breathing rate from that video.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reason, don't template.&lt;/strong&gt; Those vitals, plus your profile (age, goals, equipment, injuries, diet, allergies, budget, cuisine), become a readiness score and a prompt. Gemini 2.5 Flash writes a 7-day workout and diet plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapt, don't repeat.&lt;/strong&gt; Scan again tomorrow and the plan is rebuilt from tomorrow's body, not yesterday's.&lt;/li&gt;
&lt;/ul&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%2F4za27kyd5mefcyx63tl7.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%2F4za27kyd5mefcyx63tl7.png" alt="Three real SoloFit screens: the vitals scan viewfinder, the dashboard with readiness, and the day 1 workout plan" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every screenshot in this post is the real app, running on web through Playwright with no API keys. That matters later, so remember it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The rest of the app keeps the Solo Leveling &lt;em&gt;mechanics&lt;/em&gt;: quests with XP, a global leaderboard, a community feed, a posture screen that uses MediaPipe, and exercise GIFs so nobody has to guess what a "Bulgarian split squat" is. It doesn't keep the Solo Leveling &lt;em&gt;look&lt;/em&gt;. It had it for part of the weekend, and why it lost it is the best story from the event. That's in Hackathon Experience.&lt;/p&gt;

&lt;p&gt;The stack: React Native on Expo SDK 54, Firebase Auth and Firestore for profiles, the Presage Physiology REST API for vitals, Gemini 2.5 Flash for plans, Groq for fast meal swaps, MongoDB Atlas behind an Express server for the leaderboard and community, and Zustand for state.&lt;/p&gt;
&lt;h3&gt;
  
  
  Three engineering decisions worth stealing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Never trust an LLM's JSON. Validate it against hard rules, repair it, then fall back to code.&lt;/strong&gt;&lt;br&gt;
The LLM does not get the last word on your workout. A validator does (more on the seven rules below). If the plan breaks a rule, the exact violation goes back to the model in a repair prompt. If the repair still breaks a rule, a deterministic plan builder takes over. The user always gets a valid week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Route each call to the model that fits its latency budget.&lt;/strong&gt;&lt;br&gt;
Generating a whole week can take a while, and nobody minds. Swapping one meal while you stand in the kitchen has to feel instant. So meal swaps go to Groq's &lt;code&gt;llama-3.1-8b-instant&lt;/code&gt; in JSON mode at temperature 0.2, and only fall back to Gemini if that fails. Plan generation goes the other way: Gemini first (with a backup key), then Groq's &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt; if Gemini is down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tag every data point with where it came from.&lt;/strong&gt;&lt;br&gt;
Every vitals object carries a &lt;code&gt;source&lt;/code&gt; string. That one field is what lets the UI tell measured data apart from estimated data. It is also what made the problem in the third clock easy to find.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/832SW84q05s" width="710" height="399"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ipsamurai" rel="noopener noreferrer"&gt;
        ipsamurai
      &lt;/a&gt; / &lt;a href="https://github.com/ipsamurai/solofit" rel="noopener noreferrer"&gt;
        solofit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Solofit — Real-Time Adaptive AI Fitness Coach&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;A mobile fitness application that uses live biometric data to generate and continuously adapt personalised workout and diet plans — in real time, for every individual.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#what-is-solofit" rel="noopener noreferrer"&gt;What is Solofit?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#the-problem-we-solve" rel="noopener noreferrer"&gt;The Problem We Solve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#core-technology-stack" rel="noopener noreferrer"&gt;Core Technology Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#presage-smartspectra--the-heart-of-solofit" rel="noopener noreferrer"&gt;Presage SmartSpectra — The Heart of Solofit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#gemini-ai--the-brain-of-solofit" rel="noopener noreferrer"&gt;Gemini AI — The Brain of Solofit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#mongodb--the-social-layer" rel="noopener noreferrer"&gt;MongoDB — The Social Layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#full-architecture-overview" rel="noopener noreferrer"&gt;Full Architecture Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#key-features" rel="noopener noreferrer"&gt;Key Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#challenges--how-we-solved-them" rel="noopener noreferrer"&gt;Challenges &amp;amp; How We Solved Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#business-model--freemium-proposal" rel="noopener noreferrer"&gt;Business Model — Freemium Proposal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ipsamurai/solofit#getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is Solofit?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Solofit is a real-time adaptive AI fitness coach built as a React Native mobile application. It combines clinical-grade biometric analysis, large-language-model reasoning, and a social community layer to deliver hyper-personalised fitness and nutrition plans that update every time a user's physiological state changes.&lt;/p&gt;

&lt;p&gt;Most fitness apps ask the user how they feel. Solofit measures it.&lt;/p&gt;

&lt;p&gt;A 10-second face scan through the phone camera captures…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ipsamurai/solofit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Verify this in 60 seconds, no keys
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The whole Presage pipeline in one function.&lt;/strong&gt; &lt;a href="https://github.com/saviour2/solofit/blob/main/src/services/presageService.js#L223-L278" rel="noopener noreferrer"&gt;&lt;code&gt;presageService.js&lt;/code&gt; L223-L278&lt;/a&gt;. Read the &lt;code&gt;try&lt;/code&gt; block top to bottom: read file, get upload URLs, upload chunks, complete, poll. The &lt;code&gt;catch&lt;/code&gt; at the bottom is the fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The readiness score.&lt;/strong&gt; &lt;a href="https://github.com/saviour2/solofit/blob/main/src/utils/readinessEngine.js#L23-L65" rel="noopener noreferrer"&gt;&lt;code&gt;readinessEngine.js&lt;/code&gt; L23-L65&lt;/a&gt;. Start at 100, subtract. Then look at lines 62-65, which come up again in the third clock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The LLM guardrails.&lt;/strong&gt; &lt;a href="https://github.com/saviour2/solofit/blob/main/src/services/geminiService.js#L40-L128" rel="noopener noreferrer"&gt;&lt;code&gt;geminiService.js&lt;/code&gt; L40-L128&lt;/a&gt; for the validator, and &lt;a href="https://github.com/saviour2/solofit/blob/main/src/services/geminiService.js#L1491-L1603" rel="noopener noreferrer"&gt;L1491-L1603&lt;/a&gt; for the generate, validate, repair, fall back chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why the scan is 10 seconds.&lt;/strong&gt; &lt;a href="https://github.com/saviour2/solofit/blob/main/src/screens/ScanScreen.js#L36" rel="noopener noreferrer"&gt;&lt;code&gt;ScanScreen.js&lt;/code&gt; L36&lt;/a&gt;. One line, one comment. Remember it.&lt;/li&gt;
&lt;/ol&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%2F28mmvpb49ajjnufmyk9n.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%2F28mmvpb49ajjnufmyk9n.png" alt="Onboarding welcome screen, diet plan with meal alternatives, and the XP leaderboard" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Partner Technologies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Presage (the prize)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What rPPG actually is.&lt;/strong&gt; Remote photoplethysmography. A pulse oximeter clipped to your finger shines light through your skin and watches how much comes back as blood surges with each heartbeat. rPPG does the same thing with ambient light and a camera, from a distance. The colour change is tiny and buried in noise from movement, lighting and compression, so the hard part is not the idea. The hard part is pulling a clean signal out of a shaky phone video, and that is the part Presage does for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we used, exactly.&lt;/strong&gt; The prize is called "Best Use of Presage SDK." What SoloFit ships is the &lt;strong&gt;Presage Physiology REST API&lt;/strong&gt; at &lt;code&gt;https://api.physiology.presagetech.com&lt;/code&gt;, called from React Native with plain &lt;code&gt;fetch&lt;/code&gt;, not the on-device native SDK. The header comment in our service says the flow was &lt;em&gt;"reverse-engineered from official Python client v1.6.0."&lt;/em&gt; In other words, Saikat read Presage's Python client, worked out the HTTP calls it makes, and wrote them again in JavaScript, because there was no drop-in React Native package for what we needed. None of the rest of us could have built this part.&lt;/p&gt;

&lt;p&gt;The flow has five steps:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Call&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/upload-url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send the file size and &lt;code&gt;hr_br: { to_process: true }&lt;/code&gt;. Get back a job &lt;code&gt;id&lt;/code&gt;, an &lt;code&gt;upload_id&lt;/code&gt;, and a list of presigned S3 URLs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PUT&lt;/code&gt; to each S3 URL&lt;/td&gt;
&lt;td&gt;Upload the video in 5 MB chunks. Keep each chunk's &lt;code&gt;ETag&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/complete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send the job id, upload id and the &lt;code&gt;{ ETag, PartNumber }&lt;/code&gt; list. Processing starts in the cloud.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /retrieve-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Poll every 2.5 s. &lt;code&gt;201&lt;/code&gt; means still processing, &lt;code&gt;200&lt;/code&gt; means results are ready. Give up after 40 tries (100 s).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Parse&lt;/td&gt;
&lt;td&gt;Response looks like &lt;code&gt;{ hr: { "0.5": 72, "1.0": 74, ... }, rr: { ... } }&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is the heart of it, trimmed from the source:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;upload_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requestUploadUrls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;completeUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uploadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&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;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_POLL_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;POLL_INTERVAL_MS&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/retrieve-data`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&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;parseResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// still processing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Presage processing timed out&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;In plain English: ask Presage where to put the video, put it there in pieces, tell Presage the pieces are all in, then knock on the door every two and a half seconds until the answer is ready or 100 seconds have passed.&lt;/p&gt;

&lt;p&gt;And the parse step, which matters more than it looks:&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;hrVals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="nx"&gt;hr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;hr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hrVals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&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;/&lt;/span&gt; &lt;span class="nx"&gt;hrVals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...same for rr...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;heart_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hr&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;breathing_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stress_level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;inferStress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;presage_api&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;In plain English: take every positive sample in the time series, average them into one number, and label the result as live Presage data. Stress is not measured at all. It is inferred from heart rate alone: above 90 is &lt;code&gt;high&lt;/code&gt;, above 72 is &lt;code&gt;medium&lt;/code&gt;, anything else is &lt;code&gt;low&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hold on to three details from that block: &lt;em&gt;every&lt;/em&gt; sample is averaged, nothing looks at confidence, and there is a &lt;code&gt;?? 15&lt;/code&gt;. All three come back later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Presage made easy.&lt;/strong&gt; The server-side processing. We never touched a colour channel, a bandpass filter or a face tracker. Upload a video, get a heart-rate time series back. For a team with a 36-hour clock, that is the entire reason the feature exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fought back.&lt;/strong&gt; The upload. Presage's API has a 10 MB file limit, and a phone's front camera fills that quickly. The fix is in &lt;a href="https://github.com/saviour2/solofit/blob/main/src/screens/ScanScreen.js#L36" rel="noopener noreferrer"&gt;&lt;code&gt;ScanScreen.js&lt;/code&gt;&lt;/a&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;RECORD_DURATION_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Reduced to keep file under 10MB API limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;quality: "480p"&lt;/code&gt;, &lt;code&gt;mute: true&lt;/code&gt; (rPPG does not need audio), and a &lt;code&gt;maxFileSize&lt;/code&gt; cap of 9 MB. The in-code estimate is 2-4 MB for ten seconds, which fits in a single 5 MB part. The scan had been longer (a leftover comment in the same file still says &lt;code&gt;recording (15s)&lt;/code&gt;). We cut it to ten to make the upload fit.&lt;/p&gt;

&lt;p&gt;That trade looked free at 2 a.m. It is the most important line in this post, and the third clock explains why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini 2.5 Flash, and the guardrails around it
&lt;/h3&gt;

&lt;p&gt;The prompt is heavy. Gemini gets the full user profile, the vitals JSON, the readiness score and band, the list of injuries to work around, the equipment, the allergens (marked &lt;code&gt;STRICT&lt;/code&gt;), and the full exercise catalog with the instruction &lt;em&gt;use ONLY these, EXACT names&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A prompt is a request, though, not a guarantee. So every plan goes through &lt;code&gt;validateAndNormalizeWorkoutPlan()&lt;/code&gt;, which throws on the first violation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Error it throws (verbatim)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Exactly 7 days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workout_plan must contain exactly 7 days.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2-4 exercises per day&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Day 3 must contain 2-4 exercises, got 5.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Only exercises from the catalog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Day 2, exercise 1 uses unknown exercise: "..."&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;No duplicates within a day&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Day 4 has duplicate exercise "..."&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;No exercise on consecutive days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Day 5 repeats "..." from the previous day. Consecutive-day repeats are not allowed.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;At most 2 rest or recovery days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workout_plan has too many recovery/rest days (3). Max allowed is 2.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Enough variety across the week&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workout_plan has low variety: 3 unique exercises, need at least 4.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rule 7 is smarter than it looks. The variety floor is &lt;code&gt;max(4, min(10, availableCount))&lt;/code&gt;, where &lt;code&gt;availableCount&lt;/code&gt; is how many catalog exercises this user can actually do with their equipment and injuries. A bodyweight-only user with a bad knee cannot be asked for ten unique exercises. The comment in the code explains that without the cap, &lt;em&gt;"every plan, including the deterministic fallback, [would] fail validation."&lt;/em&gt; Somebody found that out the hard way.&lt;/p&gt;

&lt;p&gt;The chain around the validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gemini (primary key -&amp;gt; backup key) -&amp;gt; Groq gpt-oss-120b if Gemini fails
  -&amp;gt; parse (with truncated-JSON recovery)
  -&amp;gt; validate
     -&amp;gt; fail: repair prompt containing the exact error -&amp;gt; parse -&amp;gt; validate
        -&amp;gt; fail: deterministic fallback workout -&amp;gt; validate
  -&amp;gt; tune for equipment + injuries -&amp;gt; validate again
  -&amp;gt; diet normalisation -&amp;gt; allergen check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is one generation, one targeted repair, then code. Not a retry loop that hopes for the best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The elimination test.&lt;/strong&gt; Could Gemini be replaced by a template? Try this user: vegan, peanut allergy, resistance bands only, knee injury, high stress this morning. A template needs a branch for every combination of those. The LLM handles the combinatorics. The validator makes sure the combinatorics came back in a shape we can trust. &lt;strong&gt;An LLM without guardrails is just a template with extra steps. The guardrails are the engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Groq
&lt;/h3&gt;

&lt;p&gt;Meal swaps only, and only for speed. &lt;code&gt;llama-3.1-8b-instant&lt;/code&gt;, &lt;code&gt;temperature: 0.2&lt;/code&gt;, JSON mode with a retry without it, 550 max tokens, and Gemini as the fallback. One job, done fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  MongoDB Atlas
&lt;/h3&gt;

&lt;p&gt;The social half of the System. An Express server exposes &lt;code&gt;GET /leaderboard&lt;/code&gt;, sorted &lt;code&gt;{ xp: -1, streak: -1 }&lt;/code&gt; (XP first, streak as the tiebreak), and &lt;code&gt;GET&lt;/code&gt;/&lt;code&gt;POST /community/posts&lt;/code&gt;, sorted newest first, with image upload. If Atlas can't be reached, the server swaps in an in-memory collection seeded with demo users, so the screen never goes blank on stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why none of this is decoration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Why it isn't decoration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Camera vitals&lt;/td&gt;
&lt;td&gt;Presage REST API&lt;/td&gt;
&lt;td&gt;Replace it with a "how do you feel?" slider and you've rebuilt every other fitness app. The scan &lt;em&gt;is&lt;/em&gt; the thesis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan generation&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Flash + Groq fallback&lt;/td&gt;
&lt;td&gt;Combinations of constraints no template covers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan validation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validateAndNormalizeWorkoutPlan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns a probabilistic output into a guaranteed shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meal swap&lt;/td&gt;
&lt;td&gt;Groq &lt;code&gt;llama-3.1-8b-instant&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Latency budget different from plan generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaderboard + community&lt;/td&gt;
&lt;td&gt;MongoDB Atlas + Express&lt;/td&gt;
&lt;td&gt;Quests without a scoreboard are just chores&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  One scan, all the way down
&lt;/h2&gt;

&lt;p&gt;Let's go down through the layers of a single scan. This is a &lt;strong&gt;worked example with plausible values&lt;/strong&gt;, not a saved reading from the event. Every arithmetic step uses the real formula from &lt;a href="https://github.com/saviour2/solofit/blob/main/src/utils/readinessEngine.js" rel="noopener noreferrer"&gt;&lt;code&gt;readinessEngine.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Where it comes from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;10 s, 480p, muted, ~3 MB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ScanScreen.js&lt;/code&gt;, in-code estimate 2-4 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload parts&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3 MB fits in one 5 MB chunk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Job&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;id&lt;/code&gt; from &lt;code&gt;/v1/upload-url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Presage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HR samples&lt;/td&gt;
&lt;td&gt;a time series keyed by seconds&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/retrieve-data&lt;/code&gt;, status &lt;code&gt;200&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Averaged HR&lt;/td&gt;
&lt;td&gt;88 bpm&lt;/td&gt;
&lt;td&gt;mean of every positive sample&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Averaged RR&lt;/td&gt;
&lt;td&gt;20 br/min&lt;/td&gt;
&lt;td&gt;mean of every positive sample&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stress&lt;/td&gt;
&lt;td&gt;&lt;code&gt;medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inferred: 88 &amp;gt; 72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;presage_api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;green badge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readiness&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;68&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;decomposed below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Band&lt;/td&gt;
&lt;td&gt;&lt;code&gt;light workout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;45-74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;7 days, lighter volume&lt;/td&gt;
&lt;td&gt;Gemini, told &lt;code&gt;RECOMMENDATION: light workout&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;passes 7/7&lt;/td&gt;
&lt;td&gt;or repair, or fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now the score by hand, for a 19-year-old with no declared health issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start                                             100
HR 88 &amp;gt; 80        excess 8, min(8 x 2, 30)        -16
HR &amp;gt; 0.7 x (220 - 19) = 140.7 ?   no                0
RR 20 &amp;gt; 18        (20 - 18) x 3                    -6
stress medium                                     -10
health issues     none                              0
                                                  ----
readiness                                          68   -&amp;gt;  "light workout"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcuc55nf4orkxx69loje4.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%2Fcuc55nf4orkxx69loje4.png" alt="Waterfall chart: 100, minus 16 for heart rate, minus 6 for breathing, minus 10 for medium stress, ending at 68 in the light workout band" width="799" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The step to watch is the breathing term. Two breaths per minute of RR moved the score by 6 points. RR is also the number the third clock is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hackathon Experience
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLOCK 2 · THE BUILD · 36 HOURS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;HackTropica 2K26. Asansol Engineering College, West Bengal. April 4-5, 2026. Thirty-six hours.&lt;/p&gt;

&lt;p&gt;The theme was &lt;strong&gt;Nature, and how beautiful it is.&lt;/strong&gt; We built an app that stares at your face. That sounds like the opposite of the brief until you remember the first paragraph of this post: a pulse is the oldest rhythm in nature, and it's been running under your skin your whole life. We just pointed a camera at it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That's the event, not the product. The app demo is up in the Demo section. This is what the 36 hours looked like from outside the laptop.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The crew
&lt;/h3&gt;

&lt;p&gt;We called ourselves &lt;strong&gt;Kolkatar Rosogollas&lt;/strong&gt;, after Kolkata's most famous syrup-soaked sweet, and then spent a weekend building an app that writes people diet plans. I'll let that sit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saikat Das&lt;/strong&gt; built the Presage connection: the upload, the S3 chunks, the polling, the parsing. It was the one piece nobody else on the team could have done, and the whole app stands on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Me (Subarna Maity)&lt;/strong&gt; built the UI of the Expo app, including the redesign you're about to read about, plus the posting features and a steady stream of bug fixes as everything else broke around him.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soumyadeep Dey&lt;/strong&gt; Community posting, the onboarding flow that collects everything the prompt needs (age, goals, equipment, injuries, diet type, allergies, budget, cuisine), and the diet planning feature. So finding #6 further down is in my part of the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sriz Debnath&lt;/strong&gt; helped where he could, and spent much of the rest of the 36 hours on dedicated fieldwork into the food and the swag. He reports that both were excellent. Every team needs one person who remembers there's an event going on outside the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on reading the repo: every commit is under Saikat's account, so the log tells you &lt;em&gt;when&lt;/em&gt; things happened, not &lt;em&gt;who&lt;/em&gt; did them. Treat it as the team's diary, not a scorecard.&lt;/p&gt;

&lt;h3&gt;
  
  
  The costume came off
&lt;/h3&gt;

&lt;p&gt;The biggest challenge of the weekend wasn't rPPG, or S3, or getting JSON out of an LLM. It was a question.&lt;/p&gt;

&lt;p&gt;We had started out building the app to look like the System itself: glowing blue quest windows, notification panels, the whole anime interface, as if the phone were Jinwoo's status screen. We loved it. It was the reason the name was SoloFit.&lt;/p&gt;

&lt;p&gt;Then the judges walked up to our table, looked at it, and asked something like: &lt;em&gt;if a serious fitness person opens this app, will they stay once they see an anime theme?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We didn't have a good answer, because there isn't one. Someone who trains every morning wants their numbers, not a costume. A theme that makes four hackers grin can make the person the app is actually for close it in five seconds. That hurt more than any stack trace that weekend, because it wasn't a bug. It was taste, and the judges were right.&lt;/p&gt;

&lt;p&gt;So we tore it down in the middle of the hackathon and rebuilt it as &lt;strong&gt;minimalist neo-brutalism&lt;/strong&gt;. Every colour is now in &lt;a href="https://github.com/saviour2/solofit/blob/main/src/constants/theme.js" rel="noopener noreferrer"&gt;&lt;code&gt;theme.js&lt;/code&gt;&lt;/a&gt;: a warm grey &lt;code&gt;#E8E8E6&lt;/code&gt; background, near-black &lt;code&gt;#111111&lt;/code&gt; text, one loud orange &lt;code&gt;#FF5A1F&lt;/code&gt; for anything you're meant to press, hard 1.5-2px borders, no glow anywhere. Heart rate, breathing and stress each get a single flat colour of their own. It's quiet enough for a serious lifter and blunt enough to still feel like a System.&lt;/p&gt;

&lt;p&gt;What we kept were the bones: quests, XP, the leaderboard, "Arise." What we threw away was the paint. That rebuild is where &lt;code&gt;Version 2.0.0 : Massive UI overhaul&lt;/code&gt; in the log below comes from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The anime was for us. The app was for them.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The diary
&lt;/h3&gt;

&lt;p&gt;Here's the diary, in IST:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apr 4, 19:10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Solofit: AI-powered adaptive fitness coach app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 4, 19:15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chore: Secure API keys and move Firebase config to environment variables&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 4, 19:18&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Updated gitignore&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;five and a half hours of silence&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apr 5, 00:54&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Presage implementation successful along with custom fallbacks&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 01:00&lt;/td&gt;
&lt;td&gt;&lt;code&gt;feat: dynamic exercise GIFs with chevron dropdown in WorkoutScreen&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 03:05&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Harden env safety and stabilize AI workout generation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 03:23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Version 2.0.0 : Massive UI overhaul&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 06:20&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3.0.0 : Leader board and Community sections live using Mongo DB&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 10:10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2.6.0 : UI changes and AI model fallbacks added&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 5, 12:13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;version 3.0.1 : Minor changes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fde029m22ac2g0i7x9vip.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%2Fde029m22ac2g0i7x9vip.png" alt="Commit timeline from 19:10 to 12:13 with a 5 hour 36 minute gap before the highlighted 00:54 Presage commit" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the gap. Between 19:18 and 00:54 there isn't a single commit. Those were Saikat's five and a half hours, and the commit that ends them is the one the whole project depends on. Our Devfolio "challenges" field sums up those hours in one line: &lt;em&gt;"Implementing presage SDK into React Native App but we nailed It."&lt;/em&gt; The Devfolio stack also lists C++ and CMake, which are not what you reach for when calling a REST API.&lt;/p&gt;

&lt;p&gt;The rest of the log reads like a team that has stopped being scared. Six minutes after Presage lands, someone ships exercise GIFs. By 03:23 the costume is off and the neo-brutalist UI is in. By 06:20 the leaderboard is live on Mongo. At 10:10 the version number goes &lt;em&gt;backwards&lt;/em&gt;, from 3.0.0 to 2.6.0, in a commit that adds AI model fallbacks. That tells you exactly how much sleep was involved.&lt;/p&gt;

&lt;p&gt;Look at the 00:54 commit message again, too. Not "Presage implementation successful." It's &lt;strong&gt;"Presage implementation successful along with custom fallbacks."&lt;/strong&gt; The fallback was born in the same minute as the feature. Hold on to that.&lt;/p&gt;

&lt;p&gt;Then judging, and the prize: &lt;strong&gt;MLH: Best Use of Presage SDK.&lt;/strong&gt; We also applied to the MongoDB and Gemini tracks. We didn't win those. We won the one the whole app was built around.&lt;/p&gt;

&lt;h2&gt;
  
  
  [Warning] What I'd fix before anyone trains on this
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLOCK 3 · THE READING · 5 MONTHS LATER&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[Warning]&lt;/strong&gt; Vitals estimated. Intense quests should be locked. They aren't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Five months later, sitting down to write this post, I opened Presage's current SmartSpectra docs to get one sentence right about breathing rate. This is the sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Confidence is 0 until the full 30-second window is reached.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Our scan is 10 seconds long.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go back through everything above with that in mind. The upload limit that pushed the scan down to ten seconds. The parse step that averages every sample and never checks confidence. The readiness formula where two breaths per minute cost six points. None of it was wrong at 2 a.m. It was just built without the one number that tells you whether the breathing data can be trusted at all.&lt;/p&gt;

&lt;p&gt;These are findings, not apologies. Each one comes with its fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Breathing rate from a 10-second window.&lt;/strong&gt; Presage's docs say breathing-rate confidence stays at 0 until 30 seconds. (Those docs describe the SDK. I haven't confirmed whether the REST endpoint behaves exactly the same.) Either way, a breathing rate from ten seconds of video is a number the provider itself says not to lean on, and our score leans on it.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; On scans under 30 seconds, drop RR from the score completely. When breathing matters, scan for 30 seconds and keep the file under the limit by dropping to a lower resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Averaging blindly.&lt;/strong&gt; &lt;code&gt;parseResults()&lt;/code&gt; averages every positive sample. The docs list what degrades the signal: large head, body or camera motion, chewing gum, flickering light like a TV screen. Pulse is valid for roughly 40-110 BPM.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; If the API returns confidence with each sample, weight by it or throw away low-confidence samples. Either way, clamp HR to the valid range before it gets near a score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A hard-coded breathing rate labelled as live data.&lt;/strong&gt; Look at &lt;code&gt;rr ?? 15&lt;/code&gt; again. If Presage sends back heart rate but no usable breathing rate, the app puts in 15, and the object still says &lt;code&gt;source: "presage_api"&lt;/code&gt;. The green badge lights up for a number nobody measured.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Return &lt;code&gt;breathing_rate: null&lt;/code&gt; and track source per field, not per object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The fallback can unlock an intense workout.&lt;/strong&gt; This is the big one. Here are lines 62-65 of &lt;code&gt;readinessEngine.js&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="c1"&gt;// For estimated/mock vitals, keep readiness randomized strictly within 55-85.&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;isFallbackSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="mi"&gt;85&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;55&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 plain English: if the vitals were made up, make the score up too, somewhere between 55 and 85. But 75 and above maps to &lt;code&gt;"intense workout"&lt;/code&gt;. From 55 to 85 there are 31 possible scores, and 11 of them are 75 or higher. So &lt;strong&gt;on a fallback scan, there's roughly a 35% chance the app prescribes an intense session from vitals that were never measured.&lt;/strong&gt;&lt;/p&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%2Fdtwsu5h03c78wkjxxq2d.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%2Fdtwsu5h03c78wkjxxq2d.png" alt="31 boxes numbered 55 to 85, with 75 to 85 highlighted as intense workout: 11 of 31, about 35%" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't have to imagine this. I ran into it while taking the screenshots for this post. Chromium's fake webcam produced no usable video file, so the app returned &lt;code&gt;mock_no_video&lt;/code&gt;, and the random roll landed on 78:&lt;/p&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%2Foq5yari7896rznik9oxd.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%2Foq5yari7896rznik9oxd.png" alt="Three real screens: Estimated Vitals with readiness 78 and Intense Workout, the dashboard saying READY, and the workout tab reading Readiness 78/100 intense workout with upper body hypertrophy on day 1" width="800" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The amber badge was there, just like it's supposed to be. So were a green 78, a "READY" pill and an upper-body hypertrophy session, all built on a heartbeat nobody measured. It gets worse downstream: the vitals JSON goes into the Gemini prompt under the heading &lt;code&gt;REAL-TIME BODY DATA&lt;/code&gt;. The &lt;code&gt;source&lt;/code&gt; field is inside that JSON, but nothing tells the model what &lt;code&gt;mock_api_fallback&lt;/code&gt; means.&lt;/p&gt;

&lt;p&gt;The fallback was a good decision for a demo. The mistake was letting it go further than the badge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fix:&lt;/em&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFallbackSource&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;readiness_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recommendation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light workout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vitals were estimated, not measured. Intense quests stay locked until a real scan.&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;In plain English: when we don't know, say so, and pick the safe option. Estimated vitals can unlock a light day. They should never unlock a hard one. Tell the model the same thing in the prompt, and make the amber badge impossible to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Two different definitions of "high stress".&lt;/strong&gt; Real vitals call stress high above 90 bpm. The mock generator uses above 85. That's small, but it means the same heart rate gets a different stress label depending on where it came from.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; One &lt;code&gt;inferStress()&lt;/code&gt; function, used by both paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. The allergen check flags instead of blocking.&lt;/strong&gt; &lt;code&gt;validateNoAllergens()&lt;/code&gt; does a substring match on meal names and, on a hit, renames the meal &lt;code&gt;[ALLERGEN WARNING] ...&lt;/code&gt;. It doesn't reject the plan, and it can't catch a peanut-based satay that never uses the word "peanut."&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Send a flagged plan back through the same repair loop the workout validator uses, and match against ingredients, not just names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The magic trick was real. The part we never checked was how long it needs to run.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this unlocks
&lt;/h2&gt;

&lt;p&gt;Solo Leveling was the costume, and the judges already made us take it off once. What was left underneath is one idea: &lt;strong&gt;measure the user before you personalise anything.&lt;/strong&gt; Nearly every "personalised" app personalises from a profile you filled in once. A camera-based vital sign is a profile that updates every morning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Study planners&lt;/strong&gt; that check fatigue before scheduling the hard chapter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus timers&lt;/strong&gt; that stretch the break when your resting heart rate is up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rehab programmes&lt;/strong&gt; that won't progress the load on a day your body says no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift and driver apps&lt;/strong&gt; that ask for thirty seconds before a long night, not a checkbox.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of them gets the same three lessons from this post: tag the source, respect the window, and never let an estimate unlock the risky option.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you measure heart rate from a phone camera in React Native?
&lt;/h3&gt;

&lt;p&gt;Record a short front-camera video with &lt;code&gt;expo-camera&lt;/code&gt; (&lt;code&gt;recordAsync&lt;/code&gt; with &lt;code&gt;quality: "480p"&lt;/code&gt; and &lt;code&gt;mute: true&lt;/code&gt;), then send it to an rPPG service. With the Presage Physiology REST API: &lt;code&gt;POST /v1/upload-url&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; the chunks to the presigned S3 URLs, &lt;code&gt;POST /v1/complete&lt;/code&gt;, then poll &lt;code&gt;POST /retrieve-data&lt;/code&gt; until you get a &lt;code&gt;200&lt;/code&gt;. The response is a heart-rate time series you can summarise.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is rPPG and how accurate is it?
&lt;/h3&gt;

&lt;p&gt;Remote photoplethysmography reads the tiny colour changes in skin caused by blood flow. Presage's docs give a valid pulse range of about 40-110 BPM and list large motion, flickering light and chewing gum as things that degrade it. Its metrics are for general wellness, not FDA cleared, and not for diagnosis.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a Presage scan need for breathing rate?
&lt;/h3&gt;

&lt;p&gt;Presage's SmartSpectra docs say breathing-rate confidence is 0 until a full 30-second window, and HRV statistics need 60 seconds. A 10-second scan is fine for a pulse estimate. It isn't enough to trust breathing rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you stop an LLM from returning an invalid workout plan?
&lt;/h3&gt;

&lt;p&gt;Don't rely on the prompt. Validate the parsed JSON against hard rules in code (for us: 7 days, 2-4 exercises per day, catalog-only names, no duplicates in a day, no consecutive-day repeats, at most 2 rest days, a variety floor). On failure, send the exact error back in a repair prompt. If the repair fails too, build the plan deterministically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my Presage poll keep returning 201?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;201&lt;/code&gt; from &lt;code&gt;/retrieve-data&lt;/code&gt; means the job is still processing, not that it failed. Keep polling. We used a 2.5-second interval and gave up after 40 attempts (100 seconds), which is when &lt;code&gt;Presage processing timed out&lt;/code&gt; fires and the fallback kicks in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team Kolkatar Rosogollas:&lt;/strong&gt; Saikat Das (the Presage pipeline), Subarna Maity (the UI, posting and bug fixes), Soumyadeep Dey (posting, onboarding, diet planning), Sriz Debnath (morale, food reconnaissance, swag).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HackTropica 2K26&lt;/strong&gt; and Asansol Engineering College, for the 36 hours and a Nature theme we stretched as far as it would go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The judges&lt;/strong&gt;, for the question that killed our anime UI. It was the best feedback we got all weekend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLH&lt;/strong&gt;, for the Presage track and the prize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presage&lt;/strong&gt;, for an API that turned "read a pulse from a face" into five HTTP calls, and for docs honest enough to tell us where we went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo Leveling&lt;/strong&gt; by Chugong, for the System we wanted to argue with.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;SoloFit is a general-wellness fitness app. Its vitals are not medical measurements, and nothing in it or in this post replaces advice from a doctor. This post was drafted with AI assistance; the code, the build and the mistakes are ours.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mlhacks</category>
      <category>devchallenge</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Feels Great To be part of this project, its a awesome experince to build this project and completed it on time</title>
      <dc:creator>Subarna Maity</dc:creator>
      <pubDate>Sun, 12 Jul 2026 18:18:31 +0000</pubDate>
      <link>https://dev.to/dronzer2code/feels-great-to-be-part-of-this-project-its-a-awesome-experince-to-build-this-project-and-completed-4kl6</link>
      <guid>https://dev.to/dronzer2code/feels-great-to-be-part-of-this-project-its-a-awesome-experince-to-build-this-project-and-completed-4kl6</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i" class="crayons-story__hidden-navigation-link"&gt;We Taught a Snowflake Warehouse to Judge World Cup Conviction and Write the Verdict Back to Solana&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;DEV Weekend Challenge: Passion Edition Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/soumyadeepdey" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1076361%2F0aa8630b-3826-4103-9ca3-7436013bf933.jpg" alt="soumyadeepdey profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/soumyadeepdey" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Soumyadeep Dey 
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Soumyadeep Dey 
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-4127328" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/soumyadeepdey" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1076361%2F0aa8630b-3826-4103-9ca3-7436013bf933.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Soumyadeep Dey &lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i" id="article-link-4127328"&gt;
          We Taught a Snowflake Warehouse to Judge World Cup Conviction and Write the Verdict Back to Solana
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/weekendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;weekendchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/snowflake"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;snowflake&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/100daysofsolana"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;100daysofsolana&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;32&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/soumyadeepdey/we-taught-a-snowflake-warehouse-to-judge-world-cup-conviction-and-write-the-verdict-back-to-solana-305i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            15 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>blockchain</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
