<?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: Maya Chen</title>
    <description>The latest articles on DEV Community by Maya Chen (@jamesmitchelldev).</description>
    <link>https://dev.to/jamesmitchelldev</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%2F3920391%2F69dab238-b6b8-4288-90fd-b62c2a73c89a.jpeg</url>
      <title>DEV Community: Maya Chen</title>
      <link>https://dev.to/jamesmitchelldev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jamesmitchelldev"/>
    <language>en</language>
    <item>
      <title>Our New Proofreader Can't Write a Single Word</title>
      <dc:creator>Maya Chen</dc:creator>
      <pubDate>Wed, 07 Oct 2026 00:40:13 +0000</pubDate>
      <link>https://dev.to/jamesmitchelldev/our-new-proofreader-cant-write-a-single-word-6lc</link>
      <guid>https://dev.to/jamesmitchelldev/our-new-proofreader-cant-write-a-single-word-6lc</guid>
      <description>&lt;p&gt;We had an AI that only answers yes or no reread every rule we publish. Here is how that went.&lt;/p&gt;

&lt;p&gt;Last week &lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;TypeSafe&lt;/a&gt; released &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;Jev&lt;/a&gt;, an AI model that can't write. You give it some text and a list of questions, and it answers with things like "yes, 0.96" or "option B." It won't summarize the text or explain itself, and an answer usually comes back in about a sixth of a second. TypeSafe calls it a System One model, after the fast, gut-reaction half of Kahneman's two kinds of thinking.&lt;/p&gt;

&lt;p&gt;Their launch demos have it playing Doom and racing through Wikipedia links. We had a duller job in mind. Every rule on Court Rules comes with the judge's own words attached, so that a reader can check our summary against the sentence it came from. A model that does nothing except compare a claim to a piece of text is pretty much a proofreader, and we had about 64,000 rules that had never had a second pair of eyes on them all at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twenty minutes and $1.34
&lt;/h3&gt;

&lt;p&gt;Each rule we publish starts with the judge's sentence, quoted word for word. Under that is our plain-English summary, and under that a handful of structured fields (a page limit, say, or a number of days). The quote gets matched against the source document before a rule goes live. The summary and the fields are written by an extraction model, and until now the only way to double-check them was to pay a second large model to reread everything. That is slow and expensive enough that we had only ever done it in samples.&lt;/p&gt;

&lt;p&gt;Jev read all of it in twenty minutes, and the bill was $1.34. For each rule we asked it two things: does the summary say what the quote says, and is each structured field something the quote states?&lt;/p&gt;

&lt;h3&gt;
  
  
  What it caught
&lt;/h3&gt;

&lt;p&gt;About 95 percent of rules came back clean. In the rest, the quote and the citation were fine, and the trouble was a summary or a field that said slightly more than the judge did. One judge wrote that a party "may submit" two courtesy copies of a compendium of authorities, and our summary had turned that into a requirement. Another judge encourages junior attorneys to argue motions, and our structured data had attached a years-of-experience cutoff the judge never gave. A rule about tabbing exhibit binders had picked up page margins from somewhere.&lt;/p&gt;

&lt;p&gt;A litigator mostly reads a rule to find out whether something is required or just allowed, so we took these seriously. We went through a sample by hand, agreed with Jev, and fixed the flagged rules the same day.&lt;/p&gt;

&lt;p&gt;We also changed the form our extraction model fills in. It used to have required blanks for things like margins and experience cutoffs, and a model handed a required blank will find something to put in it. Those fields are optional now, and an empty one means the judge didn't say.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it was bad
&lt;/h3&gt;

&lt;p&gt;Our first multiple-choice question was a flop, and it was our fault. We gave Jev a menu of eleven document types and asked which one a page was, and it decided that nearly every blank template order was an order entered in one particular named case. We blamed the model and switched to separate yes-or-no questions, which worked.&lt;/p&gt;

&lt;p&gt;Then Jerry Liu published &lt;a href="https://github.com/jerryjliu/docjev" rel="noopener noreferrer"&gt;DocJev&lt;/a&gt;, which classifies documents with Jev by defining each category in one sentence about the document's main purpose. Our eleven options had been bare labels with nothing telling them apart. We rewrote the menu that way and ran it on a batch of documents, five of which we had already read by hand. It agreed with our reading on four. The miss was a links page that talks about filing rules without stating any, which a second yes-or-no question now catches. &lt;a href="https://parallel.ai/blog/testing-jev" rel="noopener noreferrer"&gt;Parallel found&lt;/a&gt; that very long label lists still hurt it, so we keep the menus short.&lt;/p&gt;

