<?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: Matheus Coelho</title>
    <description>The latest articles on DEV Community by Matheus Coelho (@matheus_coelho_102a025529).</description>
    <link>https://dev.to/matheus_coelho_102a025529</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%2F4185173%2F30582eb1-3b88-446c-8e21-337ef4f7424b.jpg</url>
      <title>DEV Community: Matheus Coelho</title>
      <link>https://dev.to/matheus_coelho_102a025529</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matheus_coelho_102a025529"/>
    <language>en</language>
    <item>
      <title>The fy field in SEC XBRL data doesn't mean what you think</title>
      <dc:creator>Matheus Coelho</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:04:08 +0000</pubDate>
      <link>https://dev.to/matheus_coelho_102a025529/the-fy-field-in-sec-xbrl-data-doesnt-mean-what-you-think-1fef</link>
      <guid>https://dev.to/matheus_coelho_102a025529/the-fy-field-in-sec-xbrl-data-doesnt-mean-what-you-think-1fef</guid>
      <description>&lt;p&gt;The SEC gives you every US public company's financials for free through &lt;code&gt;data.sec.gov&lt;/code&gt;. No key, no signup. It's one of the best free datasets in finance, and it's easy to get wrong.&lt;/p&gt;

&lt;p&gt;Here's Apple's net income from the companyfacts endpoint, filtered the way most people would try first: &lt;code&gt;fy == 2025&lt;/code&gt; and &lt;code&gt;form == "10-K"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;start&lt;/span&gt;        &lt;span class="k"&gt;end&lt;/span&gt;          &lt;span class="k"&gt;value&lt;/span&gt;            &lt;span class="k"&gt;fp&lt;/span&gt;   &lt;span class="k"&gt;frame&lt;/span&gt;
&lt;span class="ld"&gt;2022-09-25&lt;/span&gt;   &lt;span class="ld"&gt;2023-09-30&lt;/span&gt;   &lt;span class="mf"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;995&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;   &lt;span class="k"&gt;FY&lt;/span&gt;   &lt;span class="k"&gt;CY&lt;/span&gt;&lt;span class="mf"&gt;2023&lt;/span&gt;
&lt;span class="ld"&gt;2023-10-01&lt;/span&gt;   &lt;span class="ld"&gt;2024-09-28&lt;/span&gt;   &lt;span class="mf"&gt;93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;736&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;   &lt;span class="k"&gt;FY&lt;/span&gt;   &lt;span class="k"&gt;CY&lt;/span&gt;&lt;span class="mf"&gt;2024&lt;/span&gt;
&lt;span class="ld"&gt;2024-09-29&lt;/span&gt;   &lt;span class="ld"&gt;2025-09-27&lt;/span&gt;   &lt;span class="mf"&gt;112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;000&lt;/span&gt;  &lt;span class="k"&gt;FY&lt;/span&gt;   &lt;span class="k"&gt;CY&lt;/span&gt;&lt;span class="mf"&gt;2025&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rows, three different numbers, all tagged &lt;code&gt;fy: 2025&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fy&lt;/code&gt; is the fiscal year &lt;strong&gt;of the filing&lt;/strong&gt;, not of the number. Apple's FY2025 10-K reports three years of income statement data (2023, 2024 and 2025), and every one of those values gets &lt;code&gt;fy: 2025&lt;/code&gt;. If your code takes the first match, you just reported Apple's 2023 profit as 2025's.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other traps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tags change over time.&lt;/strong&gt; Apple reported revenue as &lt;code&gt;Revenues&lt;/code&gt; until 2018 and as &lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt; after the ASC 606 accounting change. Query one tag and you get a series that stops or starts in the middle. NVIDIA reports capex as &lt;code&gt;PaymentsToAcquireProductiveAssets&lt;/code&gt; instead of the more common &lt;code&gt;PaymentsToAcquirePropertyPlantAndEquipment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fiscal years aren't calendar years.&lt;/strong&gt; Apple's fiscal 2023 ran 371 days (a 53-week year). Microsoft's ends in June, NVIDIA's in late January.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual and quarterly values live side by side.&lt;/strong&gt; A 10-K also contains Q4-only values for some items, and a 10-Q contains year-to-date numbers. They all share the same tag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same period gets restated.&lt;/strong&gt; A value first reported in 2023 can be refiled in 2024 with a correction. You want the latest one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually works
&lt;/h2&gt;

