<?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: Ayush S Pangaonkar</title>
    <description>The latest articles on DEV Community by Ayush S Pangaonkar (@ayush_pangaonkar).</description>
    <link>https://dev.to/ayush_pangaonkar</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%2F4161676%2F261e361e-753b-42a8-830b-0ec2b98af58c.jpg</url>
      <title>DEV Community: Ayush S Pangaonkar</title>
      <link>https://dev.to/ayush_pangaonkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ayush_pangaonkar"/>
    <language>en</language>
    <item>
      <title>Data Engineering Log 3/3: Data Storage and Queries</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Fri, 23 Oct 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/data-engineering-log-33-data-storage-and-queries-57lm</link>
      <guid>https://dev.to/ayush_pangaonkar/data-engineering-log-33-data-storage-and-queries-57lm</guid>
      <description>&lt;p&gt;I finished &lt;strong&gt;Data Storage and Queries&lt;/strong&gt;, the third course in the DeepLearning.AI + AWS Data Engineering Professional Certificate, on October 3, 2026. The labs were hands-on on AWS, and I cleared the final exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Course
&lt;/h2&gt;

&lt;p&gt;Data Storage and Queries, DeepLearning.AI and Amazon Web Services, on Coursera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify certificate:&lt;/strong&gt; &lt;a href="https://coursera.org/verify/6AY7BEQK4RBQ" rel="noopener noreferrer"&gt;https://coursera.org/verify/6AY7BEQK4RBQ&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Studied how data is physically stored.&lt;/strong&gt; That covers storage on disk and in memory, then object, block and file storage, and the databases built on top of those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Followed the evolution of storage architectures.&lt;/strong&gt; From data warehouses to data lakes to data lakehouses, comparing the advantages and drawbacks of each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Designed a simple data lake with AWS Glue&lt;/strong&gt;, then &lt;strong&gt;built a data lakehouse with AWS Lake Formation and Apache Iceberg&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looked at how queries work behind the scenes.&lt;/strong&gt; I wrote more advanced SQL, compared query performance on row-oriented versus column-oriented storage, and ran streaming queries with Apache Flink.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;Storage and queries are connected. How data is laid out on disk, row or column, decides how fast a query can run on it, so the storage choice has to be made with the queries in mind.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Data Engineering Log, part 3 of 3, from the DeepLearning.AI + AWS Data Engineering Professional Certificate.&lt;/p&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>aws</category>
      <category>sql</category>
      <category>datalake</category>
    </item>
    <item>
      <title>Data Engineering Log 2/3: Source Systems, Data Ingestion, and Pipelines</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/data-engineering-log-23-source-systems-data-ingestion-and-pipelines-c61</link>
      <guid>https://dev.to/ayush_pangaonkar/data-engineering-log-23-source-systems-data-ingestion-and-pipelines-c61</guid>
      <description>&lt;p&gt;I finished &lt;strong&gt;Source Systems, Data Ingestion, and Pipelines&lt;/strong&gt;, the second course in the DeepLearning.AI + AWS Data Engineering Professional Certificate, on October 3, 2026. The labs were hands-on on AWS, and I cleared the final exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Course
&lt;/h2&gt;

&lt;p&gt;Source Systems, Data Ingestion, and Pipelines, DeepLearning.AI and Amazon Web Services, on Coursera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify certificate:&lt;/strong&gt; &lt;a href="https://coursera.org/verify/FWGU74AERQUM" rel="noopener noreferrer"&gt;https://coursera.org/verify/FWGU74AERQUM&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explored source systems.&lt;/strong&gt; I looked at how different source systems generate and update data, and how to connect to them and troubleshoot common connection issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compared batch and streaming ingestion.&lt;/strong&gt; I identified the use cases and trade-offs of each pattern, and compared the ETL and ELT paradigms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built a batch and a streaming ingestion pipeline on AWS.&lt;/strong&gt; Both pulled data in from source systems, using AWS services for each pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated and orchestrated pipelines.&lt;/strong&gt; The course covers infrastructure as code and pipelines as code, with Apache Airflow as the orchestration tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looked at monitoring and data quality.&lt;/strong&gt; I explored AWS and open source tools for watching both the data systems and the quality of the data moving through them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;Batch versus streaming is a decision driven by the use case, not by which one sounds more modern. ETL versus ELT comes down to where the transformation happens: before the data is loaded, or after it lands in the destination.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Data Engineering Log, part 2 of 3, from the DeepLearning.AI + AWS Data Engineering Professional Certificate.&lt;/p&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>aws</category>
      <category>etl</category>
      <category>airflow</category>
    </item>
    <item>
      <title>Data Engineering Log 1/3: Introduction to Data Engineering</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/data-engineering-log-13-introduction-to-data-engineering-3d4j</link>
      <guid>https://dev.to/ayush_pangaonkar/data-engineering-log-13-introduction-to-data-engineering-3d4j</guid>
      <description>&lt;p&gt;I finished &lt;strong&gt;Introduction to Data Engineering&lt;/strong&gt;, the first course in the DeepLearning.AI + AWS Data Engineering Professional Certificate, on October 2, 2026. The labs were hands-on on AWS, and I cleared the final exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Course