&lt;p&gt;Its confidence scores only mean something at the ends. When it was 95 percent sure a summary contradicted its quote, it was usually right. At 60 percent it was right about one time in four, and once it insisted that "at least 21 days before" and "no later than 21 days before" were in conflict. Another time it pulled the correct number out of a 90-minute time limit and then told us the unit was hours.&lt;/p&gt;

&lt;p&gt;And it has no ideas of its own. It will count any category you hand it across every rule we have, for pennies, but we had to bring the categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Questions we couldn't afford before
&lt;/h3&gt;

&lt;p&gt;The cheapness changes what you bother to ask. We would never have paid a big model to find out how judges phrase deadlines, but at these prices we just ran the question across the nearly nine thousand rules that have one. More than a third just say "days" and leave you to work out which kind. About one in fourteen say "court days," which skip court holidays and &lt;a href="https://www.courtrules.app/blog/federal-court-deadline-calculation-holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=jev-decision-model-court-rules-test" rel="noopener noreferrer"&gt;are not the same thing as business days&lt;/a&gt;. We are reworking our deadline fields around what that survey found.&lt;/p&gt;

&lt;p&gt;We also ran it over about four thousand source documents with a single question: does this page tell a litigant to do something? That took 70 seconds. It turned up several hundred pages we had set aside, most of them the blank template orders judges post for parties to fill in, which do carry requirements if you read them. A second model that had to quote its evidence agreed on 39 of the 40 we pulled at random. Those documents are in the extraction queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does now
&lt;/h3&gt;

&lt;p&gt;Jev now gets the first say on whether a document we collect contains rules at all. The large model only weighs in when Jev is unsure. It also has the proofreading job for good. Every new rule's summary and values are checked against the judge's sentence before the rule shows up on &lt;a href="https://www.courtrules.app/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=jev-decision-model-court-rules-test" rel="noopener noreferrer"&gt;the page you search&lt;/a&gt;, and a rule it can't stand behind gets flagged for us to look at. We will write again once that has been running for a while and we can say how much it catches.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.courtrules.app/blog/jev-decision-model-court-rules-test?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=jev-decision-model-court-rules-test" rel="noopener noreferrer"&gt;Court Rules&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>legaltech</category>
      <category>api</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Privacy Enforcement Is Becoming Product Data</title>
      <dc:creator>Maya Chen</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:24:06 +0000</pubDate>
      <link>https://dev.to/jamesmitchelldev/privacy-enforcement-is-becoming-product-data-42hh</link>
      <guid>https://dev.to/jamesmitchelldev/privacy-enforcement-is-becoming-product-data-42hh</guid>
      <description>&lt;p&gt;No one should read 1,338 enforcement releases and regulatory records one at a time. The useful work begins when those materials become source-linked records: dates, regulators, violation types, remedies, penalties, and citations that a legal workflow can actually use.&lt;/p&gt;

&lt;p&gt;Regulatory enforcement is usually consumed as a stream of press releases. A new FTC settlement, a state attorney general investigation, an HHS breach report, a warning letter, a consent order. Each item matters, but the stream is hard to use. It tells you what happened, then leaves your team to decide whether it affects contracts, product settings, vendor reviews, board reporting, or customer-facing policy.&lt;/p&gt;

&lt;p&gt;That is the wrong shape for modern legal work. A legal team does not need another inbox. It needs enforcement data that can move through systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1,338&lt;/strong&gt; Enforcement records: Source-linked actions, guidance, investigations, settlements, and regulatory updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;14&lt;/strong&gt; Jurisdictions: Federal and state authorities, including FTC, HHS OCR, state AGs, and CPPA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$50B+&lt;/strong&gt; Tracked penalties: Monetary exposure is only one signal. Remedies and warnings often move first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of May 23, 2026, the CourtRules.app enforcement tracker contains 1,338 records across 14 jurisdictions. The tracker covers monetary penalties, but the more interesting signal is not the penalty total. It is the combination of violation type, remedy, source language, and regulator behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The top enforcement signals in the dataset
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Data breach: 789 records&lt;/li&gt;
&lt;li&gt;Health data: 780 records&lt;/li&gt;
&lt;li&gt;Security failure: 747 records&lt;/li&gt;
&lt;li&gt;Unauthorized data sharing: 221 records&lt;/li&gt;
&lt;li&gt;Notice failure: 146 records&lt;/li&gt;
&lt;li&gt;Consent failure: 121 records&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The recent feed is already changing shape
&lt;/h2&gt;