&lt;p&gt;Ignore &lt;code&gt;fy&lt;/code&gt;. For each value, look at the period itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If it has a &lt;code&gt;start&lt;/code&gt;, measure the duration. About 365 days (350 to 380, to cover 52/53-week years) is annual. About 90 days is a quarter. Anything else (year-to-date, 6 or 9 months) gets dropped.&lt;/li&gt;
&lt;li&gt;If it has no &lt;code&gt;start&lt;/code&gt;, it's a balance-sheet snapshot. Classify it by the form that reported it: 10-K means annual, 10-Q means quarterly.&lt;/li&gt;
&lt;li&gt;Key each value by its period &lt;code&gt;end&lt;/code&gt; date and keep the one with the latest &lt;code&gt;filed&lt;/code&gt; date.&lt;/li&gt;
&lt;li&gt;For each metric, try a list of tags in priority order and merge them by period, so a tag switch doesn't leave a hole.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done that way, Apple's last five fiscal years come out right:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fiscal year end&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Net income&lt;/th&gt;
&lt;th&gt;Free cash flow&lt;/th&gt;
&lt;th&gt;Net margin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2025-09-27&lt;/td&gt;
&lt;td&gt;$416.2B&lt;/td&gt;
&lt;td&gt;$112.0B&lt;/td&gt;
&lt;td&gt;$98.8B&lt;/td&gt;
&lt;td&gt;26.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-09-28&lt;/td&gt;
&lt;td&gt;$391.0B&lt;/td&gt;
&lt;td&gt;$93.7B&lt;/td&gt;
&lt;td&gt;$108.8B&lt;/td&gt;
&lt;td&gt;24.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-09-30&lt;/td&gt;
&lt;td&gt;$383.3B&lt;/td&gt;
&lt;td&gt;$97.0B&lt;/td&gt;
&lt;td&gt;$99.6B&lt;/td&gt;
&lt;td&gt;25.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-09-24&lt;/td&gt;
&lt;td&gt;$394.3B&lt;/td&gt;
&lt;td&gt;$99.8B&lt;/td&gt;
&lt;td&gt;$111.4B&lt;/td&gt;
&lt;td&gt;25.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021-09-25&lt;/td&gt;
&lt;td&gt;$365.8B&lt;/td&gt;
&lt;td&gt;$94.7B&lt;/td&gt;
&lt;td&gt;$93.0B&lt;/td&gt;
&lt;td&gt;25.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And NVIDIA's last five years, which make a nicer chart:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fiscal year end&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Net income&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-25&lt;/td&gt;
&lt;td&gt;$215.9B&lt;/td&gt;
&lt;td&gt;$120.1B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025-01-26&lt;/td&gt;
&lt;td&gt;$130.5B&lt;/td&gt;
&lt;td&gt;$72.9B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-01-28&lt;/td&gt;
&lt;td&gt;$60.9B&lt;/td&gt;
&lt;td&gt;$29.8B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-01-29&lt;/td&gt;
&lt;td&gt;$27.0B&lt;/td&gt;
&lt;td&gt;$4.4B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-01-30&lt;/td&gt;
&lt;td&gt;$26.9B&lt;/td&gt;
&lt;td&gt;$9.8B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The short version in Python
&lt;/h2&gt;

&lt;p&gt;If you'd rather not maintain the tag lists and period logic yourself, I packaged all of the above into an Apify Actor: &lt;a href="https://apify.com/ninja.dev/sec-edgar-filings-financials" rel="noopener noreferrer"&gt;SEC EDGAR Filings &amp;amp; Financials&lt;/a&gt;. You pass tickers, it returns normalized annual and quarterly statements plus the company's recent filings with document links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ninja.dev/sec-edgar-filings-financials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSFT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NVDA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxFilingsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# financials only
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;financialYears&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;financialQuarters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;financials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;periodEnd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;netIncome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeCashFlow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each annual or quarterly row has revenue, gross profit, operating income, net income, EPS (basic and diluted), operating cash flow, capex, free cash flow, R&amp;amp;D, assets, liabilities, equity, cash, long-term debt and the three margins.&lt;/p&gt;

&lt;p&gt;It also does full-text search across every filing since 2001 (&lt;code&gt;"fullTextQuery": "\"going concern\""&lt;/code&gt;), which is handy for screening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it yourself
&lt;/h2&gt;

&lt;p&gt;If you build your own pipeline instead, two practical notes from the SEC's side: send a &lt;code&gt;User-Agent&lt;/code&gt; with a contact email, and stay under 10 requests per second. They block clients that don't.&lt;/p&gt;

&lt;p&gt;And whatever you do, don't filter on &lt;code&gt;fy&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
      <category>api</category>
    </item>
    <item>
      <title>I read 800 recent reviews of the top habit tracker apps. Paywalls are the #1 complaint.</title>
      <dc:creator>Matheus Coelho</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:01:15 +0000</pubDate>
      <link>https://dev.to/matheus_coelho_102a025529/i-read-800-recent-reviews-of-the-top-habit-tracker-apps-paywalls-are-the-1-complaint-32ge</link>
      <guid>https://dev.to/matheus_coelho_102a025529/i-read-800-recent-reviews-of-the-top-habit-tracker-apps-paywalls-are-the-1-complaint-32ge</guid>
      <description>&lt;p&gt;Every habit tracker in the top search results has a rating between 4.3 and 4.95 stars. If you're thinking about building one, that looks like a market with no room left.&lt;/p&gt;

