<?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: Sujal Amati</title>
    <description>The latest articles on DEV Community by Sujal Amati (@sujal5amati).</description>
    <link>https://dev.to/sujal5amati</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png</url>
      <title>DEV Community: Sujal Amati</title>
      <link>https://dev.to/sujal5amati</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sujal5amati"/>
    <language>en</language>
    <item>
      <title>aalas: A privacy-first Job Hunting platform</title>
      <dc:creator>Sujal Amati</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:51:14 +0000</pubDate>
      <link>https://dev.to/sujal5amati/aalas-a-privacy-first-job-hunting-platform-3c59</link>
      <guid>https://dev.to/sujal5amati/aalas-a-privacy-first-job-hunting-platform-3c59</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Aalas 🦥: Let the Job Hunt Run While You Sleep&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sxwwdvo8rp2aygafs9s.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%2F4sxwwdvo8rp2aygafs9s.png" alt=" " width="799" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most boring part of looking for a job, for me, is &lt;strong&gt;looking for a job&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Searching across job listings, checking whether a role matches my experience, tailoring my resume for every application, and writing cover letters can take hours. And after all that effort, there is still no guarantee that I'll even get an interview.&lt;/p&gt;

&lt;p&gt;So, for Hacktoberfest Week 3's &lt;em&gt;Slow and Steady&lt;/em&gt; challenge, I built &lt;strong&gt;Aalas&lt;/strong&gt; — a privacy-first job-hunting platform that does the tedious parts of job searching for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjydpin1l8pzplcev2v8i.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%2Fjydpin1l8pzplcev2v8i.png" alt=" " width="799" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Aalas&lt;/em&gt; means laziness in Hindi. And honestly, why should we spend hours doing repetitive work that can be automated?&lt;/p&gt;

&lt;p&gt;The idea is simple: &lt;strong&gt;let Aalas search for jobs while you sleep, so you can wake up to a fresh list of relevant opportunities waiting to be explored.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what Aalas does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automates job discovery&lt;/strong&gt;: Search for jobs based on your preferred roles, locations, job type, experience level, and salary preferences. Scheduled refreshes keep listings up to date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ranks jobs by relevance&lt;/strong&gt;: Instead of displaying jobs in a random or purely chronological order, Aalas considers your resume, skills, preferences, projects, and additional experience to surface opportunities that are more relevant to you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tailors your resume&lt;/strong&gt;: Upload your existing LaTeX resume (.tex), select a job, and let AI tailor your resume to its description while preserving the original format. Download the resulting PDF and use it for your application.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8xj7fimtm4ljqvjjhcy8.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%2F8xj7fimtm4ljqvjjhcy8.png" alt=" " width="799" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generates cover letters&lt;/strong&gt;: Get a separate, job-specific cover letter without having to start from a blank page every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keeps AI processing local&lt;/strong&gt;: Resume tailoring and cover-letter generation run using a local LLM, so your resume and additional candidate context don't need to be sent to a hosted AI generation API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to automate applying blindly to hundreds of jobs. It's to reduce repetitive work so that job seekers can spend more time preparing for interviews, improving their skills, and applying thoughtfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;💻 GitHub repository: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sujalamati" rel="noopener noreferrer"&gt;
        sujalamati
      &lt;/a&gt; / &lt;a href="https://github.com/sujalamati/aalas" rel="noopener noreferrer"&gt;
        aalas
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Hacktoberfest Week3 Dev Challenge Submission
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Aalas — Job Hunter Backend (FastAPI)&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Backend scaffold for a job discovery tool with PostgreSQL/pgvector hybrid ranking and Ollama-powered resume/cover-letter generation.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Important integration notes&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/services/jobs_provider.py&lt;/code&gt; is a native Python port of the supplied JavaScript implementation. It uses &lt;code&gt;httpx&lt;/code&gt; and BeautifulSoup; the unrelated &lt;code&gt;linkedin-jobs-api&lt;/code&gt; Python package has been removed. It requests LinkedIn guest-job listing HTML, so the endpoint and HTML selectors can change and requests may be rate-limited. This is not an official LinkedIn API; verify applicable terms before deployment and keep request rates conservative.&lt;/li&gt;
&lt;li&gt;Search results do not contain the full description, so the provider makes a second request to each job URL and extracts &lt;code&gt;description&lt;/code&gt; from the page’s schema.org &lt;code&gt;JobPosting&lt;/code&gt; JSON-LD. It falls back to common visible-description selectors. A failed detail-page request does not discard the listing; its description remains empty.&lt;/li&gt;
&lt;li&gt;Check LinkedIn and provider terms, rate limits, privacy obligations, and any applicable API terms before deploying scraping-based ingestion.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sujalamati/aalas" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
The project uses a React frontend and a Python FastAPI backend, with a PostgreSQL-based search layer and local AI inference.
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I wanted Aalas to be more than a wrapper around an AI API. The interesting part was combining job discovery, personalized retrieval, and document generation into one workflow while keeping control over the AI processing.&lt;/p&gt;

