<?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: undersilentskies</title>
    <description>The latest articles on DEV Community by undersilentskies (@undersilentskies).</description>
    <link>https://dev.to/undersilentskies</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%2F4038288%2F10305dc2-ead2-4f02-99a0-1156d655a8a9.png</url>
      <title>DEV Community: undersilentskies</title>
      <link>https://dev.to/undersilentskies</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/undersilentskies"/>
    <language>en</language>
    <item>
      <title>Creating a QuantifiedSelf Dashboard - Part 1</title>
      <dc:creator>undersilentskies</dc:creator>
      <pubDate>Mon, 20 Jul 2026 18:35:22 +0000</pubDate>
      <link>https://dev.to/undersilentskies/creating-a-quantifiedself-dashboard-part-1-32ha</link>
      <guid>https://dev.to/undersilentskies/creating-a-quantifiedself-dashboard-part-1-32ha</guid>
      <description>&lt;p&gt;Hello DEV.to! I've been lurking this site for years but I hadn't felt like I had anything to share until now. You see...&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I've recently realized I have a problem we're all very familiar with: years of disconnected data with no central hub. What brought this to my attention was trying to visualize my health data in one place. I have years of phone step counts, months of data from a previous fitness band, lab reports, etc...&lt;/p&gt;

&lt;p&gt;Most people don't think this is a problem but I would like it normalized.&lt;/p&gt;

&lt;p&gt;Most of us change phones and simply throw away the data we didn't think was important. Once I noticed this problem I started grieving for all the information I lost every time I bought a new phone and only backed up pictures when I could have a very clear historical timeline of my physical activity. And it's not just health data, but music, books, screentime, coding stats, etc. This matters to me more than I was expecting.&lt;/p&gt;

&lt;p&gt;So I came across the quantifiedself community on reddit and let's just say the rest is history. I've spent weeks trying to bring my data from all my sources into one hub, which led me to finally learning SQL properly, to learning about different databases I hadn't heard of, to trying out different visualization apps. All of this hoping to figure out whether there's a correlation between screentime, food intake, exercise, sleep and the chronic pain condition I've been trying to get diagnosed for almost 20 years. I think I'm finally onto something.&lt;/p&gt;

&lt;h2&gt;
  
  
  So far I've tried the following
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using Apple Health as a wearables hub, then syncing to Guava for lab reports and symptoms. this provides no integration for other types of data and ignores my previous wearables' raw data because AH can't pull it.&lt;/li&gt;
&lt;li&gt;Parsing the GDPR exports for my Xiaomi Fit band myself and then adding to AH. Xiaomi exports are notoriously difficult to use.&lt;/li&gt;
&lt;li&gt;Using DuckDB as a final database source. This became more promising, as DuckDB is perfect for taking data from different sources and pulling it into one final database.&lt;/li&gt;
&lt;li&gt;Asking Gemini to parse the Fit data. Gemini isn't very smart but it did successfully help me turn it into a DuckDB table! We're getting somewhere.&lt;/li&gt;
&lt;li&gt;Visualizing it through Grafana. This was the first time I actually felt like I was accomplishing something because I could visualize the data I had pulled with DuckDB. I don't like Grafana though and would rather code the logic and graphics myself.&lt;/li&gt;
&lt;li&gt;Using Datasette with Dogsheep. I'm not too sure what I hoped to achieve with this, but I'm in the process of pulling my deleted Twitter account data through WaybackTweets which I found through a Dogsheep-related site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using Evidence.dev for visualization.&lt;/strong&gt; This is what I've settled for. I'm an Obsidian lover, so the idea of using Markdown to design the dashboard was immediately appealing to me. Furthermore, I think my favorite part about it is how scalable the system is. I can have different connectors from different data sources, use source queries to organize my data and then graph it with ECharts which gives me full control over it. And it uses my beloved DuckDB dialect!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also realized I've been doing everything wrong. I spent weeks trying to convert my data into the format I wanted, molding my final structure around the data I have instead of doing the opposite. I read an O'Reilly book about data engineering a few years ago and it's been in the back of my mind ever since. Isn't it better to create an ideal schema first and then convert your data to fit that? So this is what I'm doing from now on, and I'll be documenting it here to keep myself accountable!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a plan
&lt;/h2&gt;

