<?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: Shahrukh </title>
    <description>The latest articles on DEV Community by Shahrukh  (@shahrukhaidev).</description>
    <link>https://dev.to/shahrukhaidev</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%2F4012361%2Fb8743d8d-2fcb-4bb4-b225-129e7fbb7167.png</url>
      <title>DEV Community: Shahrukh </title>
      <link>https://dev.to/shahrukhaidev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shahrukhaidev"/>
    <language>en</language>
    <item>
      <title># What Happens When You Try to Build a Lawyer for Someone Who Can't Afford One?</title>
      <dc:creator>Shahrukh </dc:creator>
      <pubDate>Sat, 04 Jul 2026 00:40:11 +0000</pubDate>
      <link>https://dev.to/shahrukhaidev/-what-happens-when-you-try-to-build-a-lawyer-for-someone-who-cant-afford-one-15ma</link>
      <guid>https://dev.to/shahrukhaidev/-what-happens-when-you-try-to-build-a-lawyer-for-someone-who-cant-afford-one-15ma</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Wouldn't Leave Me Alone
&lt;/h2&gt;

&lt;p&gt;Pakistan has 220 million people. A functioning legal system. Hundreds of Acts, ordinances, and constitutional provisions that technically protect every citizen.&lt;/p&gt;

&lt;p&gt;Almost nobody can use them.&lt;/p&gt;

&lt;p&gt;The median lawyer's consultation fee in Karachi is more than what many families earn in a week. Legal aid is understaffed and geographically concentrated in major cities. And the laws themselves? Written in English — a language most of the population reads functionally at best, and doesn't speak at home at all.&lt;/p&gt;

&lt;p&gt;So when a landlord illegally locks someone out. When a factory worker gets fired without severance. When a woman wants to know her inheritance rights. When a tenant needs to understand what "Section 16 of the Rent Restriction Ordinance" actually means for their specific situation — they either find a lawyer they can't afford, ask someone who doesn't really know, or quietly give up.&lt;/p&gt;

&lt;p&gt;This isn't a knowledge problem. It's an access problem.&lt;/p&gt;

&lt;p&gt;I'm a CS student at Sukkur IBA University in interior Sindh — not Karachi, not Islamabad. The kind of city where you feel the gap between what the law says and what people actually know it says every single day. That gap is where HAQ started.&lt;/p&gt;

&lt;p&gt;HAQ is an Arabic and Urdu word. It means &lt;em&gt;right&lt;/em&gt; — as in, what is rightfully yours.&lt;/p&gt;

&lt;p&gt;The name felt important.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Idea: Ask the Law, Get the Law
&lt;/h2&gt;

&lt;p&gt;There's a specific failure mode with AI and legal questions that drove every design decision I made, and it's worth naming clearly.&lt;/p&gt;

&lt;p&gt;Standard LLMs — any of them — will answer legal questions confidently. They'll cite "Section 144" or "the Transfer of Property Act" with total authority. They are often wrong. Sometimes subtly: the section exists but doesn't say what the model claims. Sometimes obviously: the Act doesn't apply in that province. Always uncitable: the user has no way to verify without finding the source themselves.&lt;/p&gt;

&lt;p&gt;For an accessibility tool, a confidently wrong answer isn't neutral. It's actively dangerous. Someone might not challenge their landlord, file the wrong petition, or miss a critical deadline because an AI answered with a tone that didn't invite doubt.&lt;/p&gt;

&lt;p&gt;The insight behind HAQ: &lt;strong&gt;don't let the model make legal claims it can't back up with a document you retrieved yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RAG — Retrieval-Augmented Generation — is the technical name. The idea is: before the model speaks, find the relevant passages from actual Pakistani legislation. Feed those passages to the model. Tell the model it can only answer using what it was given. Link directly to the source.&lt;/p&gt;

&lt;p&gt;If it can't find a relevant passage? Say so. Don't hallucinate.&lt;/p&gt;

&lt;p&gt;That's the whole system. Everything else is engineering to make it work at scale, in Urdu, for Pakistani law specifically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: The Data Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Building an AI on Pakistani law first requires having Pakistani law.&lt;/p&gt;