&lt;p&gt;Here's the stack behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.FastAPI and React&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend is built with Python and FastAPI. It handles user profiles, job ingestion, search, ranking, and document generation. The React frontend provides the interface for uploading resumes, exploring jobs, and downloading generated documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.TigerData, pgvector, and hybrid search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is one of the core parts of Aalas.&lt;/p&gt;

&lt;p&gt;A keyword-only search can miss a job described using different terminology from the candidate's resume. On the other hand, semantic search alone may retrieve conceptually similar jobs that don't contain important role-specific keywords.&lt;/p&gt;

&lt;p&gt;To combine the strengths of both approaches, Aalas uses &lt;strong&gt;hybrid search with Reciprocal Rank Fusion (RRF).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The search pipeline has two retrieval methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BM25 keyword search&lt;/strong&gt;: Uses TigerData's pg_textsearch extension to retrieve jobs based on textual relevance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector search&lt;/strong&gt;: Uses pgvector embeddings to compare the user's profile and resume context with job listings based on semantic similarity.
The two ranked lists are then combined using Reciprocal Rank Fusion:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farj5q0esdu4g50agc30d.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%2Farj5q0esdu4g50agc30d.png" alt=" " width="278" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, (r_{\text{BM25}}(d)) and (r_{\text{vector}}(d)) are the ranks assigned to a job by the two retrieval methods, and (k) is a smoothing constant.&lt;/p&gt;

&lt;p&gt;Rather than directly adding BM25 and cosine-similarity scores, RRF combines their rankings. This helps Aalas prioritize jobs that rank well across both methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TigerData is the partner tool I use for the PostgreSQL search layer&lt;/strong&gt;, including the pgvector-based embedding retrieval and pg_textsearch-based BM25 search. During development, the database runs in Docker with persistent storage, making it easier to test the complete search workflow locally.&lt;/p&gt;

&lt;p&gt;One additional detail: salary is not treated as a mandatory field. When a listing doesn't specify salary, it can still appear in the results instead of being discarded simply because that information is missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.Gemma 4 through Ollama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the AI features, I use gemma4:e4b through Ollama for local inference.&lt;/p&gt;

&lt;p&gt;Resume tailoring and cover-letter generation use separate model calls and carefully designed prompts.&lt;/p&gt;

&lt;p&gt;For resume tailoring, the model receives the original LaTeX resume, the job description, and additional candidate context such as projects or experience that may not be included in the original resume. It returns a complete LaTeX document that aims to preserve the original template while emphasizing relevant, supported experience.&lt;/p&gt;

&lt;p&gt;For cover letters, the model uses the same candidate information and the selected job description to generate a separate letter tailored to that opportunity.&lt;/p&gt;

&lt;p&gt;The prompts explicitly instruct the model not to invent skills, qualifications, achievements, or experience. A job description tells the model what the employer is looking for; it is not evidence that the candidate possesses those qualifications.&lt;/p&gt;

&lt;p&gt;The generated LaTeX is compiled into a PDF that the user can download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.Job descriptions and scheduled discovery&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The initial job listing response doesn't always include a full description, so Aalas makes an additional request to the job's detail page when needed and extracts the description for ranking and document generation.&lt;/p&gt;

&lt;p&gt;Job discovery can also run on a schedule, including a noon and midnight refresh, so that new listings can be available without the user having to initiate every search manually.&lt;/p&gt;