&lt;p&gt;The most recent privacy and platform-safety records are not just classic data breach settlements. They are about nonconsensual intimate images, AI-enabled devices, sensitive location data, youth social media design, consent flows, and biometric capture. Those categories look less like after-the-fact litigation and more like product governance.&lt;/p&gt;

&lt;p&gt;FTC: May 20, 2026&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.courtrules.app/enforcement/ftc-12-unnamed-nudify-tool-providers-2026-05-20-e0253b?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;Warning letters to 12 nudify tool providers&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The useful field is not a fine amount. It is the remedy clock: covered platforms must remove qualifying nonconsensual intimate images within 48 hours of a valid request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ftc.gov/news-events/news/press-releases/2026/05/ftc-sends-warning-letters-companies-about-compliance-take-it-down-act" rel="noopener noreferrer"&gt;Official source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TX: May 20, 2026&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.courtrules.app/enforcement/tx-meta-formerly-known-as-facebook-2026-05-20-2614ab?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;Investigation into Meta AI glasses&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The event clusters biometric data, notice failure, consent failure, unauthorized data sharing, and dark patterns in one record. That is exactly the kind of bundle a privacy team needs to route correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.texasattorneygeneral.gov/news/releases/attorney-general-ken-paxton-launches-investigation-meta-glasses-protect-texans-privacy-unlawful" rel="noopener noreferrer"&gt;Official source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FTC: May 4, 2026&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.courtrules.app/enforcement/ftc-kochava-inc-and-collective-data-solutions-cds-2026-05-04-f166ed?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;Kochava sensitive location data settlement&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The headline is location data. The workflow signal is stronger: consent assessments, retention schedules, incident reporting, data deletion, and a ban on certain sensitive-data sales.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ftc.gov/news-events/news/press-releases/2026/05/ftc-ban-kochava-subsidiary-selling-sensitive-location-data-settle-charges-they-sold-location-data" rel="noopener noreferrer"&gt;Official source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CT: May 1, 2026&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.courtrules.app/enforcement/ct-social-media-companies-2026-05-01-2cc6c5?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;Youth social media and AI harm legislation&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is not a company settlement. It is still operationally important because it creates future enforcement hooks around minors, addictive algorithms, AI chatbots, reporting, and consent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://portal.ct.gov/ag/press-releases/2026-press-releases/statement-on-final-passage-of-legislation-to-combat-youth-social-media-addiction" rel="noopener noreferrer"&gt;Official source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put those events side by side and a pattern appears. The enforcement system is moving earlier in the product lifecycle. It is not waiting for one giant fine to tell the market what matters. Warning letters, investigations, civil investigative demands, new enforcement authorities, and remedy terms all tell a product and compliance team where regulator attention is headed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine amount is a weak sorting key
&lt;/h2&gt;

&lt;p&gt;A fine is easy to sort. It is also late. By the time a large penalty appears, the market has usually had months or years of warning. In our data, many important records have no penalty at all: warning letters, investigations, guidance, new laws, complaint filings, and consent orders with operational restrictions.&lt;/p&gt;

&lt;p&gt;That matters for legal operations. A zero-dollar enforcement record can still trigger contract review if it names a sensitive data category. It can trigger a product review if the remedy involves deletion, affirmative consent, reporting, or a ban. It can trigger a vendor review if the source language names subcontractors, data brokers, or onward sharing.&lt;/p&gt;

&lt;p&gt;In other words, the workflow question is not only "how much did they pay?" It is "what conduct did the regulator identify, and what would we have to prove if asked?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured records make the pattern visible
&lt;/h2&gt;

&lt;p&gt;The same enforcement action can be read several ways. A press release is a news item. A consent order is a legal document. A source quote is evidence. A violation type is a routing key. A remedy is a product requirement. A source URL is an audit trail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fields that turn news into workflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jurisdiction&lt;/code&gt; tells the system which regulator is moving.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;violation_types&lt;/code&gt; normalize messy agency language into a usable taxonomy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remedy_types&lt;/code&gt; show what the company has to change, not only what it paid.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;primary_source_url&lt;/code&gt; preserves the official record for verification.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;source_quotes&lt;/code&gt; make the extracted claim auditable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the data model matters. A plain-English summary is useful for a reader, but a workflow needs stable fields. An AI agent cannot reliably answer "show me recent actions involving consent failure and children's data" unless those concepts are normalized. A contract system cannot flag a vendor agreement for review unless it can connect the event to search terms, industries, laws, and remedies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next alert is not an email
&lt;/h2&gt;