&lt;p&gt;This sounds obvious. It was not simple.&lt;/p&gt;

&lt;p&gt;Pakistan's legal documents are scattered across government portals of varying quality — sometimes available as scanned PDFs, sometimes as HTML tables with broken encoding, sometimes simply not publicly available in machine-readable form at all. The Federal Legislative List, provincial Acts, ordinances — each comes from a different source with different formatting conventions and different levels of digitization.&lt;/p&gt;

&lt;p&gt;I ended up with 100+ Acts. The pipeline: download, extract text, clean it, chunk it into pieces an embedding model could meaningfully index.&lt;/p&gt;

&lt;p&gt;Chunking strategy matters more than it sounds. Split too coarsely and a single chunk contains an entire Act — retrieval becomes a lottery. Split too finely and a clause is torn from its context — the model retrieves words without meaning. I settled on section-level chunking: each chunk is one section of one Act, plus metadata (Act name, section number, jurisdiction, source URL).&lt;/p&gt;

&lt;p&gt;4,000+ chunks. Source link back to the original legislative document for every single one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Embedding Nightmare: From 10 Hours to 8 Minutes
&lt;/h2&gt;

&lt;p&gt;The first time I ran the full embedding pipeline, it took 10 hours and 23 minutes.&lt;/p&gt;

&lt;p&gt;This is the part of ML projects nobody tweets about. You write the code, it looks correct, you kick it off, and you go to sleep because it will still be running when you wake up.&lt;/p&gt;

&lt;p&gt;The problem was sequential API calls. For each chunk, the pipeline would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send chunk text to Cohere's embedding API&lt;/li&gt;
&lt;li&gt;Wait for response&lt;/li&gt;
&lt;li&gt;Store the vector in Pinecone&lt;/li&gt;
&lt;li&gt;Move to the next chunk&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each round trip takes roughly 200ms. Four thousand chunks × 200ms = around 800 seconds in pure API wait time, before you account for network variance, rate limiting, and Pinecone write latency. In practice: over ten hours.&lt;/p&gt;

&lt;p&gt;The fix was embarrassingly obvious in hindsight:&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="c1"&gt;# Before: sequential, one chunk at a time
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cohere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pinecone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="c1"&gt;# After: batched — the way the APIs were designed to be used
&lt;/span&gt;&lt;span class="n"&gt;COHERE_BATCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;
&lt;span class="n"&gt;PINECONE_BATCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;COHERE_BATCH&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;COHERE_BATCH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cohere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# one API call, 96 texts
&lt;/span&gt;
    &lt;span class="n"&gt;vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&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;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&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;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;PINECONE_BATCH&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pinecone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;PINECONE_BATCH&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New runtime: &lt;strong&gt;7 minutes and 43 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same 4,000+ chunks. Same vectors. Same output. 87× faster — just by respecting the batch interfaces that were there all along.&lt;/p&gt;

&lt;p&gt;If you're building anything with embedding pipelines: batch from day one. Sequential processing is only acceptable for five-chunk demos.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hallucination Problem, Specifically
&lt;/h2&gt;

&lt;p&gt;RAG reduces hallucination. It doesn't eliminate it.&lt;/p&gt;

&lt;p&gt;Language models are trained to be helpful, which means they're trained to complete answers even when they should say "I don't know." Give a model three retrieved chunks and ask a question those chunks don't quite answer — it will often bridge the gap with something plausible-sounding that came from training weights, not from what you gave it.&lt;/p&gt;

&lt;p&gt;For general Q&amp;amp;A, that's annoying. For legal advice, it's a liability.&lt;/p&gt;

&lt;p&gt;I spent more time on the system prompt than on any other single piece of code. The key constraints ended up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are HAQ, a Pakistani legal assistant.

RULES YOU CANNOT BREAK:
1. You may only cite law that appears in the RETRIEVED CONTEXT below.
2. If the context does not contain a clear answer, respond with:
   "I couldn't find a clear answer in the available legislation.
    Please consult a qualified lawyer for this situation."