&lt;p&gt;The job ingestion layer uses public LinkedIn job pages through a custom Python implementation, rather than assuming that a similarly named Python package has the same API as the original JavaScript library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A resume is personal. It contains a person's education, work history, contact information, projects, and sometimes much more.&lt;/p&gt;

&lt;p&gt;I didn't want the core AI workflow of a job-hunting application to require sending all that information to a hosted AI API.&lt;/p&gt;

&lt;p&gt;Using an open-weight model through local inference gives me more control over where document generation happens. Combined with a locally running database during development, it lets me experiment with the complete workflow without making a cloud AI service a mandatory part of every resume-tailoring request.&lt;/p&gt;

&lt;p&gt;Open tools also make the system easier to inspect and improve. I can change the embedding model, adjust the hybrid search algorithm, refine the prompts, or replace the job ingestion provider without rebuilding the entire application around a single closed platform.&lt;/p&gt;

&lt;p&gt;For me, open innovation is about more than access to a model's weights. It's about having the freedom to understand, modify, and improve the whole system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TigerData&lt;/strong&gt;: Partner tool used for the PostgreSQL search layer, including pgvector embeddings and pg_textsearch-based BM25 retrieval.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>SoloOps AI: An Open-Weight AI Expense Agent for Freelancers</title>
      <dc:creator>Sujal Amati</dc:creator>
      <pubDate>Mon, 19 Oct 2026 06:36:15 +0000</pubDate>
      <link>https://dev.to/sujal5amati/soloops-ai-an-open-weight-ai-expense-agent-for-freelancers-4n6k</link>
      <guid>https://dev.to/sujal5amati/soloops-ai-an-open-weight-ai-expense-agent-for-freelancers-4n6k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SoloOps AI: An Open-Weight AI Expense Agent for Freelancers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkctdyl4m7cf67gygw2k.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%2Fpkctdyl4m7cf67gygw2k.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Freelancers and solo founders shouldn't have to spend hours manually organizing invoices and receipts.&lt;/p&gt;

&lt;p&gt;I built SoloOps AI, an expense-management assistant that turns receipt images and invoice PDFs into structured expense records that users can review, correct, approve, and export to CSV.&lt;/p&gt;

&lt;p&gt;The goal is simple: automate repetitive bookkeeping tasks while keeping people in control of their financial data.&lt;/p&gt;

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

&lt;p&gt;SoloOps AI is a local-first expense management application designed for freelancers, independent consultants, and small business owners.&lt;/p&gt;

&lt;p&gt;The typical workflow involves collecting receipts from different sources, extracting the relevant information, entering it into a spreadsheet, and checking that the amounts are correct. This is repetitive work, and mistakes can easily slip through.&lt;/p&gt;

&lt;p&gt;SoloOps AI automates much of this workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it can do:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Upload receipt images and invoice PDFs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract document text using OCR.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use an open-weight language model to identify merchants, dates, currencies, subtotals, taxes, totals, and expense categories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flag missing fields, suspicious totals, and possible duplicate expenses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let users edit extracted information and approve records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Display expense summaries in a dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Export approved expenses to CSV.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intended user is someone who wants to spend less time entering financial data and more time running their business.&lt;/p&gt;

&lt;p&gt;I designed the application around an important principle: &lt;strong&gt;AI should assist with data entry, not silently make financial decisions&lt;/strong&gt;. Every extracted record can be reviewed and corrected before approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Demo video: &lt;a href="https://youtu.be/nOJrkddMuHQ" rel="noopener noreferrer"&gt;https://youtu.be/nOJrkddMuHQ&lt;/a&gt;&lt;br&gt;
The demo walks through the complete workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Uploading a receipt or invoice.&lt;br&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%2Fcgy613ic4jl4c8xjqy9u.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%2Fcgy613ic4jl4c8xjqy9u.png" alt="Upload documents" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extracting structured expense information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reviewing fields that need attention.&lt;br&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%2F1dhlgh82il4xpev6crnj.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%2F1dhlgh82il4xpev6crnj.png" alt="Review expenses" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Correcting and approving an expense.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exporting the approved expense ledger to CSV.&lt;br&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%2Fg6zm1wdd2wow0stjyhdq.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%2Fg6zm1wdd2wow0stjyhdq.png" alt="CSV export" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub repository: &lt;a href="https://github.com/sujalamati/soloops-ai" rel="noopener noreferrer"&gt;https://github.com/sujalamati/soloops-ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is built around a React frontend and a Python backend, with the AI extraction pipeline separated from the validation and approval logic.&lt;/p&gt;