&lt;p&gt;Most enforcement monitoring still looks like a newsletter. That is fine for awareness, but it is not enough for operational response. The useful version is an event record that can be routed.&lt;/p&gt;

&lt;p&gt;A healthcare record with security failure and health data should go to privacy and security. A youth social media record involving dark patterns and children's data should go to product counsel. A data broker record involving sensitive location data should go to vendor management and commercial contracting. A biometric investigation should go to the team that owns device features, notices, and consent.&lt;/p&gt;

&lt;p&gt;That is why we are building enforcement data next to court rules. Both domains have the same underlying problem. The source material is public, but it is not workflow-shaped. Lawyers can read it, but systems cannot reliably act on it until it has been normalized, cited, and connected to the decisions teams actually make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data notes
&lt;/h2&gt;

&lt;p&gt;The counts above come from the CourtRules.app enforcement tracker as of May 23, 2026. Records are extracted from official government sources and linked back to the source release or document. The tracker includes privacy enforcement, consumer protection records with privacy implications, HHS OCR breach records, state attorney general actions, FTC actions, CPPA activity, guidance, investigations, and new enforcement authorities.&lt;/p&gt;

&lt;p&gt;Start with the &lt;a href="https://www.courtrules.app/enforcement?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;enforcement tracker&lt;/a&gt;, or read the &lt;a href="https://docs.courtrules.app/concepts/enforcement-data" rel="noopener noreferrer"&gt;enforcement data docs&lt;/a&gt; for the fields exposed through our MCP tools.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.courtrules.app/blog/privacy-enforcement-data-product-signal?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=privacy-enforcement-data-product-signal" rel="noopener noreferrer"&gt;Court Rules&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>legaltech</category>
      <category>data</category>
    </item>
    <item>
      <title>Court Deadline Calculator for Business Days, Court Days, and Holidays</title>
      <dc:creator>Maya Chen</dc:creator>
      <pubDate>Tue, 29 Sep 2026 16:55:36 +0000</pubDate>
      <link>https://dev.to/jamesmitchelldev/court-deadline-calculator-for-business-days-court-days-and-holidays-26if</link>
      <guid>https://dev.to/jamesmitchelldev/court-deadline-calculator-for-business-days-court-days-and-holidays-26if</guid>
      <description>&lt;p&gt;A deadline calculator is only useful if it knows when the court is closed. Court Rules now handles federal and selected state court holiday calendars in the same workflow.&lt;/p&gt;

&lt;p&gt;Counting days is the easy part. The hard part is knowing what the rule means by days, which court calendar applies, and whether a holiday moves the answer. That is where most deadline mistakes start.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.courtrules.app/calculator?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;Court Rules Deadline Calculator&lt;/a&gt; is built for that exact problem. Pick a court, choose a start date, choose a counting method, and get a date that uses that court's holiday schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the counting method before you count
&lt;/h2&gt;

&lt;p&gt;A rule that says 10 business days does not mean the same thing as 10 calendar days. Before you count anything, decide which of these modes the rule is using.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business days:&lt;/strong&gt; skip weekends and court holidays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar days:&lt;/strong&gt; count every day, then check whether the final day needs to move under the governing rule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Court days:&lt;/strong&gt; use only when the rule or local practice says court days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Federal Rule of Civil Procedure 6 is the starting point for federal civil cases. State courts can use different terminology, different holidays, and different local tools. The calculator keeps those calendars tied to the court you select.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the court matters
&lt;/h2&gt;

&lt;p&gt;Court holidays are not just federal holidays. California courts, for example, publish statewide judicial holidays that include dates like Lincoln Day, Farmworkers Day, Native American Day, Thanksgiving, and the day after Thanksgiving. The California Judicial Branch publishes those dates on its &lt;a href="https://courts.ca.gov/about/court-holidays" rel="noopener noreferrer"&gt;court holidays page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Los Angeles Superior Court also maintains its own &lt;a href="https://www.lacourt.ca.gov/holiday/ui/index.aspx" rel="noopener noreferrer"&gt;judicial holiday schedule&lt;/a&gt; and an official &lt;a href="https://www.lacourt.org/courtdatecalculator/ui/" rel="noopener noreferrer"&gt;court date calculator&lt;/a&gt;. For critical filings, use the official court source as the final check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Los Angeles Superior Court
&lt;/h2&gt;