3. Every legal claim must end with a citation: [Act Name, Section X]
4. Never speculate about intent, enforcement, or likely court outcomes.
5. If the user's province is unclear, ask before answering.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule 2 was the hardest to nail. Models resist admitting ignorance — you have to make the refusal feel like a legitimate, well-formed response rather than a malfunction. The phrasing &lt;em&gt;"I couldn't find a clear answer in the available legislation"&lt;/em&gt; was tested against probably thirty variations before it consistently produced the behavior I wanted without the model trying to work around it.&lt;/p&gt;

&lt;p&gt;The citation layer closes the loop. Every retrieved chunk carries a source URL. When HAQ cites &lt;code&gt;[Rent Restriction Ordinance 2001, Section 8]&lt;/code&gt;, the UI renders that as a live link to the actual government document. The user can verify. The model can't hide behind vague authority.&lt;/p&gt;

&lt;p&gt;Temperature: 0.1. Deliberate. Legal answers should be boring and accurate, not creative.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fallback Chain: Four Models, One Query
&lt;/h2&gt;

&lt;p&gt;HAQ runs inference on Groq — specifically because of the inference speed. For someone who might be in genuine distress about an eviction notice or a wrongful termination, waiting fifteen seconds for an AI to respond is a UX failure.&lt;/p&gt;

&lt;p&gt;But Groq's free tier has rate limits. Models have context window limits. Sometimes a specific model is just unavailable. So I built a fallback chain:&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;MODELS&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;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Primary: best reasoning quality
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama-3.1-70b-versatile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Fallback 1
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3-70b-8192&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# Fallback 2
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mixtral-8x7b-32768&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# Fallback 3: longest context window
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_with_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;MODELS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groq_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;system&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;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;SYSTEM_PROMPT&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;role&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;user&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;content&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="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&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;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;RateLimitError&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;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;log_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&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;raise&lt;/span&gt; &lt;span class="nc"&gt;AllModelsFailedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All fallback models exhausted&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;Four models. One query. The user never sees a rate limit error — they just get an answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bilingual Problem Is Harder Than It Sounds
&lt;/h2&gt;

&lt;p&gt;Urdu-English code-switching is the natural register of educated Pakistanis. Nobody speaks pure Urdu in a legal context — you'd say &lt;em&gt;"میرے landlord نے mujhe غیر قانونی طور پر lock out کر دیا"&lt;/em&gt; (My landlord has illegally locked me out) before you'd say any pure-Urdu equivalent that nobody actually uses.&lt;/p&gt;

&lt;p&gt;LLMs handle Urdu worse than English. They handle Romanized Urdu (Urdu transliterated into Latin characters) even more inconsistently. They handle code-switched Urdu-English at unpredictable quality levels depending on which model you're using and whether the moon is in the right phase.&lt;/p&gt;