&lt;p&gt;The repository includes the application source code, setup instructions, configuration templates, and testing code.&lt;/p&gt;

&lt;p&gt;I'd love feedback on the architecture, extraction reliability, and ways to make the workflow more useful for freelancers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I used a combination of familiar web technologies and open-weight AI tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend — React, Vite, and CSS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend provides the dashboard, receipt upload interface, expense table, editing controls, approval actions, and CSV export.&lt;/p&gt;

&lt;p&gt;I wanted to build a usable business application rather than just a chat interface wrapped around a language model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend — Python and FastAPI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FastAPI handles document uploads, extraction requests, expense records, review actions, and exports. SQLite stores the structured expense data.&lt;/p&gt;

&lt;p&gt;The backend exposes individual endpoints for uploading documents, listing expenses, editing records, approving expenses, deleting records, and exporting approved expenses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR — Tesseract and PyMuPDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Digital PDFs can contain extractable text, while scanned documents and images require OCR.&lt;/p&gt;

&lt;p&gt;I use PyMuPDF to extract text from digital PDFs and Tesseract to recognize text in scanned documents and receipt images.&lt;/p&gt;

&lt;p&gt;OCR is imperfect. It can reorder lines, confuse characters, and separate amounts from their labels. This makes reliable downstream extraction and validation particularly important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-weight AI — Ollama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ollama runs the language model used to extract structured expense information from OCR text.&lt;/p&gt;

&lt;p&gt;The model receives the document text and a schema describing the expected output. The goal is to return predictable fields rather than an unstructured response.&lt;/p&gt;

&lt;p&gt;I started with Qwen2.5 3B and experimented with structured output, prompting, and schema validation. Smaller models can struggle with noisy documents or omit fields, so model selection is an empirical decision rather than simply choosing the largest available model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation — Python and Pydantic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model's output is validated against a Pydantic schema. Additional Python checks flag missing values, suspicious totals, and possible duplicate records.&lt;/p&gt;

&lt;p&gt;For example, an invoice might contain line-item amounts that don't agree with its stated subtotal. Valid JSON alone cannot catch that discrepancy.&lt;/p&gt;

&lt;p&gt;This is why I separated the workflow into distinct stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR → AI extraction → deterministic validation → human review → approval → export.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model extracts information, but the application controls what gets approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For an application that handles invoices and receipts, open-weight AI offers practical benefits beyond experimentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. More control over financial data&lt;/strong&gt;&lt;br&gt;
When SoloOps AI runs locally, documents can remain on the user's machine instead of being sent to a third-party model API.&lt;br&gt;
This matters because invoices and receipts can contain business details, addresses, transaction amounts, and other sensitive information.&lt;br&gt;
A hosted deployment has different privacy implications, so local processing and hosted processing need to be treated separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Freedom to change models&lt;/strong&gt;&lt;br&gt;
The extraction model is not hardwired to a proprietary AI provider. I can compare different open-weight models, experiment with quantization, and choose a model based on accuracy, latency, and hardware requirements.&lt;br&gt;
This makes it possible to optimize for the actual workload instead of accepting a single provider's trade-offs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Control over inference costs&lt;/strong&gt;&lt;br&gt;
Local inference can eliminate per-request API charges, although it still consumes computing resources, electricity, and storage.&lt;br&gt;
For a product processing many small documents, understanding the actual cost per document is important to determining whether the business model makes sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Inspectable and replaceable components&lt;/strong&gt;&lt;br&gt;
The extraction pipeline, validation rules, storage, and approval process are separate components. I can improve one part without redesigning the entire application.&lt;br&gt;
Open innovation does not automatically make a model more accurate or secure. It gives developers more flexibility to test, inspect, adapt, and improve the system.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