&lt;p&gt;Start with Friday, November 20, 2026. Add 10 business days in Los Angeles Superior Court. A weekend-only calculator lands on Friday, December 4. A court-aware calculator lands on Tuesday, December 8, because Thanksgiving Day and the day after Thanksgiving are court holidays.&lt;/p&gt;

&lt;p&gt;That is the difference between adding weekdays and calculating a court deadline. The calculator shows the skipped holidays, then gives you a calendar export so the result can go straight into Outlook, Apple Calendar, or Google Calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the holiday pages with the calculator
&lt;/h2&gt;

&lt;p&gt;The calculator is the fastest path when you already know the date and the rule. The &lt;a href="https://www.courtrules.app/holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;court holiday index&lt;/a&gt; is better when you need to inspect a court's full schedule first.&lt;/p&gt;

&lt;p&gt;For state courts, start with high-volume calendars like &lt;a href="https://www.courtrules.app/courts/ca-los-angeles-superior/holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;Los Angeles Superior Court holidays&lt;/a&gt;, &lt;a href="https://www.courtrules.app/courts/ny-court-appeals/holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;New York Court of Appeals holidays&lt;/a&gt;, and &lt;a href="https://www.courtrules.app/courts/de-chancery/holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;Delaware Court of Chancery holidays&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.courtrules.app/blog/court-deadline-calculator-state-federal-holidays?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=maya&amp;amp;utm_content=court-deadline-calculator-state-federal-holidays" rel="noopener noreferrer"&gt;Court Rules&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>legaltech</category>
      <category>data</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Checking missing dates in a legal metadata CSV with Python</title>
      <dc:creator>Maya Chen</dc:creator>
      <pubDate>Mon, 28 Sep 2026 23:54:14 +0000</pubDate>
      <link>https://dev.to/jamesmitchelldev/checking-missing-dates-in-a-legal-metadata-csv-with-python-f1o</link>
      <guid>https://dev.to/jamesmitchelldev/checking-missing-dates-in-a-legal-metadata-csv-with-python-f1o</guid>
      <description>&lt;p&gt;A small Python audit that separates missing source dates from malformed values, preserves judge-page URLs and reports what a dated metadata snapshot can actually show.&lt;/p&gt;

&lt;p&gt;A court-rules index is a list of pages with dates attached, and a date is the field most likely to be empty. The Court Rules Judge Index snapshot for September 28, 2026 has 1,233 rows across 39 courts, and 486 of them carry no date at all. Before quoting any count from a file like this, run a quick audit of the dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The snapshot
&lt;/h2&gt;

&lt;p&gt;The CSV has nine columns. The one this post cares about is rules_last_changed, an ISO date such as 2024-09-01, or an empty cell when the judge page shows no date.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;column&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;judge_name&lt;/td&gt;
&lt;td&gt;the judge or commissioner on the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;judge_status&lt;/td&gt;
&lt;td&gt;Judge, Senior Judge, Magistrate Judge and similar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;court_name&lt;/td&gt;
&lt;td&gt;the court the page belongs to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;court_slug&lt;/td&gt;
&lt;td&gt;the court's short id, for example sdny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;court_type&lt;/td&gt;
&lt;td&gt;federal or state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rule_topics&lt;/td&gt;
&lt;td&gt;the rule topics the page covers, separated by semicolons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rule_count&lt;/td&gt;
&lt;td&gt;how many rule records the page lists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rules_last_changed&lt;/td&gt;
&lt;td&gt;the date the page displays, or empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;url&lt;/td&gt;
&lt;td&gt;the judge page on courtrules.app&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The audit
&lt;/h2&gt;

&lt;p&gt;The script uses only the standard library. It never writes today's date into a missing cell, and every line of the report repeats the judge-page URL so a finding can be checked against the page.&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;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Audit a judge index CSV for missing, malformed and present source dates.

Usage: python3 date-audit.py [judges.csv]

Reads the CSV named on the command line (default: sample-judges.csv) and checks
rules_last_changed against ISO YYYY-MM-DD dates. An empty value is missing, an
unparseable value is malformed, everything else is present. Today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s date is
never substituted for a missing value, and every report line keeps the row&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s
judge-page URL.