&lt;p&gt;The star rating is an all-time average, though. Some of these apps have millions of ratings collected over years. A rough few months barely moves that number, so I wanted to see what people are saying right now.&lt;/p&gt;

&lt;p&gt;I pulled the 100 most recent reviews for the top 4 results for "habit tracker" on both the App Store and Google Play. That's 8 apps and 800 reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Store&lt;/th&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;Store rating&lt;/th&gt;
&lt;th&gt;Avg of last 100 reviews&lt;/th&gt;
&lt;th&gt;1–2 star share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App Store&lt;/td&gt;
&lt;td&gt;Habit Tracker&lt;/td&gt;
&lt;td&gt;4.79&lt;/td&gt;
&lt;td&gt;4.03&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Store&lt;/td&gt;
&lt;td&gt;HabitKit&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;4.48&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Store&lt;/td&gt;
&lt;td&gt;Onrise&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;4.40&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Store&lt;/td&gt;
&lt;td&gt;Finch: Self-Care Pet&lt;/td&gt;
&lt;td&gt;4.95&lt;/td&gt;
&lt;td&gt;4.68&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Play&lt;/td&gt;
&lt;td&gt;Loop Habit Tracker&lt;/td&gt;
&lt;td&gt;4.72&lt;/td&gt;
&lt;td&gt;4.84&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Play&lt;/td&gt;
&lt;td&gt;HabitNow&lt;/td&gt;
&lt;td&gt;4.74&lt;/td&gt;
&lt;td&gt;4.60&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Play&lt;/td&gt;
&lt;td&gt;Disciplined&lt;/td&gt;
&lt;td&gt;4.29&lt;/td&gt;
&lt;td&gt;4.26&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Play&lt;/td&gt;
&lt;td&gt;HabitKit&lt;/td&gt;
&lt;td&gt;4.55&lt;/td&gt;
&lt;td&gt;3.85&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two apps show a real gap. Habit Tracker on iOS sits at 4.79 overall but its recent reviews average 4.03, and more than one in five is a 1 or 2. HabitKit on Android drops from 4.55 to 3.85.&lt;/p&gt;

&lt;p&gt;Loop is the opposite case. It's open source, completely free, and its recent reviews are better than its lifetime average.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the angry reviews are about
&lt;/h2&gt;

&lt;p&gt;Out of 800 reviews, 80 were 1 or 2 stars. I tagged them by topic (a review can hit more than one):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Complaint&lt;/th&gt;
&lt;th&gt;Reviews&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subscription, paywall, paid features&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bugs and crashes&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sync problems or lost data&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Widgets&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reminders and notifications&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ads&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ads barely show up. Money does. A few real examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"paywalled at 3 habits"&lt;/li&gt;
&lt;li&gt;"must pay to track more than 4"&lt;/li&gt;
&lt;li&gt;"i paid for the family premium but it didn't work"&lt;/li&gt;
&lt;li&gt;"i bought the premium version a year ago and when they updated i lost everything"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is people who paid and then lost data or features, or who hit a wall at habit number 3 or 4 and felt tricked. The widget complaints are a smaller version of the same story: someone paid for Pro and the widget still shows "subscribe to pro".&lt;/p&gt;

&lt;h2&gt;
  
  
  So is there room for another habit tracker?
&lt;/h2&gt;

&lt;p&gt;Based on this, I'd look at:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A generous free tier. Unlimited habits for free is the main thing people are mad about losing, and Loop shows it earns goodwill.&lt;/li&gt;
&lt;li&gt;Boring reliability around sync and backups. Losing a 200-day streak is how you get a 1-star review that never gets edited.&lt;/li&gt;
&lt;li&gt;Widgets that work on day one, since they're the main way people interact with these apps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;800 reviews is a small sample, so treat this as a starting point for your own research.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I got the data
&lt;/h2&gt;

&lt;p&gt;I used an Apify Actor I built that scrapes both stores into one format: &lt;a href="https://apify.com/ninja.dev/app-store-google-play" rel="noopener noreferrer"&gt;App Store &amp;amp; Google Play Scraper&lt;/a&gt;. Apple data comes from Apple's official iTunes Search API and review feed; Google Play uses the open-source &lt;code&gt;google-play-scraper&lt;/code&gt; library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ninja.dev/app-store-google-play&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchTerms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;habit tracker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;both&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxSearchResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxReviewsPerApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;apps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewStats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;averageRating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negativeShare&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every app row already includes &lt;code&gt;reviewStats&lt;/code&gt; (average of the fetched reviews, star distribution and share of 1–2 star reviews), so the table above is one loop. For the complaint tagging I used a few regexes over the 1–2 star texts. An LLM would do a nicer job if you want to go deeper.&lt;/p&gt;

&lt;p&gt;Swap "habit tracker" for whatever category you're eyeing and you'll get the same picture for your niche in a couple of minutes.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>python</category>
      <category>datascience</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