&lt;/h2&gt;

&lt;p&gt;Introduction to Data Engineering, DeepLearning.AI and Amazon Web Services, on Coursera.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify certificate:&lt;/strong&gt; &lt;a href="https://coursera.org/verify/AGUYGXWGDWE7" rel="noopener noreferrer"&gt;https://coursera.org/verify/AGUYGXWGDWE7&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Walked the full data engineering lifecycle.&lt;/strong&gt; Data is generated in source systems, ingested, transformed, stored and then served to downstream consumers. Alongside those stages sit the undercurrents that touch all of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turned stakeholder needs into system requirements.&lt;/strong&gt; The course has you start from what the business actually needs, write that down as requirements, and only then choose tools and technologies. The order matters, because it keeps the tool choice from driving the design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spun up batch and streaming pipelines on AWS.&lt;/strong&gt; The end goal of the course is pipelines that serve product recommendations. Batch processes data collected over a window of time. Streaming processes events as they arrive. Building both side by side made the difference concrete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provisioned infrastructure with Terraform.&lt;/strong&gt; Instead of clicking resources together by hand, the setup is described as code and applied, so the environment can be recreated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;The lifecycle works as a checklist I can run against any pipeline: where does the data come from, how does it get in, where does it live, how is it transformed, and who consumes it.&lt;/p&gt;