Standard library only.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;COLUMNS&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;judge_name&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;court_slug&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;url&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;rules_last_changed&lt;/span&gt;&lt;span class="sh"&gt;"&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;is_iso_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;malformed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;present&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fieldnames&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;absent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;COLUMNS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;absent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cannot audit, the input has no &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules_last_changed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;judge_name&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="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;court_slug&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="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;is_iso_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;present&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;malformed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&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;missing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;malformed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;present&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows read = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;present = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&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;present&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, missing = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&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;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, malformed = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&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;malformed&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&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;malformed date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;malformed&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;present date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;present&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&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;records&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;court&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;shown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(empty)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;court&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;shown&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;malformed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample-judges.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 date-audit.py judges.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Against the real snapshot the summary is one line, and the per-row blocks list every missing and malformed value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rows read = 6
present = 3, missing = 1, malformed = 2

missing date (1)
  ex-district | Example Judge Missing Date | (empty) | https://example.invalid/courts/ex-district/example-judge-missing-date

malformed date (2)
  ex-district | Example Judge Month Name | March 2024 | https://example.invalid/courts/ex-district/example-judge-month-name
  ex-district | Example Judge Impossible Date | 2024-02-30 | https://example.invalid/courts/ex-district/example-judge-impossible-date

present date (3)
  ex-district | Example Judge, With Comma | 2024-09-01 | https://example.invalid/courts/ex-district/example-judge-with-comma
  ex-district | Example Judge Padded Date | 2024-09-01 | https://example.invalid/courts/ex-district/example-judge-padded-date
  ex-district | Example Judge Old Date | 2019-02-01 | https://example.invalid/courts/ex-district/example-judge-old-date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output above comes from a synthetic fixture beside the script. It carries the cases that matter. A quoted name with a comma parses as one field. A padded value strips to a clean date. March 2024 and 2024-02-30 both fail the ISO check even though a human reader might accept one of them. The real file, by contrast, reports 747 present dates and 486 missing ones, and no malformed values.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit cannot show
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A missing date stays unknown. The script leaves the cell empty and never guesses.&lt;/li&gt;
&lt;li&gt;A present date describes what the page displayed when the snapshot was built. Legal deadlines come from the court's current rules.&lt;/li&gt;
&lt;li&gt;The count measures rows in one file. It says nothing about whether a court requirement applies to a case.&lt;/li&gt;
&lt;li&gt;The audit checks the file only. The court documents behind the pages need a separate source check.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source and license
&lt;/h2&gt;

&lt;p&gt;The snapshot is published at &lt;a href="https://www.courtrules.app/courts/sdny/alison-j-nathan?ref=devto" rel="noopener noreferrer"&gt;Alison J. Nathan's source page&lt;/a&gt; and each row links its page. Nathan's entry traces back to the court's own &lt;a href="https://www.nysd.uscourts.gov/sites/default/files/practice_documents/AJN%20Nathan%20Criminal%20Rules%207.7.22%20.pdf" rel="noopener noreferrer"&gt;criminal rules PDF&lt;/a&gt;, which is the kind of document the pages summarize. CC BY 4.0 covers judges.csv, judges.json and the dataset README only. Credit Court Rules and keep every row's source URL.&lt;/p&gt;

&lt;p&gt;The script is short enough to adapt: point it at a newer snapshot, or swap the column name for any other dated field, and it reports the same three buckets.&lt;/p&gt;

</description>
      <category>python</category>
      <category>data</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How We Built a Federal Court Rules Database</title>
      <dc:creator>Maya Chen</dc:creator>
      <pubDate>Fri, 08 May 2026 20:04:28 +0000</pubDate>
      <link>https://dev.to/jamesmitchelldev/how-we-built-a-federal-court-rules-database-1n2b</link>
      <guid>https://dev.to/jamesmitchelldev/how-we-built-a-federal-court-rules-database-1n2b</guid>
      <description>&lt;h2&gt;
  
  
  Parsing 94 district courts worth of PDFs. It went about as well as you'd expect.
&lt;/h2&gt;

&lt;p&gt;We started with a simple problem. My co-founder is a litigator. I am an engineer. He was spending hours every week digging through PDFs on federal court websites to find judge-specific rules, local filing procedures, and deadline information. The PDFs were inconsistently formatted. Searchable text sat next to scanned images from 1995, some updated quarterly, some untouched for five years.&lt;/p&gt;

&lt;p&gt;He asked me if I could build something to aggregate it all. I said sure. How hard could it be?&lt;/p&gt;

&lt;p&gt;Famous last words.&lt;/p&gt;

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