&lt;p&gt;My solution was to stop fighting this. HAQ accepts input in any mixture of languages and scripts. The query preprocessing detects dominant language and script. The retrieval always runs against English-language legislation (because that's how Pakistani law is written). The response is generated in whatever language the user wrote in.&lt;/p&gt;

&lt;p&gt;The UI renders RTL text for Urdu script. Mixing RTL and LTR in the same paragraph — when someone writes half in Urdu script and half in English — is a CSS situation I spent two days on. Unicode bidirectional algorithm, &lt;code&gt;dir="auto"&lt;/code&gt;, then eventually a per-paragraph language detection function that sets directionality at the paragraph level rather than the document level.&lt;/p&gt;

&lt;p&gt;It's still not perfect. Code-switched sentences with English entity names embedded mid-Urdu are the hardest case. It's good enough that users don't notice unless they're looking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Location, Location, Jurisdiction
&lt;/h2&gt;

&lt;p&gt;Pakistan has four provinces, federally administered territories, and a capital territory — each with different legislative jurisdiction across different domains. Tenancy law is provincial. Labor law is partly federal, partly provincial (post-18th Amendment). Family law has its own court system entirely.&lt;/p&gt;

&lt;p&gt;An answer about rent disputes in Lahore can be completely wrong for Karachi. And wrong answers here are worse than no answers.&lt;/p&gt;

&lt;p&gt;HAQ asks for city on first launch and maps it to jurisdiction. The Pinecone query filters by jurisdiction metadata before ranking by semantic similarity:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jurisdiction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$or&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jurisdiction&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;$eq&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;federal&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;jurisdiction&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;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jurisdiction&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;build_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_jurisdiction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;include_metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Federal law plus the user's provincial law. Five results, semantically ranked. That's what the model gets to work with.&lt;/p&gt;

&lt;p&gt;This sounds like a small feature. It changes accuracy dramatically for anyone outside Islamabad.&lt;/p&gt;




&lt;h2&gt;
  
  
  Voice, OCR, and Legal Letters
&lt;/h2&gt;

&lt;p&gt;Three features that don't sound like AI features but matter more than any of the ML decisions above, for the actual users I'm building for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice input + TTS&lt;/strong&gt;: If your legal question is &lt;em&gt;"میرا کرایہ ختم ہو رہا ہے اور مالک مکان کیا کر سکتا ہے"&lt;/em&gt; and you're not comfortable typing on a phone — or you're not fully literate — you should be able to say it. Whisper handles transcription. pyttsx3 handles response playback. The full pipeline works voice-in, voice-out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document OCR&lt;/strong&gt;: Someone receives a legal notice. It's a physical piece of paper or a photo on their phone. HAQ accepts the image, runs Tesseract OCR with the Urdu language pack, extracts the text, and adds it to query context. "What does this mean? What are my rights?" is a full HAQ query at that point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legal letter generation&lt;/strong&gt;: If the answer is "yes, you have grounds to challenge this" — the next thing the user needs is a formal letter. HAQ generates one in the appropriate Pakistani legal format, citing the relevant sections, as a downloadable PDF or DOCX. Not a substitute for a lawyer reviewing it. But something concrete to start with, in the right format, with the right citations.&lt;/p&gt;

&lt;p&gt;None of these are technically sophisticated. All of them are the difference between a demo and a tool that someone actually uses.&lt;/p&gt;




&lt;h2&gt;
  
  
  What HAQ Actually Is
&lt;/h2&gt;

&lt;p&gt;Let me be precise about what this does and doesn't do.&lt;/p&gt;

&lt;p&gt;HAQ &lt;strong&gt;does&lt;/strong&gt;: help someone understand what Pakistani law says about their situation, with verifiable citations, in Urdu or English, on their phone, for free.&lt;/p&gt;

&lt;p&gt;HAQ &lt;strong&gt;does not&lt;/strong&gt;: replace a lawyer. It doesn't interpret law — it retrieves it. It doesn't advise on strategy. It doesn't predict court outcomes. Every response includes a clear disclaimer directing users toward qualified legal counsel for anything complex.&lt;/p&gt;

&lt;p&gt;The goal was never to replace the legal system. It was to close the information gap between people and the system that already exists to serve them.&lt;/p&gt;

&lt;p&gt;When someone in Sukkur can read what Section 8 of the Rent Restriction Ordinance actually says about their eviction notice — rather than relying on what someone told them someone else said the law might be — that's not a small thing. That's the difference between knowing your rights and not knowing them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Still Broken
&lt;/h2&gt;

&lt;p&gt;HAQ is live. HAQ is imperfect. Here's the honest list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;100+ Acts is substantial. It's not complete. A crawler for remaining provincial Acts is in progress.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urdu retrieval&lt;/td&gt;
&lt;td&gt;A dedicated Urdu sentence encoder would improve semantic search quality beyond what multilingual-e5 provides.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversation memory&lt;/td&gt;
&lt;td&gt;History doesn't persist across sessions. Follow-up questions lose context. This matters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lawyer directory&lt;/td&gt;
&lt;td&gt;The natural next step after "you have a case" is "here's someone in your city." That integration doesn't exist yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want to contribute — especially if you work in Pakistani law, Urdu NLP, or legal tech — the repository is open.&lt;br&gt;
**&lt;/p&gt;

&lt;p&gt;Try It&lt;br&gt;
HAQ is live on Hugging Face Spaces — no installation, no API key, no setup.&lt;/p&gt;

&lt;p&gt;🤗 Hugging Face → &lt;a href="https://huggingface.co/spaces/Shahrukh350/Advocate-AI" rel="noopener noreferrer"&gt;https://huggingface.co/spaces/Shahrukh350/Advocate-AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ask it about tenancy rights in Punjab. Ask what the Defamation Ordinance says about online speech. Ask in Urdu. Ask in English. Ask the way you actually talk. Watch what it retrieves, read the citations, click the source links, and see whether it holds up.&lt;/p&gt;

&lt;p&gt;The full source — RAG pipeline, embedding scripts, system prompts, everything — is open under MIT:&lt;/p&gt;

&lt;p&gt;GitHub → [HAQ repo] &lt;a href="https://github.com/Shahrukh-aidev/Adovacate-AI" rel="noopener noreferrer"&gt;https://github.com/Shahrukh-aidev/Adovacate-AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find gaps in legislation coverage, broken Urdu rendering, or a case where it hallucinated past its guardrails — open an issue. That's exactly the kind of feedback that makes this better. PRs welcome, especially from anyone working in Pakistani law, Urdu NLP, or legal tech accessibility.*&lt;/p&gt;




&lt;p&gt;There are 220 million people in Pakistan. A significant number of them will interact with the legal system at some point in their lives without ever speaking to a lawyer.&lt;/p&gt;

&lt;p&gt;That's not acceptable. And it's not unfixable.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by Shahrukh at Sukkur IBA University.&lt;/em&gt;  &lt;/p&gt;




&lt;p&gt;&lt;code&gt;#ai #llm #showdev #opensource&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;#python #rag #legaltech #nlp #accessibility #pakistan #webdev #beginners #programming&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>What Happens When You Try to Build Eyes for Someone Who Can't See or Hear?</title>
      <dc:creator>Shahrukh </dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:38:52 +0000</pubDate>
      <link>https://dev.to/shahrukhaidev/what-happens-when-you-try-to-build-eyes-for-someone-who-cant-see-or-hear-1jk0</link>
      <guid>https://dev.to/shahrukhaidev/what-happens-when-you-try-to-build-eyes-for-someone-who-cant-see-or-hear-1jk0</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Wouldn't Leave Us Alone
&lt;/h2&gt;

&lt;p&gt;There are &lt;strong&gt;285 million people&lt;/strong&gt; living with visual impairment worldwide. That's not a statistic we stumbled on in a paper. That's roughly the entire population of Pakistan — the country we live in — walking through the world with a tool designed in the 1800s as their primary navigation aid.&lt;/p&gt;

&lt;p&gt;The white cane is genuinely brilliant engineering for what it does. But it has a fundamental physical constraint: it scans the ground. Obstacles at chest height, head height, or anywhere in the mid-air zone between? Invisible.&lt;/p&gt;

&lt;p&gt;Guide dogs are extraordinary. They're also expensive to train, unavailable in most of the developing world, and not everyone can care for an animal.&lt;/p&gt;

&lt;p&gt;We're four CS students at Sukkur IBA University. We don't have research grants or hardware labs. What we had was the question: &lt;em&gt;what if a piece of clothing could give someone a spatial sense they were born without?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That question turned into IRIS — Interactive Responsive Inclusive System.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea: Make the Jacket Feel
&lt;/h2&gt;

&lt;p&gt;The core insight was deceptively simple.&lt;/p&gt;

&lt;p&gt;Skin is an incredible input device. We use tactile sensation constantly — the gentle pressure of a doorframe, the change in floor texture, the warmth of sunlight from a window. We know from research on sensory substitution (shoutout to Paul Bach-y-Rita's work) that the brain is remarkably good at reinterpreting tactile input as spatial awareness, given enough meaningful signal.&lt;/p&gt;

&lt;p&gt;So: what if we mapped space onto the body?&lt;/p&gt;

&lt;p&gt;A vibration on your left shoulder = something approaching from the left. A strong buzz on your center chest = obstacle directly ahead, close. Light hum everywhere = clear path for meters.&lt;/p&gt;

&lt;p&gt;Not just on/off. &lt;em&gt;Proportional&lt;/em&gt;. The closer the obstacle, the more intense the vibration. The more off-center it is, the more precisely the corresponding motor responds.&lt;/p&gt;

&lt;p&gt;That was the dream. Now we had to build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hardware: What Goes Inside a Jacket
&lt;/h2&gt;

&lt;p&gt;Let's be honest with you — early conversations about this project involved a lot of very naive assumptions about how simple this would be.&lt;/p&gt;

&lt;p&gt;The core hardware stack ended up being:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sensing layer&lt;/strong&gt; — Three HC-SR04 ultrasonic sensors mounted left, center, and right on the jacket. They pulse ultrasound and listen for the echo. Simple, cheap, battle-tested. Range up to 2 meters, which gives roughly a 1–2 second warning window at walking pace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The brain&lt;/strong&gt; — An ESP32 microcontroller runs the real-time loop: read sensors, run the math model, drive the motors. It's fast enough, it has built-in WiFi, and it's cheap enough that we didn't cry when we fried one (we fried one).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The feeling&lt;/strong&gt; — Seven ERM (Eccentric Rotating Mass) vibration motors distributed across the jacket. Left, center, right for the ultrasonic zone. Plus three for the AI vision layer. Plus one dedicated "URGENT" motor for critical alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The eyes&lt;/strong&gt; — A Raspberry Pi 4 with a camera module runs in parallel, capturing frames every 3 seconds and sending them to Microsoft Azure Computer Vision. The Pi is the brains for the AI layer; the ESP32 handles the real-time haptics.&lt;/p&gt;

&lt;p&gt;The first prototype looked like someone had attacked a jacket with a soldering iron and lost. By the third iteration it looked almost intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Night Everything Was Wrong: A Love Story With Motors
&lt;/h2&gt;

&lt;p&gt;Here's something nobody tells you about haptic feedback projects: ERM vibration motors are little monsters.&lt;/p&gt;

&lt;p&gt;We'd wire everything up, send a PWM signal, and... nothing. Motor just sat there. Or it would hum faintly — clearly receiving power — but not spin.&lt;/p&gt;

&lt;p&gt;This is static friction. ERM motors need significantly more torque to &lt;em&gt;start&lt;/em&gt; rotating than to &lt;em&gt;keep&lt;/em&gt; rotating. If your target intensity is, say, PWM 80 out of 255, and the motor's breakaway torque requires PWM 120 to overcome — it just won't start.&lt;/p&gt;

&lt;p&gt;We lost a full weekend to this. We thought we had bad motors. We thought our transistor circuit was wrong. We tried different resistor values, different supply voltages, different PWM frequencies.&lt;/p&gt;

&lt;p&gt;The fix, when we finally found it, is almost embarrassingly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Motor was OFF + new signal received?
  → Blast full 255 PWM for 50ms   (kick it over the static friction hump)
  → Then settle to target PWM     (proportional intensity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 50 millisecond kick-start. That's it. That's the fix for a weekend of misery.&lt;/p&gt;

&lt;p&gt;If you're ever building a haptic system — save yourself the pain. Implement the kick-start from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Math That Made It Real
&lt;/h2&gt;

&lt;p&gt;This is the part we're most proud of, and the part that took the longest to get right.&lt;/p&gt;

&lt;p&gt;The naive approach to directional haptics is simple: if something is on the left, buzz the left motor. On the right, buzz the right. In the middle, buzz the center.&lt;/p&gt;

&lt;p&gt;That works. But it produces jarring, binary feedback. On. Off. On. Off. For someone relying on this as a navigation sense, that's tiring and imprecise.&lt;/p&gt;

&lt;p&gt;We wanted something that felt &lt;em&gt;continuous&lt;/em&gt;. As an obstacle drifted from left to center, the motors should smoothly interpolate — the left motor fading down, the center motor rising up, like a balance needle swinging.&lt;/p&gt;

&lt;p&gt;The solution came from thinking geometrically. Each motor has a fixed angular orientation θ in the jacket's coordinate plane:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left motor → θ = π (facing 180°)&lt;/li&gt;
&lt;li&gt;Center motor → θ = π/2 (facing forward)&lt;/li&gt;
&lt;li&gt;Right motor → θ = 0° (facing right)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, given two distance readings — center distance and side distance — the PWM intensity for any motor at angle θ becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                255 × side × center
PWM = 255 - ──────────────────────────────────────
             √( center² · cos²θ + side² · sin²θ )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visually, think of an ellipse defined by the two distances. The motor's angle determines where on that ellipse we sample — giving it natural directional sensitivity. Close in the center? The ellipse is narrow and tall — the center motor maxes out, the side motors stay relatively calm. Close on the left? The ellipse stretches sideways — the left motor fires hard, center fires moderately, right barely registers.&lt;/p&gt;

&lt;p&gt;We built an interactive 3D Desmos model to visualize this before we committed to the formula. Watching the motor intensities shift smoothly as we moved a virtual obstacle around the space was genuinely one of the best moments of the project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Left Motor&lt;/th&gt;
&lt;th&gt;Center Motor&lt;/th&gt;
&lt;th&gt;Right Motor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Everything 3m away&lt;/td&gt;
&lt;td&gt;~127 (mild)&lt;/td&gt;
&lt;td&gt;~127 (mild)&lt;/td&gt;
&lt;td&gt;~127 (mild)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left obstacle at 0.3m&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~245 (strong)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~145 (moderate)&lt;/td&gt;
&lt;td&gt;~10 (barely)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Center obstacle at 0.3m&lt;/td&gt;
&lt;td&gt;~145 (moderate)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~245 (strong)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~145 (moderate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything at 0.2m&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;255 (max)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;255 (max)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;255 (max)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is what made IRIS feel like a &lt;em&gt;sense&lt;/em&gt; rather than a burglar alarm.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI Layer: Teaching the Jacket to See
&lt;/h2&gt;

&lt;p&gt;The ultrasonic sensors are fast and reliable, but they have no concept of &lt;em&gt;what&lt;/em&gt; they're detecting. A wall and a child both return the same echo.&lt;/p&gt;

&lt;p&gt;For that, we added an Azure Computer Vision pipeline running on the Raspberry Pi.&lt;/p&gt;

&lt;p&gt;Every 3 seconds, the Pi camera captures a 1280×720 JPEG. That image goes to Azure's dual API — one call for scene description ("a person standing near a flight of stairs"), one for object detection with bounding boxes.&lt;/p&gt;

&lt;p&gt;Anything below 50% confidence gets discarded. The rest gets classified.&lt;/p&gt;

&lt;p&gt;The frame is divided into three horizontal zones (left / center / right), and each detected object's bounding box center determines which motor zone fires. Object size in the frame estimates physical distance — a bounding box covering more than 25% of the frame means it's very close, triggering maximum intensity plus the dedicated URGENT motor.&lt;/p&gt;

&lt;p&gt;Some objects earn special treatment:&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;URGENT_OBSTACLES&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;car&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;truck&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;person&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;stairs&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;hole&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;wall&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;pole&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;dog&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of these appear close or center, the jacket interrupts whatever it's doing and fires the urgent motor while the Pi speaks a voice alert via &lt;code&gt;espeak&lt;/code&gt;. The voice can describe the full scene — "stairs ahead" — something no ultrasonic sensor could ever tell you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Communication Problem Nobody Warned Us About
&lt;/h2&gt;

&lt;p&gt;Two devices. One jacket. They need to talk to each other over WiFi.&lt;/p&gt;

&lt;p&gt;Our first instinct was MQTT — it's the standard IoT messaging protocol, it's reliable, it has guaranteed delivery. We spent time setting up a broker, testing it, latency testing it.&lt;/p&gt;

&lt;p&gt;Then we noticed something uncomfortable: ~50ms latency between the Pi sending a command and the ESP32 firing a motor. For haptics, that's a lifetime. The user's body is expecting a response &lt;em&gt;now&lt;/em&gt; — delayed feedback teaches the nervous system the wrong thing.&lt;/p&gt;

&lt;p&gt;We switched to raw UDP sockets. The command format is as minimal as we could make it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"V,{motor_index},{intensity},{duration_ms}"

Examples:
  "V,3,255,500"  →  AI-Left motor, full power, 500ms
  "V,1,100,200"  →  Center motor, gentle pulse, 200ms
  "STOP"         →  Kill all motors immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Latency dropped to ~1ms. Yes, UDP is "fire and forget" — if a packet drops, the motor misses one pulse. But for haptic feedback, that's fine. A missed vibration is imperceptible. A &lt;em&gt;late&lt;/em&gt; vibration is actively wrong.&lt;/p&gt;

&lt;p&gt;We also scrapped hardcoded IP addresses (which would break on any new network) and had the ESP32 advertise itself via mDNS as &lt;code&gt;esp32.local&lt;/code&gt;. The Pi finds it automatically on startup. Plug in anywhere, it just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Submitting to Microsoft Imagine Cup
&lt;/h2&gt;

&lt;p&gt;We submitted IRIS to Microsoft Imagine Cup 2026 in the Accessibility category.&lt;/p&gt;

&lt;p&gt;Writing the submission forced us to articulate something we'd been feeling but not quite saying: this project isn't about the technology. The ESP32, the Azure API, the trigonometric model — those are just means. The thing we actually built is a small reduction in the margin between what sighted people take for granted and what everyone else has to work around.&lt;/p&gt;

&lt;p&gt;285 million people. The cane has been the primary tool for most of them for over a century. We're four students from Sukkur, Pakistan writing a few hundred lines of C++ and Python and we built something that works &lt;em&gt;today&lt;/em&gt;, with components anyone can order, that gives directional spatial awareness no cane has ever given.&lt;/p&gt;

&lt;p&gt;That's not nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It, Break It, Improve It
&lt;/h2&gt;

&lt;p&gt;Everything is open source under MIT — hardware, firmware, Python scripts, the math model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mrshaikhmuhammad/IRIS-Jacket" rel="noopener noreferrer"&gt;GitHub → IRIS-Jacket&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full architecture, pin mappings, circuit diagrams, setup instructions — it's all there. If you build a version, change the motor layout, swap Azure for a different vision API, add a GSM module so it works offline — we want to know. Open a PR. File an issue. Send a message.&lt;/p&gt;

&lt;p&gt;There are 285 million people in the world this could matter to.&lt;/p&gt;

&lt;p&gt;That's a lot of reasons to keep building.&lt;/p&gt;




&lt;p&gt;Built by &lt;a href="https://github.com/mrshaikhmuhammad" rel="noopener noreferrer"&gt;Shaikh Muhammad&lt;/a&gt;, &lt;a href="https://github.com/shahzebalipirzada" rel="noopener noreferrer"&gt;Shahzeb&lt;/a&gt;, &lt;a href="https://github.com/khizarbinmaalik" rel="noopener noreferrer"&gt;Khizar&lt;/a&gt;, and &lt;a href="https://github.com/shahrukh-aidev" rel="noopener noreferrer"&gt;Shahrukh&lt;/a&gt; at Sukkur IBA University.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post was written with AI assistance for structure and language. &lt;br&gt;
All technical decisions, debugging, and math are our own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;#showdev&lt;/code&gt; &lt;code&gt;#iot&lt;/code&gt; &lt;code&gt;#accessibility&lt;/code&gt; &lt;code&gt;#opensource&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#raspberrypi&lt;/code&gt; &lt;code&gt;#esp32&lt;/code&gt; &lt;code&gt;#python&lt;/code&gt; &lt;code&gt;#cpp&lt;/code&gt; &lt;code&gt;#computervision&lt;/code&gt; &lt;code&gt;#hardware&lt;/code&gt; &lt;code&gt;#azure&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#beginners&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#programming&lt;/code&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>hardware</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