&lt;p&gt;I had already built this kind of system before. For a previous project I handled 500GB of data and wrote ETL pipeline scripts that parsed it, cleaned it and loaded it into databases. The course made me realize I already had hands-on practice with this, and the certificate now confirms it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Data Engineering Log, part 1 of 3, from the DeepLearning.AI + AWS Data Engineering Professional Certificate.&lt;/p&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>aws</category>
      <category>terraform</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Three Things I Found When I Revisited My Gender Classification Project</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Sun, 11 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/three-things-i-found-when-i-revisited-my-gender-classification-project-13mb</link>
      <guid>https://dev.to/ayush_pangaonkar/three-things-i-found-when-i-revisited-my-gender-classification-project-13mb</guid>
      <description>&lt;p&gt;I went back to my Gender Classification project, a learning exercise on a small tabular dataset, and found three things worth writing down. (It is a classroom-style dataset of facial and physical measurements, not something I would build a real product on.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The dataset, &lt;code&gt;gender_classification.csv&lt;/code&gt;, has 5,001 rows. The features are &lt;code&gt;long_hair&lt;/code&gt;, &lt;code&gt;forehead_width_cm&lt;/code&gt;, &lt;code&gt;forehead_height_cm&lt;/code&gt;, &lt;code&gt;nose_wide&lt;/code&gt;, &lt;code&gt;nose_long&lt;/code&gt;, &lt;code&gt;lips_thin&lt;/code&gt; and &lt;code&gt;distance_nose_to_lip_long&lt;/code&gt;. The target is Male or Female. My pipeline: drop duplicates, encode the target with &lt;code&gt;LabelEncoder&lt;/code&gt;, scale with &lt;code&gt;MinMaxScaler&lt;/code&gt;, split 80/20 (&lt;code&gt;random_state=42&lt;/code&gt;), then compare Logistic Regression, KNN (k=3) and a Decision Tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A third of the dataset was duplicates
&lt;/h2&gt;

&lt;p&gt;1,768 of the 5,001 rows, about 35%, were duplicates. After dropping them, 3,233 rows remained. The class balance also moved, from an almost even 2,500 / 2,501 to 1,783 Male (55.1%) and 1,450 Female (44.9%). I now run &lt;code&gt;duplicated().sum()&lt;/code&gt; before I trust any dataset's size or balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The simplest model generalized best
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Train acc&lt;/th&gt;
&lt;th&gt;Test acc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Logistic Regression&lt;/td&gt;
&lt;td&gt;95.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KNN (k=3)&lt;/td&gt;
&lt;td&gt;97.0%&lt;/td&gt;
&lt;td&gt;94.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision Tree&lt;/td&gt;
&lt;td&gt;99.8%&lt;/td&gt;
&lt;td&gt;94.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Decision Tree has the biggest train/test gap (99.8% vs 94.4%), the same overfitting pattern from my admissions project. For Logistic Regression, precision and recall are balanced across both classes (F1 of 0.958 for Female, 0.962 for Male).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The feature I expected to matter did not
&lt;/h2&gt;

&lt;p&gt;Looking at the Logistic Regression coefficients on the scaled features:&lt;/p&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;Coefficient magnitude&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nose_wide&lt;/td&gt;
&lt;td&gt;3.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lips_thin&lt;/td&gt;
&lt;td&gt;3.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;distance_nose_to_lip_long&lt;/td&gt;
&lt;td&gt;3.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nose_long&lt;/td&gt;
&lt;td&gt;3.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;forehead_width_cm&lt;/td&gt;
&lt;td&gt;2.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;forehead_height_cm&lt;/td&gt;
&lt;td&gt;1.81&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;long_hair&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I would have guessed hair length mattered most. Its coefficient is 0.07, next to nothing. The facial measurements carry the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two corrections to my old write-up
&lt;/h2&gt;

&lt;p&gt;My original README said the model used "age and height". Neither column exists in this dataset, and I rewrote it to list the real features. I also found a copy-paste bug where the Decision Tree's accuracy print statement shows the KNN variable. It only affects that printed line, not the final comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Accuracy tells you a model works. The coefficients tell you why.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Gender_Classification" rel="noopener noreferrer"&gt;github.com/bluntjudg/Gender_Classification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 7 of 7 in my ML fundamentals revisit. That wraps up the seven basics. Next, I move on to the apps I deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>My Near-Perfect R2 Was Fake: Debugging a Speed and Distance Model</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Sat, 10 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/my-near-perfect-r2-was-fake-debugging-a-speed-and-distance-model-43mn</link>
      <guid>https://dev.to/ayush_pangaonkar/my-near-perfect-r2-was-fake-debugging-a-speed-and-distance-model-43mn</guid>
      <description>&lt;p&gt;The result looked great: R2 of 0.9993 and a correlation of 0.9994 between speed and distance. The problem was that the data had no real relationship in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the script does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Generates 1,000 random speed values (10 to 1,000) and 1,000 random distance values (100 to 10,000) independently with &lt;code&gt;random.randint&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sorts both arrays ascending, separately.&lt;/li&gt;
&lt;li&gt;Pairs them by index into a DataFrame and saves &lt;code&gt;speed_dist.csv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Splits 97/3 into train and test (970 and 30 rows).&lt;/li&gt;
&lt;li&gt;Fits a &lt;code&gt;LinearRegression&lt;/code&gt; of distance on speed and reports R2 on the test set.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I caught it
&lt;/h2&gt;

&lt;p&gt;Speed and distance were generated independently, so by construction there should be no relationship between them. A near-perfect fit should not have been possible. That mismatch made me look at step 2.&lt;/p&gt;

&lt;p&gt;Sorting two unrelated arrays and lining them up by index forces a monotonic pattern. Row 1 gets the smallest speed and the smallest distance, row 1,000 gets the largest of both, and nothing about speed and distance is actually connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the suspicion
&lt;/h2&gt;

&lt;p&gt;I generated the same two arrays with the same seed and skipped the sorting step:&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;Correlation&lt;/th&gt;
&lt;th&gt;Test R2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With sorting (original script)&lt;/td&gt;
&lt;td&gt;0.9994&lt;/td&gt;
&lt;td&gt;0.9993&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Without sorting (random pairing)&lt;/td&gt;
&lt;td&gt;0.0055&lt;/td&gt;
&lt;td&gt;-0.0168&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without the sort, the correlation falls to about zero, and R2 goes negative. A negative R2 means the model does worse than predicting the average distance every time. The high score came entirely from how I assembled the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do differently now
&lt;/h2&gt;

&lt;p&gt;A high R2 or accuracy only means something if you know exactly how the data was built. Since this project, I check how any synthetic or generated data was assembled before I trust a metric computed from it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Speed-distance-model-" rel="noopener noreferrer"&gt;github.com/bluntjudg/Speed-distance-model-&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 6 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;Gender Classification&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>96.5% Accurate Spam Filter, and the 58 Spam Messages It Let Through</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Fri, 09 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/965-accurate-spam-filter-and-the-58-spam-messages-it-let-through-akm</link>
      <guid>https://dev.to/ayush_pangaonkar/965-accurate-spam-filter-and-the-58-spam-messages-it-let-through-akm</guid>
      <description>&lt;p&gt;My spam classifier scores 96.5% accuracy on the test set. That sounds great. The confusion matrix tells a more useful story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The dataset is &lt;code&gt;mail_data.csv&lt;/code&gt;, with 5,572 messages: 4,825 ham (86.6%) and 747 spam (13.4%), no missing values. I mapped labels to 0 (spam) and 1 (ham), split 70/30 with &lt;code&gt;random_state=3&lt;/code&gt;, fit a &lt;code&gt;TfidfVectorizer&lt;/code&gt; (English stopwords removed, vocabulary of 6,896 terms) on the training messages, and trained a &lt;code&gt;LogisticRegression&lt;/code&gt; model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Train accuracy&lt;/td&gt;
&lt;td&gt;96.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test accuracy&lt;/td&gt;
&lt;td&gt;96.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Always predict ham&lt;/td&gt;
&lt;td&gt;86.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Test accuracy beats the always-ham baseline by about ten points, so the model is learning something real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the accuracy hides
&lt;/h2&gt;

&lt;p&gt;On the test set (232 actual spam, 1,440 actual ham):&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;Predicted spam&lt;/th&gt;
&lt;th&gt;Predicted ham&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Actual spam&lt;/td&gt;
&lt;td&gt;174&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual ham&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1,439&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Spam precision is 99.4%. Only one real message was flagged as spam.&lt;/li&gt;
&lt;li&gt;Spam recall is 75.0%. 58 of 232 spam messages got through.&lt;/li&gt;
&lt;li&gt;Ham recall is 99.9%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the filter almost never blocks a real message, but it misses about one spam message in four. For a spam filter, letting spam through is usually the safer failure compared with blocking real mail, but that is a trade-off worth stating plainly instead of only quoting 96.5%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two corrections I had to make
&lt;/h2&gt;

&lt;p&gt;When I went back through this project, my own write-up did not match my code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I had described the project as using Naive Bayes and SVM. The script only trains and evaluates Logistic Regression. There is no Naive Bayes or SVM anywhere in it.&lt;/li&gt;
&lt;li&gt;The notebook builds a &lt;code&gt;clean_message&lt;/code&gt; column with NeatText, but the model is trained on the raw &lt;code&gt;Message&lt;/code&gt; text. The cleaning step had no effect on the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I fixed the README to describe what the code does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;This was the first project where I looked past one accuracy number into a confusion matrix. The model trades recall for precision on the minority class, and that is a measurable choice, not just a score to quote. It also taught me to re-read my own project descriptions against the code before I publish them.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Spam-Classifier-" rel="noopener noreferrer"&gt;github.com/bluntjudg/Spam-Classifier-&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 5 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;Speed Distance Model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Eight Emotions, 34,792 Tweets, and One Label I Don't Fully Trust</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Thu, 08 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/eight-emotions-34792-tweets-and-one-label-i-dont-fully-trust-22cm</link>
      <guid>https://dev.to/ayush_pangaonkar/eight-emotions-34792-tweets-and-one-label-i-dont-fully-trust-22cm</guid>
      <description>&lt;p&gt;One example in my sentiment project still bothers me. The dataset contains the text "I have a feeling i will fail french #fuckfrench", and its label is &lt;strong&gt;joy&lt;/strong&gt;. A human would read that as anxious or frustrated.&lt;/p&gt;

&lt;p&gt;I will come back to that. First, the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;This is not a positive/negative classifier. It predicts one of eight emotions: joy, sadness, fear, anger, surprise, neutral, disgust or shame. The dataset has 34,792 short, mostly Twitter-style texts, and the classes are very uneven:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Emotion&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;joy&lt;/td&gt;
&lt;td&gt;11,045&lt;/td&gt;
&lt;td&gt;31.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sadness&lt;/td&gt;
&lt;td&gt;6,722&lt;/td&gt;
&lt;td&gt;19.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fear&lt;/td&gt;
&lt;td&gt;5,410&lt;/td&gt;
&lt;td&gt;15.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anger&lt;/td&gt;
&lt;td&gt;4,297&lt;/td&gt;
&lt;td&gt;12.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;surprise&lt;/td&gt;
&lt;td&gt;4,062&lt;/td&gt;
&lt;td&gt;11.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;neutral&lt;/td&gt;
&lt;td&gt;2,254&lt;/td&gt;
&lt;td&gt;6.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;disgust&lt;/td&gt;
&lt;td&gt;856&lt;/td&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shame&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clean the text with NeatText (strip user handles and stopwords).&lt;/li&gt;
&lt;li&gt;Vectorize with &lt;code&gt;CountVectorizer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Train a &lt;code&gt;LogisticRegression&lt;/code&gt; model inside a scikit-learn pipeline.&lt;/li&gt;
&lt;li&gt;Compare with a &lt;code&gt;MultinomialNB&lt;/code&gt; baseline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I used a 70/30 split, stratified by emotion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Test accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Logistic Regression&lt;/td&gt;
&lt;td&gt;63.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Naive Bayes&lt;/td&gt;
&lt;td&gt;57.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Always predict "joy"&lt;/td&gt;
&lt;td&gt;31.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Logistic Regression beats the majority baseline by a wide margin. Macro F1 is 0.60 and weighted F1 is 0.63.&lt;/p&gt;

&lt;p&gt;A single accuracy number hides the spread between classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Emotion&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Recall&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;th&gt;Test support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fear&lt;/td&gt;
&lt;td&gt;0.74&lt;/td&gt;
&lt;td&gt;0.68&lt;/td&gt;
&lt;td&gt;0.71&lt;/td&gt;
&lt;td&gt;1,623&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;joy&lt;/td&gt;
&lt;td&gt;0.63&lt;/td&gt;
&lt;td&gt;0.77&lt;/td&gt;
&lt;td&gt;0.69&lt;/td&gt;
&lt;td&gt;3,313&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anger&lt;/td&gt;
&lt;td&gt;0.64&lt;/td&gt;
&lt;td&gt;0.55&lt;/td&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,289&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;surprise&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;0.49&lt;/td&gt;
&lt;td&gt;1,219&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;disgust&lt;/td&gt;
&lt;td&gt;0.54&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;257&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Disgust is the weakest, with a recall of 0.18. Shame scores the highest F1 (0.75), but it has only 44 test examples, so I would not lean on that number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the "joy" label
&lt;/h2&gt;

&lt;p&gt;If the model predicts joy for text like that, it is not necessarily wrong. It may simply have learned the pattern that is in the training labels, flaws included.&lt;/p&gt;

&lt;p&gt;The dataset looks like it was built from hashtag-based labeling, which is a common way to build emotion datasets. I cannot verify how every row was labeled, but a hashtag does not guarantee the emotion in the sentence, so some noise in the labels would not be surprising.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;This was my first time dealing with class imbalance and reading per-class metrics instead of one accuracy figure. It was also the first time a "wrong" prediction sent me to the raw labels instead of the model. Look at your labels before you blame your model.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Text-Sentiment-Analysis-" rel="noopener noreferrer"&gt;github.com/bluntjudg/Text-Sentiment-Analysis-&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 4 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;Spam Classifier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>When My House Price Model Hit 100% Accuracy, I Should Have Been Worried</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Wed, 07 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/when-my-house-price-model-hit-100-accuracy-i-should-have-been-worried-3ec1</link>
      <guid>https://dev.to/ayush_pangaonkar/when-my-house-price-model-hit-100-accuracy-i-should-have-been-worried-3ec1</guid>
      <description>&lt;p&gt;My first script on the Kaggle House Prices data reported 100% training accuracy. I was pleased for about as long as it took to look at my feature list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: the answer was in the inputs
&lt;/h2&gt;

&lt;p&gt;The script was meant to predict &lt;code&gt;SalePrice&lt;/code&gt;, but I had included &lt;code&gt;SalePrice&lt;/code&gt; itself as one of the input columns. The model could simply read the answer off its own inputs. Once the leaked column was gone, the same setup gave a meaningless 52% on held-out data, because I was treating a price as if it were a class label.&lt;/p&gt;

&lt;p&gt;A suspiciously perfect number is usually a sign that something is broken, not that the model is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;The data is the standard Kaggle split: &lt;code&gt;train.csv&lt;/code&gt; with 1,460 rows and 81 columns. I used these features: LotArea, BsmtFinSF1, TotalBsmtSF, GrLivArea, FullBath, HalfBath, BedroomAbvGr, GarageArea and SaleCondition.&lt;/p&gt;

&lt;p&gt;Predicting &lt;code&gt;SalePrice&lt;/code&gt; properly, as regression and without the leak:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;R2&lt;/th&gt;
&lt;th&gt;RMSE&lt;/th&gt;
&lt;th&gt;MAE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decision Tree Regressor&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;$43,830&lt;/td&gt;
&lt;td&gt;$29,724&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest Regressor&lt;/td&gt;
&lt;td&gt;0.84&lt;/td&gt;
&lt;td&gt;$34,656&lt;/td&gt;
&lt;td&gt;$21,784&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mean sale price is $180,921, so the Random Forest's average error of about $21.8k is roughly 12% of the average price. That is a believable result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second lesson: beat the baseline first
&lt;/h2&gt;

&lt;p&gt;My second script classifies &lt;code&gt;SaleCondition&lt;/code&gt;. About 82% of houses sell under "Normal" conditions, so the target is heavily imbalanced.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Train acc&lt;/th&gt;
&lt;th&gt;Test acc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decision Tree&lt;/td&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;70.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest&lt;/td&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;80.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Always guess "Normal"&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;82.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither model beats a rule that always says "Normal". An 80% accurate model sounds good until you learn that guessing the majority class gets you 82%.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do now
&lt;/h2&gt;

&lt;p&gt;Two habits came out of this project. I check my feature list for the target (or anything derived from it) before I train anything. And I compute a naive baseline before I trust any classification score.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/House-Price-Predictions-" rel="noopener noreferrer"&gt;github.com/bluntjudg/House-Price-Predictions-&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 3 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;Text Sentiment Analysis&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>A Book Recommender That Nails Harry Potter and Fumbles Seven Plays</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Tue, 06 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/a-book-recommender-that-nails-harry-potter-and-fumbles-seven-plays-5j3</link>
      <guid>https://dev.to/ayush_pangaonkar/a-book-recommender-that-nails-harry-potter-and-fumbles-seven-plays-5j3</guid>
      <description>&lt;p&gt;Let me start with the good result. I asked my book recommender for books similar to &lt;em&gt;Harry Potter and the Order of the Phoenix&lt;/em&gt;, and it returned:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Recommended title&lt;/th&gt;
&lt;th&gt;Similarity&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;Harry Potter and the Chamber of Secrets&lt;/td&gt;
&lt;td&gt;0.782&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Harry Potter and the Sorcerer's Stone&lt;/td&gt;
&lt;td&gt;0.778&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Harry Potter and the Half-Blood Prince&lt;/td&gt;
&lt;td&gt;0.768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Harry Potter and the Prisoner of Azkaban&lt;/td&gt;
&lt;td&gt;0.765&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Harry Potter Boxed Set, Books 1-5&lt;/td&gt;
&lt;td&gt;0.764&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nice. Now the other one. I asked for books similar to &lt;em&gt;Seven Plays&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Recommended title&lt;/th&gt;
&lt;th&gt;Similarity&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;Buried Child&lt;/td&gt;
&lt;td&gt;0.458&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;See You Around, Sam!&lt;/td&gt;
&lt;td&gt;0.263&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sam Walton: Made In America&lt;/td&gt;
&lt;td&gt;0.246&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second and third results are there because of the name "Sam", not because the books are alike. Same model, very different quality. Here is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The dataset has 11,127 books with &lt;code&gt;bookID&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;authors&lt;/code&gt; and &lt;code&gt;average_rating&lt;/code&gt;, and no missing values.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I build a &lt;code&gt;book_content&lt;/code&gt; field by joining each book's title and authors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TfidfVectorizer&lt;/code&gt; (English stopwords removed) turns that text into vectors. The matrix is 11,127 by 17,937, and only 0.04% of its cells are non-zero.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;linear_kernel&lt;/code&gt; computes cosine similarity between every pair of books, giving an 11,127 by 11,127 matrix, around 124 million scores.&lt;/li&gt;
&lt;li&gt;For a queried title, I return the top 10 most similar books, excluding the book itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simplified sketch of the idea:&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="n"&gt;tfidf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TfidfVectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop_words&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;english&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;book_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;linear_kernel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Harry Potter works and Seven Plays doesn't
&lt;/h2&gt;

&lt;p&gt;The only signal I gave the model is title and author text. A long series with a consistent author and naming pattern shares a lot of words, so it clusters well, with similarities between 0.66 and 0.78. A title with no such pattern leaves the model matching on incidental word overlap.&lt;/p&gt;

&lt;p&gt;This is a content-based recommender, so it relies entirely on item text. There is no genre, no description, and no user ratings in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do next
&lt;/h2&gt;

&lt;p&gt;Add genre or description text to the content string, which gives the vectors something real to compare. If user rating histories were available, collaborative filtering could be layered on top.&lt;/p&gt;

&lt;p&gt;The lesson I took away: a recommender is only as good as the features behind it, and testing the awkward queries tells you more than testing the easy ones.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Book-Recommendation-System-" rel="noopener noreferrer"&gt;github.com/bluntjudg/Book-Recommendation-System-&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 2 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;House Price Predictions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Five Classifiers, One Dataset: What I Learned About Model Choice</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/five-classifiers-one-dataset-what-i-learned-about-model-choice-53cf</link>
      <guid>https://dev.to/ayush_pangaonkar/five-classifiers-one-dataset-what-i-learned-about-model-choice-53cf</guid>
      <description>&lt;p&gt;If you hand five different classifiers the exact same data, how different do the results really look? That was the question behind my Admissions Predictions project, and the answer was more interesting than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The task is binary: given a student's entrance exam score and percentage, predict whether they get admitted.&lt;/p&gt;

&lt;p&gt;The data is synthetic, 1,000 students, with both features drawn uniformly between 0 and 100. I generated the labels myself from a logistic function, then sampled each outcome from a Bernoulli distribution:&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="n"&gt;admission_prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;exp&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="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;X1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;X2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gave 755 admitted and 245 not admitted. I used an 80/20 split (800 train, 200 test) with &lt;code&gt;random_state=42&lt;/code&gt;, and trained all five models on identical splits so the comparison was fair.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Train acc&lt;/th&gt;
&lt;th&gt;Test acc&lt;/th&gt;
&lt;th&gt;F1 (admitted)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Logistic Regression&lt;/td&gt;
&lt;td&gt;94.13%&lt;/td&gt;
&lt;td&gt;92.50%&lt;/td&gt;
&lt;td&gt;95.05%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVM (RBF)&lt;/td&gt;
&lt;td&gt;93.75%&lt;/td&gt;
&lt;td&gt;91.50%&lt;/td&gt;
&lt;td&gt;94.46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision Tree&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;td&gt;90.50%&lt;/td&gt;
&lt;td&gt;93.77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest (100 trees, entropy)&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93.00%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.45%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KNN&lt;/td&gt;
&lt;td&gt;95.50%&lt;/td&gt;
&lt;td&gt;92.00%&lt;/td&gt;
&lt;td&gt;94.81%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Three things I noticed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The decision tree memorized the training set.&lt;/strong&gt; A perfect 100% on train and the lowest score on test, 90.5%. That roughly 9.5 point gap is what overfitting looks like in numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logistic regression held its own.&lt;/strong&gt; It is the simplest model here and still scored 92.5%. Since I built the labels from a logistic function, a logistic model matching the data-generating process makes sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gap between models was small.&lt;/strong&gt; All five finished between 90.5% and 93.0%. The test set has 200 rows, so half a point is a single student. I would not call Random Forest a clear winner over Logistic Regression from this experiment alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it changed for me
&lt;/h2&gt;

&lt;p&gt;This was my first side-by-side model comparison, and it changed how I evaluate everything after it. I no longer trust train accuracy by itself, and I look at precision and recall next to accuracy, especially when the classes are not balanced (here, about 75% of students are admitted).&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/bluntjudg/Admissions-Predictions" rel="noopener noreferrer"&gt;github.com/bluntjudg/Admissions-Predictions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 1 of 7 in my ML fundamentals revisit. Next in the series: &lt;strong&gt;Book Recommendation System&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live projects I built after these basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ats-resume-asp.streamlit.app" rel="noopener noreferrer"&gt;ATS Resume Analyzer&lt;/a&gt;, a Streamlit app that scores resumes against job descriptions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-based-loan-verification-system-asp.streamlit.app" rel="noopener noreferrer"&gt;AI-Based Loan Verification System&lt;/a&gt;, a Streamlit app for automated loan eligibility checks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi"&gt;Subreach&lt;/a&gt;, a two-agent Reddit tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More of my work is on &lt;a href="https://github.com/bluntjudg" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>beginners</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Subreach: Building Two AI Agents for Reddit</title>
      <dc:creator>Ayush S Pangaonkar</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:42:52 +0000</pubDate>
      <link>https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi</link>
      <guid>https://dev.to/ayush_pangaonkar/subreach-building-two-ai-agents-for-reddit-42bi</guid>
      <description>&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%2Fz2c2mp35ph2elsrxp0vs.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%2Fz2c2mp35ph2elsrxp0vs.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;Reddit is full of people describing problems out loud. Founders ask what others are building, site owners ask why Google is ignoring their pages, and small business owners ask for help they cannot find elsewhere. Every one of those threads is a chance to be useful, and for a product, a chance to be found.&lt;/p&gt;

&lt;p&gt;Doing that by hand does not scale. So for my capstone I built Subreach, an automation system that reads Reddit, decides which threads are worth a reply, writes the reply and posts it. The product it promotes is a SaaS tool.&lt;/p&gt;

&lt;p&gt;The interesting part was not the posting. It was deciding how the work should be divided between agents, and what each one is allowed to do. This article walks through that design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof it runs
&lt;/h2&gt;

&lt;p&gt;Both agents running live, combined into one short video.&lt;br&gt;
Refer LinkedIn Article to verify videos&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.linkedin.com/pulse/subreach-building-two-ai-agents-reddit-ayush-s-pangaonkar-hodnf" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4D12AQGwhzUw8a5jcA%2Farticle-cover_image-shrink_720_1280%2FB4DaEHH9OHJMAQ-%2F0%2F1791120889959%3Fe%3D2147483647%26v%3Dbeta%26t%3DniVKSqDU2zJdcc80RRihDghh163kF0ULP6yEjVA8EnE" height="720" class="m-0" width="1280"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.linkedin.com/pulse/subreach-building-two-ai-agents-reddit-ayush-s-pangaonkar-hodnf" rel="noopener noreferrer" class="c-link"&gt;
            Subreach: Building Two AI Agents for Reddit

          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A capstone project in agent design, by Ayush S Pangaonkar Why I built it Reddit is full of people describing problems out loud. Founders ask what others are building, site owners ask why Google is ignoring their pages, and small business owners ask for help they cannot find elsewhere.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.licdn.com%2Faero-v1%2Fsc%2Fh%2Fal2o9zrvru7aqj8e1x2rzsrca" width="64" height="64"&gt;
          linkedin.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The design in one picture
&lt;/h2&gt;

&lt;p&gt;Subreach is two agents that share the same engine but have opposite jobs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent 1, the marketing agent,&lt;/strong&gt; is the only one allowed to mention the product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent 2, the knowledge agent,&lt;/strong&gt; is never allowed to mention it. Its only job is to give useful answers to real SEO problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each agent runs in its own Chrome instance, with its own configuration, its own list of subreddits, its own search queries and its own prompt files. They do not share state, so a change to one cannot break the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline both agents follow
&lt;/h2&gt;

&lt;p&gt;Every agent moves a post through the same stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search.&lt;/strong&gt; Build Reddit search requests from the subreddit list and query templates, sorted by newest, limited to the last day, 25 results per request and up to 2 pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raw storage and duplicate check.&lt;/strong&gt; Save what was fetched, then drop any post that was already commented on, so the same thread is never answered twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI intent filter.&lt;/strong&gt; A language model reads the post against the agent's rules and returns a single word, APPROVE or REJECT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue.&lt;/strong&gt; Approved posts go into a first-in-first-out queue with their URL, subreddit and timestamps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commenting.&lt;/strong&gt; A comment is generated for the next post in the queue, then typed and submitted in a logged-in browser session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracking.&lt;/strong&gt; Every successful comment is recorded, which feeds the duplicate check on the next cycle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Data files are append-only, and every model call is logged to a usage ledger, so nothing happens silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent 1: the marketing agent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Job:&lt;/strong&gt; promote that one product, and nothing else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it looks.&lt;/strong&gt; It watches 30 subreddits and searches each one for five query templates: "what are you building", "saas ideas", "today I built", "looking for feedback" and "side project". These are the phrases that tend to show up in threads where sharing your own project is part of the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it decides.&lt;/strong&gt; The approval step checks every post against a rules file. The rule is deliberately narrow: only approve posts that explicitly invite people to share what they are building, such as a "What are you building?" thread, a build in public thread or a weekly check-in. Founder stories, indirect promotion and plain advice requests are rejected, even when they are on topic. The agent only speaks where self-promotion is welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it writes.&lt;/strong&gt; The comment is generated by gpt-4o-mini from a prompt file. That prompt contains a hard rule for the product link: it cannot end a sentence or be followed by punctuation, and it has to flow straight into a lowercase word, so it reads like part of a sentence rather than a pasted ad. The generator checks that the exact link string is present in the output. If it is missing, it retries once at a higher temperature before giving up on that post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subreddits monitored (30):&lt;/strong&gt; r/StartupAccelerators, r/startups, r/smallbusiness, r/GrowthHacking, r/microsaas, r/micro_saas, r/SaaSSolopreneurs, r/SaaS, r/indiehackers, r/buildinpublic, r/ShowMeYourSaaS, r/indie_startups, r/Entrepreneur, r/EntrepreneurRideAlong, r/SideProject, r/SideHustle, r/Startup_Ideas, r/StartupMarketing, r/GrowthMarketing, r/MarketingAutomation, r/ContentMarketing, r/SEO, r/TechStartups, r/IndieDev, r/DigitalMarketing, r/SaaSMarketing, r/Devpreneur, r/Solopreneur, r/NoCode and r/Automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent 2: the knowledge agent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Job:&lt;/strong&gt; be genuinely useful to people with SEO problems, with no promotion at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it looks.&lt;/strong&gt; It watches 8 subreddits and searches for seven query templates built around real problems: "why is my website not ranking", "google not indexing my site", "sitemap error", "meta description help", "impressions but no clicks", "on page seo help" and "keyword research beginner".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it decides.&lt;/strong&gt; It follows the same pipeline, but its goal is different. It looks for people who are stuck on a concrete SEO issue and can be helped with a real answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it writes.&lt;/strong&gt; Its comment prompt contains an explicit instruction not to promote anything. No product link and no mention of the product, ever, on this track. The writing itself is shaped by a detailed persona file: a 16-item personalization checklist, eight named comment patterns (for example short and direct, or a stack of reasons), a target mix of lengths across a day (roughly 35 percent short, 40 percent medium and 25 percent long) and a list of stock phrases to avoid because they read as machine-written. The aim is that a profile's recent comments read like they came from one consistent person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subreddits monitored (8):&lt;/strong&gt; r/SEO, r/TechSEO, r/Wordpress, r/blogging, r/smallbusiness, r/Entrepreneur, r/digital_marketing and r/webdev.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two agents instead of one
&lt;/h2&gt;

&lt;p&gt;Promotion and help pull in opposite directions. A single agent asked to do both has to balance them inside one prompt, and the balance tends to slide toward whichever instruction is stronger. Splitting by intent made each agent simpler to write, simpler to test and simpler to reason about. The marketing agent has one narrow permission. The knowledge agent has one hard prohibition. Neither can quietly drift into the other's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost-aware model routing
&lt;/h2&gt;

&lt;p&gt;Not every step needs the same model. The filtering step runs on every fetched post and only has to output one word, so it uses gpt-5-nano, set through an environment variable and chosen for cost. Comment generation runs far less often and needs better writing, so it uses gpt-4o-mini. Matching the model to the job kept the running cost down without touching comment quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pacing and reliability
&lt;/h2&gt;

&lt;p&gt;Both agents share the same timing configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A delay of 60 to 120 seconds between fetching a post and commenting on it&lt;/li&gt;
&lt;li&gt;3 to 5 minutes between comments&lt;/li&gt;
&lt;li&gt;A 15 minute break after each full cycle&lt;/li&gt;
&lt;li&gt;A randomized wait between subreddits drawn from four tiers, so the rhythm is not a fixed interval&lt;/li&gt;
&lt;li&gt;Exponential backoff when a subreddit fetch fails, and a longer backoff when Reddit returns a rate limit response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comments are typed into an already logged-in Chrome tab over the Chrome DevTools Protocol using Playwright, with a small per-keystroke delay, and submitted with Ctrl+Enter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Separate agents by intent, not by task. The same pipeline can serve two very different goals when the rules around it are different.&lt;/li&gt;
&lt;li&gt;A cheap model is enough for a yes or no decision. Save the stronger model for output a person will read.&lt;/li&gt;
&lt;li&gt;Small constraints matter. The link rule and the narrow approval rule did more for quality than any amount of general instruction.&lt;/li&gt;
&lt;li&gt;Configuration over code. Subreddits, queries and timing live in config files, so changing the target audience does not mean touching the logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A limitation worth stating
&lt;/h2&gt;

&lt;p&gt;The cost estimate in the filter service applies one flat per-token rate, labelled with older model pricing, to every call. The filter actually runs on gpt-5-nano, which is cheaper, so the logged figure overestimates the real filtering cost. It is a useful ceiling, not an accurate bill, and it is the first thing I would fix next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the project
&lt;/h2&gt;

&lt;p&gt;The full code, including both agents and their configuration, is on GitHub: &lt;a href="https://github.com/bluntjudg/autopilot" rel="noopener noreferrer"&gt;github.com/bluntjudg/autopilot&lt;/a&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>javascript</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