&lt;p&gt;There are 94 U.S. federal district courts. Each has local rules. Many of those local rules span hundreds of pages. Then there are the individual judge practices, chamber-specific documents that live on each judge's personal page on the court website. Not every judge publishes them, and courts organize them differently; some bury the files under four layers of navigation.&lt;/p&gt;

&lt;p&gt;Our first attempt at scope was naive. We thought we would scrape the rules, parse the PDFs, and build a searchable index. We did not realize that "scrape the rules" meant writing 94 different scrapers, each handling a different CMS, different URL structure, different PDF format, and different update cadence.&lt;/p&gt;

&lt;p&gt;The Southern District of New York alone has over two dozen active district judges. Each has an individual practices PDF. Some update them every few months. The URL changes when they update. The filename changes. Sometimes the PDF is replaced inline with no versioning. Tracking changes across that surface area is a full-time job.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDF parsing is the real enemy
&lt;/h2&gt;

&lt;p&gt;PDF parsing is a solved problem in computer science the same way sailing is a solved problem. The theory is straightforward. The practice involves a lot of cursing and unexpected water.&lt;/p&gt;

&lt;p&gt;We started with Python and PyPDF2. It worked on maybe 30 percent of documents. The rest were image-based PDFs with no text layer. For those we added OCR using Tesseract. That got us to about 60 percent. But OCR on legal documents is messy. Footnotes confuse it. Tables break it. Multi-column layouts destroy it.&lt;/p&gt;

&lt;p&gt;Then we found a whole category of PDFs that were text-based but had broken encoding. The text extracted as gibberish while the visual render was fine, because the underlying text was corrupted. These were usually documents that had been printed, scanned, OCR'd, and re-saved multiple times over a decade. Each step introduced more noise.&lt;/p&gt;

&lt;p&gt;Our current pipeline uses a hybrid approach. Text-based PDFs go through pdfplumber, which handles tables and layout better than PyPDF2. Image-based PDFs go through Tesseract with layout analysis. When the encoding is broken, we fall back to full-page OCR of the rendered image, slower but more reliable than trusting the corrupt text layer.&lt;/p&gt;

&lt;p&gt;Even with all of that, we still manually review a percentage of documents. Some things are just faster for a human to verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalizing judge data
&lt;/h2&gt;

&lt;p&gt;Court websites do not have APIs. They have HTML. Sometimes very old HTML.&lt;/p&gt;

&lt;p&gt;We built a judge directory covering every active federal district judge. For each judge, we track name, court, appointment date, chamber location, and links to individual practices documents. This sounds simple. It is not.&lt;/p&gt;

&lt;p&gt;Some courts list judges on a single page with clean semantic HTML. Others use a table layout from 2003. Others require clicking through to a bio page, then finding a "chambers" tab, then downloading a PDF from a link that has no predictable structure.&lt;/p&gt;

&lt;p&gt;We ended up with a per-court configuration system. Each court gets a scraper definition file that specifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The URL pattern for the judges list page&lt;/li&gt;
&lt;li&gt;The CSS selector or XPath to extract judge names and profile links&lt;/li&gt;
&lt;li&gt;The pattern for individual practices document URLs&lt;/li&gt;
&lt;li&gt;The expected update frequency&lt;/li&gt;
&lt;li&gt;A human reviewer assignment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets us add new courts without rewriting the entire system. It also means we have 94 configuration files, each slightly different, each requiring maintenance when the court redesigns its website.&lt;/p&gt;

&lt;p&gt;Which happens more often than you would think.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deadline calculator
&lt;/h2&gt;

&lt;p&gt;The most technically interesting part of the project is the deadline calculator. Federal court deadlines depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The triggering event (service, order entry, filing)&lt;/li&gt;
&lt;li&gt;The rule that governs the period (Rule 12, Rule 56, Rule 59, etc.)&lt;/li&gt;
&lt;li&gt;The type of days (calendar days, business days, court days)&lt;/li&gt;
&lt;li&gt;Federal holidays&lt;/li&gt;
&lt;li&gt;Local court closures&lt;/li&gt;
&lt;li&gt;Whether the last day falls on a weekend or holiday&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built a rules engine that encodes the Federal Rules of Civil Procedure and the Federal Rules of Appellate Procedure. The engine takes a trigger date, a rule reference, and a court identifier. It returns the calculated deadline with an explanation of each step.&lt;/p&gt;

&lt;p&gt;The tricky part is local variations. Districts modify the default federal periods through local rules, individual judges add scheduling requirements through standing orders, and courts close for local events that are not federal holidays. We maintain a per-court overrides file that layers local rules on top of the federal defaults.&lt;/p&gt;