&lt;p&gt;Before thinking about how I'll be pulling and transforming data, I'd like to take a data-engineering approach. First I'll be designing the system I want to store my data, which means I have to choose what data is important enough to visualize. To keep things simple, I'll only be selecting very basic information to work with for now. But the good thing about this approach is that we can always add more data later. I have my data sources, but I'll be designing the structure first, including creating the charts I'll use to visualize later. This means I'll just generate some mock data to graph, and once I have a solid schema, I'll bring in my own data and figure out how to transform each data source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Health &amp;amp; Fitness
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Guava, Apple Health, MiFit, Honor Health, DailyNomie (for symptom tracking &amp;amp; sleep quality perception)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heart rate (raw)&lt;/li&gt;
&lt;li&gt;Steps (raw)&lt;/li&gt;
&lt;li&gt;Calories burned (raw)&lt;/li&gt;
&lt;li&gt;Sleep data (sleep stages, sleep totals, perceived quality)&lt;/li&gt;
&lt;li&gt;Lab results&lt;/li&gt;
&lt;li&gt;Mood (from nomie)&lt;/li&gt;
&lt;li&gt;Exercise minutes&lt;/li&gt;
&lt;li&gt;Times stood&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Nutrition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Cronometer, Google Health, personal food log I plan on feeding to NotebookLM with the USDA food list to pull nutrient info.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name of meal&lt;/li&gt;
&lt;li&gt;Macros per meal (Calories, fat, protein, carbs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Media consumption
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Spotify, Goodreads, Letterboxd, RateYourMusic&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name of book/song/movie&lt;/li&gt;
&lt;li&gt;Times listened (for songs)&lt;/li&gt;
&lt;li&gt;Most read/listened/watched author/artist/director etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Time tracking / Productivity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Toggl Track, Google Sheets, Google Calendar/Apple Calendar&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date &amp;amp; Time of event&lt;/li&gt;
&lt;li&gt;What I did/What happened&lt;/li&gt;
&lt;li&gt;Category&lt;/li&gt;
&lt;li&gt;Who I did it with&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Study
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Canvas, Google Sheets&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Courses/deep dives I've done&lt;/li&gt;
&lt;li&gt;Grades&lt;/li&gt;
&lt;li&gt;Subjects&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Weather
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sources I'm pulling from:&lt;/strong&gt; Have not decided yet&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'll be working with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weather details for a particular time/date selected&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tables!
&lt;/h2&gt;

&lt;p&gt;The next step is to design the tables we'll be working with. Since I want to start small, I think I'll start working with the Apple Activity trio: calories burned, exercise minutes and times stood. Instead of pulling my data and working with it, I'll design what the table should look like. This is a very important step, because it ensures that once we bring over our own data, we transform the data to work with OUR system, instead of the other way around. I want to keep things simple but still retain granularity.&lt;/p&gt;

&lt;p&gt;Important things to decide here are what time format we'll use, as this must be consistent across all data. Furthermore, do we want to have three tables, one for calories, one for exercise and one for times stood, or one table that fits all of this data? We must keep scalability in mind. Once we bring other factors in such as steps or mood, will we keep it in the same table or create a different one?&lt;/p&gt;

&lt;p&gt;Whatever you choose for this, I think one of the best things about Evidence.dev is that instead of having to worry about how to store data, you can simply design tables to get the data you want. For this reason, I think it'd be more efficient to have separate tables for each value, because although this would usually be extremely inefficient, who wants to  match a giant table against itself? Evidence.dev treats queries as tables, so we can separate queries per data type to graph data more efficiently.&lt;/p&gt;

&lt;p&gt;Hence, for this I'll make three queries with the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calories.sql: (log_date TIMESTAMP, value NUMERIC, unit STRING 'kcal')&lt;/li&gt;
&lt;li&gt;exercise.sql: (log_date TIMESTAMP, value NUMERIC, unit STRING 'min')&lt;/li&gt;
&lt;li&gt;stood.sql: (log_date TIMESTAMP, value NUERIC, unit STRING 'stood')&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don't need to think about how to store the data yet.&lt;/p&gt;

&lt;p&gt;I asked Gemini to generate some data for us to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log_date,metric_type,value,unit
2026-07-20 00:15:00,calories,1.2,kcal
2026-07-20 06:30:00,calories,1.5,kcal
2026-07-20 06:32:00,stood,1.0,stood
2026-07-20 07:15:00,stood,1.0,stood
2026-07-20 08:00:00,calories,12.4,kcal
2026-07-20 08:00:00,exercise,1.0,min
2026-07-20 08:00:00,stood,1.0,stood
2026-07-20 08:01:00,calories,15.1,kcal
2026-07-20 08:01:00,exercise,1.0,min
2026-07-20 08:02:00,calories,14.8,kcal
2026-07-20 08:02:00,exercise,1.0,min
2026-07-20 08:03:00,calories,11.2,kcal
2026-07-20 08:03:00,exercise,1.0,min
2026-07-20 09:05:00,stood,1.0,stood
2026-07-20 10:12:00,stood,1.0,stood
2026-07-20 11:00:00,stood,1.0,stood
2026-07-20 12:30:00,stood,1.0,stood
2026-07-20 12:45:00,calories,2.1,kcal
2026-07-20 14:45:00,stood,1.0,stood
2026-07-20 16:20:00,stood,1.0,stood
2026-07-20 17:05:00,stood,1.0,stood
2026-07-20 18:30:00,calories,8.5,kcal
2026-07-20 18:30:00,exercise,1.0,min
2026-07-20 18:30:00,stood,1.0,stood
2026-07-20 18:31:00,calories,9.1,kcal
2026-07-20 18:31:00,exercise,1.0,min
2026-07-20 19:15:00,stood,1.0,stood
2026-07-20 22:10:00,calories,1.3,kcal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a new data connector in my project called mock_csv. Then I added the data in the source folder.&lt;/p&gt;

&lt;p&gt;Create the following files in a new /queries folder:&lt;/p&gt;

&lt;p&gt;calories.sql&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calories_burned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;mock_csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mock_activity_data&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;metric_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'calories'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;exercise.sql&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exercise_min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;mock_csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mock_activity_data&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;metric_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'exercise'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;stood.sql&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;log_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;stood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;mock_csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mock_activity_data&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;metric_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'stood'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a page on /pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testing"&lt;/span&gt;
&lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;exercise&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../../../queries/exercise.sql&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;calories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../../../queries/calories.sql&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stood&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../../../queries/stood.sql&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="se"&gt;\`\`\`&lt;/span&gt;sql total_exercise
SELECT 
    date_trunc('day', log_date) as day,
    SUM(exercise_min) as total_min 
FROM ${exercise}
GROUP BY 1
ORDER BY 1;
&lt;span class="se"&gt;\`\`\`&lt;/span&gt;

&lt;span class="se"&gt;\`\`\`&lt;/span&gt;sql total_calories
SELECT 
    date_trunc('day', log_date) as day,
    SUM(calories_burned) as total_calories_burned
FROM ${calories}
GROUP BY 1
ORDER BY 1;
&lt;span class="se"&gt;\`\`\`&lt;/span&gt;

&lt;span class="se"&gt;\`\`\`&lt;/span&gt;sql total_times_stood
SELECT 
    date_trunc('day', log_date) as day,
    SUM(stood) as total_times_stood
FROM ${stood}
GROUP BY 1
ORDER BY 1;
&lt;span class="se"&gt;\`\`\`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Remove the backslashes)&lt;/p&gt;

&lt;p&gt;This should load tables with the averages per day. To visualize them in a line chart, we should have one table with the averages per day. Add this to the end of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;sql daily_summary
SELECT &lt;span class="err"&gt;*&lt;/span&gt; 
FROM ${total_exercise}
JOIN ${total_calories} USING (day)
JOIN ${total_times_stood} USING (day)
&lt;span class="se"&gt;\`\`\`&lt;/span&gt;
&amp;lt;LineChart 
    data={daily_summary}
    x=day
    y={['total_exercise_min', 'total_times_stood']}
    y2=total_calories_burned
    yAxisTitle="Stats"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should give you a line chart showing the stats for calories burned, times stood and total exercise in minutes!&lt;/p&gt;

&lt;p&gt;Next post, I'll talk about other charts for visualizing other types of information and begin designing the pipeline to get Apple Health data into the dashboard.&lt;/p&gt;

&lt;p&gt;Here's the github for the project: &lt;a href="https://github.com/undersilentskies/quant-self-dashboard" rel="noopener noreferrer"&gt;https://github.com/undersilentskies/quant-self-dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>quantifiedself</category>
      <category>dataengineering</category>
      <category>duckdb</category>
      <category>evidencedev</category>
    </item>
  </channel>
</rss>