&lt;p&gt;For holidays, we pull the OPM federal holiday calendar. But we also track observed holidays separately from calendar holidays. When New Year's Day falls on a Saturday, the observed holiday is Friday. When it falls on Sunday, it is Monday. The calculator handles this correctly, which sounds trivial until you realize how many generic date libraries get it wrong.&lt;/p&gt;

&lt;p&gt;Inauguration Day is its own special case. It is a federal holiday only in the Washington D.C. area. Our calculator knows that and applies it only to D.D.C. and D.C. Circuit deadlines. Everywhere else, January 20 is a normal day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The stack is intentionally boring. We use Node.js for the API, PostgreSQL for structured data, and Elasticsearch for full-text search across rules documents. The frontend is React. Nothing exotic.&lt;/p&gt;

&lt;p&gt;Documents are stored in S3. Parsed text is indexed in Elasticsearch with metadata tags for court, judge, document type, and effective date. We version every document. When a judge updates their individual practices, we keep the old version and mark the new one as current. This lets us show change history and answer questions like "what did Judge Liman's practices say in 2023?"&lt;/p&gt;

&lt;p&gt;The scrapers run on a schedule. Most courts are checked weekly. High-update courts, like SDNY, are checked daily. The scraper jobs are idempotent. If nothing changed, nothing changes. If a PDF was updated, it gets downloaded, parsed, indexed, and queued for human review if the diff is significant.&lt;/p&gt;

&lt;p&gt;We use GitHub Actions for CI and deployment. The whole thing runs on AWS. Total monthly infrastructure cost is under $200. The biggest expense is the human review time, not the servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we got wrong
&lt;/h2&gt;

&lt;p&gt;Our first version tried to auto-detect document type from PDF content. That failed badly. A document titled "Individual Practices" might actually be a standing order. A "Local Rules" PDF might contain only criminal rules. We now require manual classification for new document types. It is slower. It is also correct.&lt;/p&gt;

&lt;p&gt;We also underestimated the maintenance burden. Court websites change. Judges retire. New judges get appointed. PDFs get reorganized. We built a system that is 80 percent automated and 20 percent human curation. I thought we could get to 95 percent. I was wrong. The long tail of edge cases is endless.&lt;/p&gt;

&lt;p&gt;Another mistake: we initially stored parsed text as plain strings. When we added full-text search, we realized we had destroyed paragraph structure, footnote references, and table data. We had to re-parse every document with layout preservation. That took three weekends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current numbers
&lt;/h2&gt;

&lt;p&gt;The database covers all 94 federal district courts. We track approximately 680 active district judges. We have parsed and indexed over 4,200 local rules and individual practices documents. The deadline calculator handles 40+ federal rule references with district-specific overrides.&lt;/p&gt;

&lt;p&gt;Traffic is modest but growing. About 12,000 unique visitors per month, mostly litigators, paralegals, and law students. The calculator is the most-used feature. The judge directory gets heavy use from associates who just got case assignments and need to know their judge's quirks.&lt;/p&gt;

&lt;p&gt;We have had zero venture capital. No paid marketing. Growth is entirely organic through legal forums, Reddit, and word of mouth in litigation departments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we keep doing it
&lt;/h2&gt;

&lt;p&gt;The honest answer is that we use it ourselves. My co-founder relies on it for his practice. I rely on it as a sanity check when he asks me whether a deadline calculation looks right. Building tools you actually use changes your priorities. You do not add features because they sound impressive. You add them because you needed them yesterday.&lt;/p&gt;

&lt;p&gt;The federal court system is public, the rules are public, and the judge practices are public. But they are scattered across 94 websites, in formats that resist aggregation. We are just trying to put them in one place, in a form that is actually usable.&lt;/p&gt;

&lt;p&gt;It is not the kind of project that gets written up in TechCrunch. It will not scale to a billion users. But it solves a real problem for a specific group of people. And for a side project, that is enough.&lt;/p&gt;

&lt;p&gt;If you are interested in the technical details or want to contribute, the project is at &lt;a href="https://courtrules.app" rel="noopener noreferrer"&gt;courtrules.app&lt;/a&gt;. We are not open source yet, though we have talked about it. The dataset might be useful to researchers, journalists, or other developers working in the legal space.&lt;/p&gt;

</description>
      <category>legaltech</category>
      <category>database</category>
      <category>javascript</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
