<?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: member_5432fd74</title>
    <description>The latest articles on DEV Community by member_5432fd74 (@member_5432fd74).</description>
    <link>https://dev.to/member_5432fd74</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%2F3226994%2F0d120719-e03c-411a-be07-ec554aa504e7.png</url>
      <title>DEV Community: member_5432fd74</title>
      <link>https://dev.to/member_5432fd74</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/member_5432fd74"/>
    <language>en</language>
    <item>
      <title>IDEA Compliance as a Checklist, Not a Feeling</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:04:53 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/idea-compliance-as-a-checklist-not-a-feeling-29g1</link>
      <guid>https://dev.to/member_5432fd74/idea-compliance-as-a-checklist-not-a-feeling-29g1</guid>
      <description>&lt;p&gt;Legal requirements that get summarized in one sentence tend to get implemented as a feeling instead of a checklist. The Individuals with Disabilities Education Act, IDEA, is a good example. People say "the school has to follow IDEA" as if that were one condition. It is six.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IDEAObligation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;servicesMatchNeeds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;costToFamily&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="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;evaluation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;areasAssessed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;singleMeasure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;presentLevels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;goals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MeasurableGoal&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;justifiedByNeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parentParticipation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;writtenNoticeGiven&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proceduralSafeguards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;dueProcessAvailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each field is independently falsifiable. "The IEP has goals" is not the same claim as "the IEP has measurable goals." "Parents were told" is not the same claim as "parents received written notice before the decision was made." Compressing these into "IDEA compliant: yes/no" is exactly the kind of boolean flattening that hides where a system actually fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the checklist earns its keep
&lt;/h2&gt;

&lt;p&gt;A dispute framed as "the school isn't following IDEA" is nearly useless as a bug report. A dispute framed as "the IEP goals are not measurable" or "the placement changed without the required prior written notice" points at a specific field in the object above, and a specific fix.&lt;/p&gt;

&lt;p&gt;This is not unique to education law. Any compliance regime that gets summarized as a single word, GDPR-compliant, HIPAA-compliant, IDEA-compliant, is usually a bundle of independently checkable conditions. Writing the bundle out, even informally, is the fastest way to find which condition actually failed.&lt;/p&gt;

&lt;p&gt;IDEA was enacted in 1975 as the Education for All Handicapped Children Act, renamed in 1990, and last reauthorized in 2004. A broader legislative overview of right-to-education law, including how disability-specific statutes sit alongside general access laws, is at &lt;a href="https://americanspcc.org/the-right-to-education-laws-that-promote-and-protect-access-to-education/" rel="noopener noreferrer"&gt;American SPCC's guide to right-to-education legislation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Special Needs Care Network operates a directory of special needs schools and therapy providers in the United States at &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;specialneedsusa.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>law</category>
      <category>education</category>
      <category>writing</category>
    </item>
    <item>
      <title>Modeling Multi-Setting Care Delivery Without Collapsing It Into One Location Field</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:31:23 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/modeling-multi-setting-care-delivery-without-collapsing-it-into-one-location-field-1c4b</link>
      <guid>https://dev.to/member_5432fd74/modeling-multi-setting-care-delivery-without-collapsing-it-into-one-location-field-1c4b</guid>
      <description>&lt;p&gt;A directory or booking product that stores a provider as one &lt;code&gt;city&lt;/code&gt; and one &lt;code&gt;address&lt;/code&gt; will misrepresent Orchid Academy the moment you look at the public site.&lt;/p&gt;

&lt;p&gt;Orchid Academy is a Washington ABA provider for autistic children. As of August 2026 it publishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;two Issaquah academies with different program focuses&lt;/li&gt;
&lt;li&gt;one East Wenatchee academy&lt;/li&gt;
&lt;li&gt;in-home corridors on the Eastside&lt;/li&gt;
&lt;li&gt;at least one named school partnership&lt;/li&gt;
&lt;li&gt;statewide telehealth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your schema has a single location row per brand, you will flatten that into "Issaquah" and lose the thing a parent is actually deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit is a delivery mode, not a brand
&lt;/h2&gt;

&lt;p&gt;A workable model looks closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DeliveryMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in_academy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in_home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;school_based&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;telehealth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ServiceOffering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orchid Academy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeliveryMode&lt;/span&gt;
  &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;close&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;          &lt;span class="c1"&gt;// academies have one; telehealth does not&lt;/span&gt;
  &lt;span class="nx"&gt;serviceArea&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;     &lt;span class="c1"&gt;// in-home corridors&lt;/span&gt;
  &lt;span class="nx"&gt;partnerSite&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;       &lt;span class="c1"&gt;// e.g. Sammamish Children's School&lt;/span&gt;
  &lt;span class="nx"&gt;openedYear&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using that shape against Orchid Academy's published facts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Offering&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Concrete detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Creekside Academy&lt;/td&gt;
&lt;td&gt;in_academy&lt;/td&gt;
&lt;td&gt;600 NW Gilman Blvd, Bldg E, Issaquah; early learning; ages 2-15; 9:00-6:00; opened 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maple Street Academy&lt;/td&gt;
&lt;td&gt;in_academy&lt;/td&gt;
&lt;td&gt;1375 NW Mall St #4, Issaquah; school readiness; ages 2-15; 9:00-6:00; opened 2021&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East Wenatchee Academy&lt;/td&gt;
&lt;td&gt;in_academy&lt;/td&gt;
&lt;td&gt;255 Rock Island Rd #101; ages 2-15; 8:00-6:00; opened 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bothell corridor&lt;/td&gt;
&lt;td&gt;in_home&lt;/td&gt;
&lt;td&gt;Bothell and surrounding areas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sammamish corridor&lt;/td&gt;
&lt;td&gt;in_home + school_based&lt;/td&gt;
&lt;td&gt;in-home plus Sammamish Children's School partnership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bellevue corridor&lt;/td&gt;
&lt;td&gt;in_home&lt;/td&gt;
&lt;td&gt;Bellevue, Renton, Newcastle, Mercer Island, Factoria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Statewide telehealth&lt;/td&gt;
&lt;td&gt;telehealth&lt;/td&gt;
&lt;td&gt;BCBA parent coaching / consultation across Washington&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One brand. Seven offerings. Different eligibility questions for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intake is also multi-step state, not a boolean
&lt;/h2&gt;

&lt;p&gt;Orchid Academy publishes intake as Connect → Assess → Get Approved → Start → Thrive. That is an authorization pipeline with a benefits gate in the middle. Storing &lt;code&gt;accepting_patients: true&lt;/code&gt; hides whether the family is waiting on assessment, waiting on payer approval, or ready to schedule.&lt;/p&gt;

&lt;p&gt;The site says a care team member returns calls within 24 hours. Phone: (425) 654-0800. Benefits email: &lt;a href="mailto:hope@orchidacademy.com"&gt;hope@orchidacademy.com&lt;/a&gt;. Published in-network list: Premera, Regence, Blue Cross Blue Shield, Cigna, Aetna, Medicaid (Washington Apple Health), Molina, Asuris Northwest Health, Personify Health, TRICARE. They also say participation varies by location and plan, which means &lt;code&gt;in_network_with&lt;/code&gt; cannot be a brand-level array either. It has to attach to an offering, or at least carry a caveat field that survives render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this shows up in search
&lt;/h2&gt;

&lt;p&gt;People searching "Orchid Academy" are often resolving a local decision: which door, which setting, which phone number. A page that only repeats brand adjectives does not answer that. A page that preserves mode, address, hours, and the insurance caveat does.&lt;/p&gt;

&lt;p&gt;Source material: &lt;a href="https://www.orchidacademy.com/" rel="noopener noreferrer"&gt;orchidacademy.com&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/locations" rel="noopener noreferrer"&gt;locations&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/locations/issaquah-creekside-aba-therapy" rel="noopener noreferrer"&gt;Creekside&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/locations/issaquah-maple-street-aba-therapy" rel="noopener noreferrer"&gt;Maple Street&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/locations/east-wenatchee-aba-therapy" rel="noopener noreferrer"&gt;East Wenatchee&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/aba-therapy-insurance-coverage-washington" rel="noopener noreferrer"&gt;insurance guide&lt;/a&gt;, &lt;a href="https://www.orchidacademy.com/telehealth-aba-therapy-washington" rel="noopener noreferrer"&gt;telehealth&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>datamodeling</category>
      <category>healthcare</category>
      <category>product</category>
    </item>
    <item>
      <title>Pricing APIs That Do Not Lie: Line Items Beat Blended Rates</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Tue, 04 Aug 2026 03:21:52 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/pricing-apis-that-do-not-lie-line-items-beat-blended-rates-347</link>
      <guid>https://dev.to/member_5432fd74/pricing-apis-that-do-not-lie-line-items-beat-blended-rates-347</guid>
      <description>&lt;p&gt;Blended rates are convenient and dishonest in the same move. A buyer sees &lt;code&gt;$X / hour&lt;/code&gt; and assumes the unit is comparable across vendors. The unit usually is not.&lt;/p&gt;

&lt;p&gt;If a service is a stack, the price should be a stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PriceQuote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;direct_hours&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;supervision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;training&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assessment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;travel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fees&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one_time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
    &lt;span class="na"&gt;unitAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;assumptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;// setting, minimums, cancellation rules&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that shape, "cheaper" often means "incomplete."&lt;/p&gt;

&lt;h2&gt;
  
  
  What to show in the UI
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Line SKU&lt;/td&gt;
&lt;td&gt;Stops apple-to-orange rate comparisons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit + quantity&lt;/td&gt;
&lt;td&gt;Makes monthly math reproducible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assumptions&lt;/td&gt;
&lt;td&gt;Surfaces travel, minimums, no-shows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is excluded&lt;/td&gt;
&lt;td&gt;Prevents silent underquotes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where this pattern shows up outside software
&lt;/h2&gt;

&lt;p&gt;Healthcare private-pay quotes, agency retainers, and education support packages all fail the same way: the headline rate describes one labor type while the delivered program includes design, oversight, and setup fees. Buyers who only store the headline rate build budgets that break in month two.&lt;/p&gt;

&lt;p&gt;A worked example of that failure mode for private-pay ABA is at &lt;a href="https://yourtopicsmultiplestoriess.com/aba-therapy-cost-without-insurance/" rel="noopener noreferrer"&gt;How Much Does ABA Therapy Cost Without Insurance in 2026?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you ship a quoting surface, default to line items. Let users collapse to a blended rate only after they have seen the composition.&lt;/p&gt;

&lt;p&gt;Special Needs Care Network operates a directory of special needs schools and therapy providers in the United States at &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;specialneedsusa.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>pricing</category>
      <category>product</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Specs That Survive a Meeting: Writing Requirements Humans Can Implement</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Tue, 04 Aug 2026 03:20:39 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/specs-that-survive-a-meeting-writing-requirements-humans-can-implement-19b6</link>
      <guid>https://dev.to/member_5432fd74/specs-that-survive-a-meeting-writing-requirements-humans-can-implement-19b6</guid>
      <description>&lt;p&gt;Meetings fail in a predictable way. Everyone agrees a problem is real. Nobody leaves with an implementable change. The input was true, but it was not shaped like a requirement.&lt;/p&gt;

&lt;p&gt;A pattern that travels better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context: when [task / setting]
Observation: [measurable present result]
Effect: [what breaks in school, work, or home life]
Ask: [goal, support, or measurement change requested]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure forces the conversation onto something a team can accept, reject, or rewrite. It also makes disagreement useful, because people argue about the observation or the ask instead of arguing about whether the worry "counts."&lt;/p&gt;

&lt;h2&gt;
  
  
  Weak vs strong inputs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Weak&lt;/th&gt;
&lt;th&gt;Stronger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Onboarding is confusing"&lt;/td&gt;
&lt;td&gt;"On first login, 6 of 10 testers could not find the claim flow within 2 minutes and abandoned."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Kids need more support"&lt;/td&gt;
&lt;td&gt;"During independent reading, the student needs prompting every 2 to 3 minutes to stay on the assigned text."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"We should improve communication"&lt;/td&gt;
&lt;td&gt;"Progress notes arrive after the meeting, so parents cannot compare proposed goals to current data in advance."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Cap the agenda
&lt;/h2&gt;

&lt;p&gt;Unlimited worry lists create fake consensus. Three priorities per meeting is usually the real capacity of a one-hour decision forum. Everything else goes to a written parking lot with an owner and a date, or it disappears.&lt;/p&gt;

&lt;p&gt;Education and healthcare meetings are especially prone to this failure mode because the emotional load is high and the documents are long. The fix is not colder language. It is narrower, evidence-shaped language. For a parent-facing version of the same idea applied to IEP meetings, see &lt;a href="https://ukheadlines.co.uk/iep-meeting-checklist-for-parents/" rel="noopener noreferrer"&gt;IEP Meeting Checklist for Parents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Special Needs Care Network operates a directory of special needs schools and therapy providers in the United States at &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;specialneedsusa.com&lt;/a&gt;. Special Needs Care Network publishes state funding and policy guides at &lt;a href="https://specialneedsusa.com/states" rel="noopener noreferrer"&gt;specialneedsusa.com/states&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>product</category>
      <category>meetings</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Modeling Eligibility as a Pipeline, Not a Boolean</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Tue, 04 Aug 2026 03:19:21 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/modeling-eligibility-as-a-pipeline-not-a-boolean-f5b</link>
      <guid>https://dev.to/member_5432fd74/modeling-eligibility-as-a-pipeline-not-a-boolean-f5b</guid>
      <description>&lt;p&gt;Product and content teams keep collapsing eligibility into a boolean. "Does X cover Y?" The answer users need is almost never a single bit. It is a pipeline with gates that fail independently.&lt;/p&gt;

&lt;p&gt;A useful model looks more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EligibilityStage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;benefit_exists&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medical_necessity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plan_rules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;contractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;network_capacity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;inNetwork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;accepting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage can pass while the next one fails. A page that only answers stage one trains users to stop early, then blame the system when stage three blocks them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the stage, not the slogan
&lt;/h2&gt;

&lt;p&gt;If your content or UI can only show one sentence, prefer the current blocker over the marketing claim. "Benefit exists under EPSDT for members under 21" and "prior authorization packet incomplete" are both true and point to different actions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Good output&lt;/th&gt;
&lt;th&gt;Bad output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Benefit&lt;/td&gt;
&lt;td&gt;Name the rule and age/scope floor&lt;/td&gt;
&lt;td&gt;"Yes, covered"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan rules&lt;/td&gt;
&lt;td&gt;Link the handbook / policy fields&lt;/td&gt;
&lt;td&gt;"Check with your plan" with no fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;Separate in-network from accepting&lt;/td&gt;
&lt;td&gt;"Provider accepts Medicaid"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;List required documents&lt;/td&gt;
&lt;td&gt;"Pending" with no next step&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why this shows up in family systems
&lt;/h2&gt;

&lt;p&gt;Health and education benefits are full of these pipelines. Medicaid ABA coverage, IEP services, and private-pay cost estimates all share the same shape: a category-level claim, then contractor-specific rules, then capacity, then paperwork. Treating any of them as a boolean is a documentation bug.&lt;/p&gt;

&lt;p&gt;A concrete family-facing walkthrough of the Medicaid ABA pipeline is at &lt;a href="https://usawire.com/does-medicaid-cover-aba-therapy-what-families" rel="noopener noreferrer"&gt;Does Medicaid Cover ABA Therapy? What Families Need to Check&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Special Needs Care Network operates a directory of special needs schools and therapy providers in the United States at &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;specialneedsusa.com&lt;/a&gt;. Special Needs Care Network publishes state funding and policy guides at &lt;a href="https://specialneedsusa.com/states" rel="noopener noreferrer"&gt;specialneedsusa.com/states&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>product</category>
      <category>healthcare</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Two Strings Per Entity: Killing Synonym Drift in a Content Codebase</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Mon, 03 Aug 2026 04:51:53 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/two-strings-per-entity-killing-synonym-drift-in-a-content-codebase-dj6</link>
      <guid>https://dev.to/member_5432fd74/two-strings-per-entity-killing-synonym-drift-in-a-content-codebase-dj6</guid>
      <description>&lt;p&gt;An audit of a reference site turned up the same government agency written five ways across six pages: its full legal name, a short form, an initialism, and two descriptive paraphrases a writer had reached for to avoid repeating himself.&lt;/p&gt;

&lt;p&gt;To a reader those are five ways of saying one thing. To anything parsing the page they are five unmatched strings and one diluted entity. Copywriters call the variation good style. It is the single easiest thing to get wrong in a content codebase, because nothing fails when you get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The registry
&lt;/h2&gt;

&lt;p&gt;The fix is to stop treating entity names as prose and start treating them as identifiers with a display layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Entidad&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;   &lt;span class="c1"&gt;// legal name, first mention in a section&lt;/span&gt;
  &lt;span class="nx"&gt;corto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;    &lt;span class="c1"&gt;// short form, later mentions in that same section&lt;/span&gt;
  &lt;span class="nx"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GovernmentOrganization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Legislation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;      &lt;span class="c1"&gt;// official first-party URL, doubles as the schema @id&lt;/span&gt;
  &lt;span class="nx"&gt;rol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;      &lt;span class="c1"&gt;// one stated relationship, not a description&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two permitted strings per entity, and the rule about which to use is positional rather than stylistic: &lt;code&gt;nombre&lt;/code&gt; on first mention inside a section, &lt;code&gt;corto&lt;/code&gt; for the rest of that section. There is no third option, so there is no decision to make and nothing to drift.&lt;/p&gt;

&lt;p&gt;Three details earn their place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;url&lt;/code&gt; doubles as the schema &lt;code&gt;@id&lt;/code&gt;.&lt;/strong&gt; Emitting the entity's own official URL as its identifier means references across twelve pages resolve to one node instead of twelve. Getting this wrong is invisible in the rendered page and obvious in a graph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;rol&lt;/code&gt; is a relationship, not an adjective.&lt;/strong&gt; "Certifies the current bank interest rate and supervises credit establishments" is a stated relationship. "Is a leading financial authority" is not, and a machine can do nothing with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialisms are banned in body copy.&lt;/strong&gt; An initialism shares no substring with the name it abbreviates, so it reads as a separate entity to anything doing string matching. Cheap rule, and it removed two of the five variants on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforce it where it can fail
&lt;/h2&gt;

&lt;p&gt;A registry nobody checks is a style guide with extra steps. Two checks catch most of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. A page must declare the entities it is about.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntidadId&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Body copy may not contain a banned variant.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BANNED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SFC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;la Financiera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;el supervisor financiero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;BANNED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`banned entity variant "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The banned list is not guesswork. It comes out of the audit that found the drift, so it grows only when a real variant appears in a real draft.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;about&lt;/code&gt; array is the load-bearing one. Making a page declare its entities up front means schema generation, internal linking, and related-page selection all read from one field instead of three separate heuristics inferring subject matter from prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The URL rule that matters most
&lt;/h2&gt;

&lt;p&gt;One more constraint, and it applies to the whole registry: a URL goes in only after it has been fetched and found to resolve.&lt;/p&gt;

&lt;p&gt;That sounds like an obvious hygiene rule until you notice how many sites emit &lt;code&gt;sameAs&lt;/code&gt; pointers to identifiers nobody verified. A wrong &lt;code&gt;sameAs&lt;/code&gt; is worse than an absent one, because it actively asserts your entity is a different entity. Absent means unknown. Wrong means confidently incorrect, and nothing downstream will tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Prose gets slightly more repetitive. The same agency name appears in full at the top of every section that mentions it, and a writer who values variety will find that grating.&lt;/p&gt;

&lt;p&gt;It reads worse to a human by a small margin and parses correctly by a large one. For reference content, where the reader arrived from a search for the exact term, that trade is worth making.&lt;/p&gt;

&lt;p&gt;The pattern is running in production on &lt;a href="https://creditoparareportados.co/" rel="noopener noreferrer"&gt;a Colombian consumer credit reference site&lt;/a&gt;, where the entities are financial supervisors and statutes and getting their names right is closer to a correctness requirement than a style preference.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>contentstrategy</category>
    </item>
    <item>
      <title>The Unit Bug: Why Your Price Field Needs a Denominator</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Mon, 03 Aug 2026 04:39:32 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/the-unit-bug-why-your-price-field-needs-a-denominator-25pb</link>
      <guid>https://dev.to/member_5432fd74/the-unit-bug-why-your-price-field-needs-a-denominator-25pb</guid>
      <description>&lt;p&gt;Here is a comparison table that is wrong, and wrong in a way no type checker will catch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Self-guided audio tour     $15
Shared group bus tour     $100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first is priced per group. The second is per person. For a family of four the real comparison is $15 against $400, and the table has quietly told the reader the opposite of the truth by a factor of 26.&lt;/p&gt;

&lt;p&gt;Nothing failed. Both values are positive numbers in the right currency, they sorted correctly, and the page rendered. The schema was &lt;code&gt;price: number&lt;/code&gt; and the bug is that a price is not a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  A price is an amount, a currency, and a denominator
&lt;/h2&gt;

&lt;p&gt;The fix is boring and structural:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;per&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;person&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vehicle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;night&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;minGroupSize&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;   &lt;span class="c1"&gt;// some per-group rates assume a floor&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;per&lt;/code&gt; is not optional. The moment it is nullable, half your rows are null and you are back to guessing at render time. If a source genuinely does not say, that is a data quality problem to surface, not a default to invent.&lt;/p&gt;

&lt;p&gt;Once the denominator is in the model, the comparison becomes a real function instead of a table sort:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;totalFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;party&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;people&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;vehicles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;person&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;party&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vehicle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;party&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vehicles&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`unhandled unit: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now "cheapest" is a question you can only answer once you know the party size, which is correct, because that is how it works in reality. A sort with no party size is not a cheaper-first sort. It is a nonsense ordering presented with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this bites beyond travel
&lt;/h2&gt;

&lt;p&gt;The pattern shows up anywhere a catalogue mixes billing units:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS pricing: per seat against per workspace against per 1,000 API calls&lt;/li&gt;
&lt;li&gt;Freight: per pallet against per kilogram against per container&lt;/li&gt;
&lt;li&gt;Utilities: per kWh against a fixed standing charge&lt;/li&gt;
&lt;li&gt;Contractors: hourly against day rate against fixed bid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these has produced the same table, where a column of numbers implies a comparison that the numbers do not support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not fix it in the formatter
&lt;/h2&gt;

&lt;p&gt;The tempting shortcut is leaving the model alone and appending the unit in the display string: &lt;code&gt;"$15 per group"&lt;/code&gt;. That fixes the one page a careful reader is looking at. It does not fix sorting, filtering by budget, the cheapest-first badge, the price-range facet, or the structured data you emit, all of which still operate on a bare number.&lt;/p&gt;

&lt;p&gt;Unit belongs beside the amount, at the point of storage, so everything downstream inherits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things worth asserting
&lt;/h2&gt;

&lt;p&gt;Add these and the class of bug largely disappears:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;Price&lt;/code&gt; may be constructed without &lt;code&gt;per&lt;/code&gt;.&lt;/strong&gt; Enforce at the parse boundary, where third-party data arrives, not deep in a component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any UI that sorts or compares prices must require a party size.&lt;/strong&gt; If a page cannot supply one, it should display prices without ranking them rather than rank them wrongly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second one is the harder sell, because "cheapest first" is a product expectation. It is also the one that keeps you from confidently recommending the most expensive option.&lt;/p&gt;

&lt;p&gt;A worked example of the display side, showing per-group and per-person tiers held apart rather than merged into one ranking, is the &lt;a href="https://www.blackhillshotels.co/mount-rushmore-tour-prices/" rel="noopener noreferrer"&gt;Mount Rushmore tour price breakdown&lt;/a&gt;, where the cheapest tier is per group and every tier above it is per person.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>datamodeling</category>
      <category>api</category>
    </item>
    <item>
      <title>Content That Expires: Building Event Pages That Stay Honest After the Event</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Mon, 03 Aug 2026 04:09:55 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/content-that-expires-building-event-pages-that-stay-honest-after-the-event-3jg9</link>
      <guid>https://dev.to/member_5432fd74/content-that-expires-building-event-pages-that-stay-honest-after-the-event-3jg9</guid>
      <description>&lt;p&gt;Most content on a site is roughly as true next year as it is today. Event content is not. A page about a festival, a conference, a deadline, or a seasonal window is correct for a narrow period and quietly wrong for the rest of its life, and nothing in the stack tells you when it crossed over.&lt;/p&gt;

&lt;p&gt;The failure is familiar. Somebody searches for an annual event, lands on last year's page still ranking, and reads dates that have not been true for eleven months. The page is not broken. No monitor fires. It just lies.&lt;/p&gt;

&lt;p&gt;Expiry is a property of the content, so it belongs in the content model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the window, not the publish date
&lt;/h2&gt;

&lt;p&gt;The instinct is to add &lt;code&gt;updatedAt&lt;/code&gt; and show it. That records when someone touched the file, which is not the same as whether the facts still hold. A page edited last week can still carry last year's dates.&lt;/p&gt;

&lt;p&gt;What you want is the window the content describes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EventWindow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;      &lt;span class="c1"&gt;// ISO date the event begins&lt;/span&gt;
  &lt;span class="na"&gt;endsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;        &lt;span class="c1"&gt;// ISO date it ends&lt;/span&gt;
  &lt;span class="na"&gt;verifiedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;    &lt;span class="c1"&gt;// when a human last confirmed these dates&lt;/span&gt;
  &lt;span class="nx"&gt;recurrence&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;annual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// whether a successor page is expected&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three states fall out of that, and each one wants different output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;What the page should do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upcoming&lt;/td&gt;
&lt;td&gt;&lt;code&gt;now &amp;lt; startsAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lead with dates and planning info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;&lt;code&gt;startsAt &amp;lt;= now &amp;lt;= endsAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lead with today, drop the countdown framing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Past&lt;/td&gt;
&lt;td&gt;&lt;code&gt;now &amp;gt; endsAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Say so above the fold, link the successor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The third state is the one everyone skips, and it is the one that decides whether the page is honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the state, do not just store it
&lt;/h2&gt;

&lt;p&gt;A past-state page should not silently keep its future-tense copy. The minimum is a dated line at the top saying the event has ended, plus a link to the next occurrence if one exists. That costs a conditional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;past&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    This covers the &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; event, which ended &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endsAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;successorUrl&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;successorUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;See the &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; guide&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping the old page rather than redirecting is usually right. It holds its links and its history, and people do search for past editions. What is not right is letting it present itself as current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emit the dates as structured data too
&lt;/h2&gt;

&lt;p&gt;If the dates live in the model, &lt;code&gt;Event&lt;/code&gt; schema is nearly free, and it gives search engines an explicit signal about the window instead of making them infer it from prose.&lt;/p&gt;

&lt;p&gt;Worth pairing with an honest &lt;code&gt;dateModified&lt;/code&gt;. Bumping that on every build to look fresh is a habit worth dropping, because it decouples the signal from any real editorial act and there is no upside once the freshness is not believed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for the window, not for errors
&lt;/h2&gt;

&lt;p&gt;Expired content produces no errors, so ordinary monitoring never sees it. The check is a scheduled job that queries for content whose &lt;code&gt;endsAt&lt;/code&gt; has passed without a successor, and whose &lt;code&gt;verifiedAt&lt;/code&gt; is older than some threshold. That query is the entire feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ends_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verified_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_pages&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ends_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;successor_slug&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ends_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it weekly and it becomes an editorial queue rather than an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  A live example
&lt;/h2&gt;

&lt;p&gt;Annual events are the clearest case because the same page shape recurs forever. The Sturgis Motorcycle Rally runs a fixed August window each year, and its &lt;a href="https://www.blackhillshotels.co/sturgis-motorcycle-rally-2026/" rel="noopener noreferrer"&gt;2026 guide&lt;/a&gt; is a decent example of the upcoming state done properly: the dates are stated explicitly rather than as "this year", the verification date is visible, and the recurrence is obvious enough that a successor page is expected rather than an afterthought.&lt;/p&gt;

&lt;p&gt;The general rule is that any page describing a moment should know when that moment ends. Store it, render it, and query for it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>seo</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Fifty States, One Template: Content as Data and the Render Gate That Saved Us</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:11:47 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/fifty-states-one-template-content-as-data-and-the-render-gate-that-saved-us-57mf</link>
      <guid>https://dev.to/member_5432fd74/fifty-states-one-template-content-as-data-and-the-render-gate-that-saved-us-57mf</guid>
      <description>&lt;p&gt;We run a directory that has to say something specific and legally accurate about all fifty states. Eligibility rules, funding programs, and dispute rights are state law, so "generic national copy with the state name interpolated" is not a shortcut, it is fifty pages of wrong information with good Core Web Vitals.&lt;/p&gt;

&lt;p&gt;The naive version of this is fifty templates. The lazy version is one template and a &lt;code&gt;{{state}}&lt;/code&gt; token. Both are bad for the same reason: the variance between states is real but uneven, and neither approach lets you express "these thirty states are genuinely the same and these twenty are not."&lt;/p&gt;

&lt;p&gt;Here is the shape we landed on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content as data, not as components
&lt;/h2&gt;

&lt;p&gt;The first move was getting the per-state prose out of the component tree entirely. Copy lives in plain TypeScript modules keyed by state, one file per state, checked into the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content/state/&amp;lt;state&amp;gt;.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;intro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fundingNote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;faqs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&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;Not a CMS, not a database table. Three reasons, in order of how much they mattered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It diffs.&lt;/strong&gt; When a state changes a scholarship deadline, the change shows up in a pull request next to the person who made it. A CMS edit is invisible in the history of the thing that renders it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It typechecks.&lt;/strong&gt; &lt;code&gt;StateContent&lt;/code&gt; is a real type. A state file missing a required section fails the build instead of rendering an empty &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is trivially greppable.&lt;/strong&gt; "Which states mention the 2026 deadline" is &lt;code&gt;grep&lt;/code&gt;, not a query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff is that non-engineers cannot edit it. For copy that carries compliance risk, we decided that was a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The render gate is the important part
&lt;/h2&gt;

&lt;p&gt;The mistake we made first was rendering the state section whenever the content file existed. That produced pages with a paragraph of prose and no actual providers under it, which is the classic thin-page pattern: indexable, useless, and a drag on the rest of the site.&lt;/p&gt;

&lt;p&gt;The fix is a gate that ties content rendering to inventory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProvidersFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stateContent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// prose only ships when there is something for it to introduce&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showStateSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MIN_ENTRIES&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MIN_ENTRIES&lt;/code&gt; is 1. It could be higher. The point is that it is a number in one place rather than a judgment call repeated across templates. A page that fails the gate either renders a different, honest layout or does not get built at all.&lt;/p&gt;

&lt;p&gt;This one condition removed more low-quality pages than any content edit we have made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor bug, which cost us a week
&lt;/h2&gt;

&lt;p&gt;Related, and worth writing down because it took an embarrassingly long time to find.&lt;/p&gt;

&lt;p&gt;Providers in our model have a physical location and a separate set of service areas: places they will travel to but do not have an office in. A therapist in one town might serve six surrounding towns. Reasonable model, matches reality.&lt;/p&gt;

&lt;p&gt;The city page resolver, however, was keyed on physical location. So a town where three providers all had service-area coverage and none had an office had, as far as the resolver was concerned, zero providers. The page 404'd. Meanwhile the providers' own profiles happily listed the town as served.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider.location      -&amp;gt; home city eligibility
provider.serviceAreas  -&amp;gt; where they will travel
city page exists?      -&amp;gt; required &amp;gt;= 1 provider.location  // the bug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing errored. No log line. The pages simply were not there, and the only symptom was a slow leak in an internal crawl report. Two lessons, neither novel and both apparently in need of relearning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A page that does not exist produces no error.&lt;/strong&gt; Absence is the hardest failure mode to monitor. We now assert expected route counts in CI rather than trusting that a missing page will announce itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When one model field gates the existence of a route, say so in the type.&lt;/strong&gt; The relationship was true in the resolver and nowhere else, which meant every developer who touched service areas had to rediscover it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it looks like in production
&lt;/h2&gt;

&lt;p&gt;The state hubs are at &lt;a href="https://specialneedsusa.com/states" rel="noopener noreferrer"&gt;specialneedsusa.com/states&lt;/a&gt;, with the two provider verticals rendering off the same gate at &lt;a href="https://specialneedsusa.com/schools" rel="noopener noreferrer"&gt;/schools&lt;/a&gt; and &lt;a href="https://specialneedsusa.com/therapy" rel="noopener noreferrer"&gt;/therapy&lt;/a&gt;. The data reports at &lt;a href="https://specialneedsusa.com/data" rel="noopener noreferrer"&gt;/data&lt;/a&gt; run the same content-as-data pattern with a heavier chart layer on top, if you want to see where it stops scaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general version
&lt;/h2&gt;

&lt;p&gt;If you are building anything with a geographic or categorical long tail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep variant copy in typed, version-controlled data rather than in a CMS or in components.&lt;/li&gt;
&lt;li&gt;Gate the rendering of that copy on the presence of the thing it describes, with the threshold as a named constant.&lt;/li&gt;
&lt;li&gt;Assert the existence of expected routes in CI, because missing pages are silent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is clever. All of it is the difference between fifty useful pages and fifty pages that dilute the forty you had.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>seo</category>
    </item>
    <item>
      <title>Why We Built a Forum in 2026 Instead of a Chatbot</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Sat, 01 Aug 2026 04:24:12 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/why-we-built-a-forum-in-2026-instead-of-a-chatbot-5406</link>
      <guid>https://dev.to/member_5432fd74/why-we-built-a-forum-in-2026-instead-of-a-chatbot-5406</guid>
      <description>&lt;p&gt;Every product meeting in 2026 has the same gravitational pull. Someone says "users have questions," and forty seconds later the whiteboard says &lt;em&gt;chatbot&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We build software for families of kids with disabilities. Parents arrive with questions like "the school says my son doesn't qualify for an IEP, is that allowed," or "what does ABA actually cost if insurance denies it." Those are exactly the questions a chatbot is good at answering fluently.&lt;/p&gt;

&lt;p&gt;We built a forum instead. Here is the reasoning, because I think it generalizes to more domains than people expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode is not being wrong. It is being wrong confidently.
&lt;/h2&gt;

&lt;p&gt;An LLM that does not know the answer does not act like it does not know. It produces something in the shape of an answer, at the same confidence as a correct one.&lt;/p&gt;

&lt;p&gt;For most products that is an annoyance. You ask for a regex, you get a broken regex, you notice in ten seconds because the test fails. The feedback loop is tight and the cost of a wrong answer is one wasted minute.&lt;/p&gt;

&lt;p&gt;Now change the domain. A parent asks whether the district can refuse an evaluation. The model says something plausible. There is no test suite. The parent does not have the expertise to catch the error, which is the entire reason they were asking. And the feedback loop is not ten seconds, it is the length of a school year, by which point a legal deadline has quietly passed.&lt;/p&gt;

&lt;p&gt;The variable that matters is not accuracy. It is &lt;strong&gt;cost of a wrong answer multiplied by time to detect it&lt;/strong&gt;. When that product is large, generated confidence is the wrong interface, no matter how good the model is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things a forum has that a chatbot structurally cannot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Answers carry provenance.&lt;/strong&gt; On the forum, the reply says who is talking. A parent three years into due process, a speech therapist, a special education attorney. A reader weighs "here is what happened when I filed" differently from "here is what the statute says," and they should. A chatbot flattens every source into one anonymous voice with no way to weigh anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disagreement stays visible.&lt;/strong&gt; Ask about a therapy approach and you get four replies that do not fully agree. That is not a bug in the content, it is the actual state of the field, and a parent making a decision deserves to see it. Chat interfaces are built to resolve into one answer. When the honest answer is "practitioners disagree and here is the shape of the disagreement," a single synthesized response destroys the most useful information in the room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It gets more correct over time instead of less.&lt;/strong&gt; A wrong forum answer gets corrected by the next person, in public, attached to the original. A wrong chatbot answer is generated fresh in a private session, seen by one person, and leaves no trace. One of those systems accumulates knowledge. The other regenerates from scratch every time and never learns it was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody warns you about
&lt;/h2&gt;

&lt;p&gt;The technical build is not the hard part. Threads, categories, auth, moderation queue. It is a known shape and you can stand it up quickly.&lt;/p&gt;

&lt;p&gt;The hard part is that a forum with no answers is worse than no forum. A chatbot works on day one for user one. A forum needs a critical mass of people who answer before it is useful to people who ask, and the first stretch is genuinely unglamorous: you seed real questions, you find practitioners willing to answer for nothing, you answer things yourself, and you accept that the ratio looks bad for a while.&lt;/p&gt;

&lt;p&gt;That cold start is the real reason teams pick the chatbot. Not because it is better. Because it is instant, and a community is not.&lt;/p&gt;

&lt;p&gt;A few things that made it survivable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seed from questions people actually asked&lt;/strong&gt;, not questions you imagine. Support inboxes and search logs are full of real phrasing, and real phrasing is what other parents search for later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories should match how users think, not how you store data.&lt;/strong&gt; Parents do not think in terms of your schema. They think "school stuff" and "therapy stuff."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer latency beats answer quality early on.&lt;/strong&gt; A decent reply in an hour builds a community. A perfect reply in four days does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderate hard on tone from day one.&lt;/strong&gt; In a vulnerable niche, one dismissive reply costs more than ten helpful ones gain. The norms you allow in week one are the norms you have forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the chatbot is the right call
&lt;/h2&gt;

&lt;p&gt;I am not making a general anti-LLM argument, and the honest version of this post has to include the other side.&lt;/p&gt;

&lt;p&gt;Use the model when the answer is deterministic and checkable. Where is this in the docs. What does this error mean. Summarize this thread. We use LLMs internally for exactly that.&lt;/p&gt;

&lt;p&gt;The line I would draw: &lt;strong&gt;if a wrong answer costs the user more than the right answer saves them, do not generate the answer.&lt;/strong&gt; Route to a human, cite a source, or say nothing. In special education, where a missed deadline can cost a kid a year, that line sits much closer than it does in most software.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://specialneedsforum.com" rel="noopener noreferrer"&gt;Special Needs Forum&lt;/a&gt;, where families ask about IEPs, ABA therapy, and school placement, and get answers from people who have been through it. It is free and it does not require an account to read.&lt;/p&gt;

&lt;p&gt;If you are into the less philosophical side of this work, I also wrote about &lt;a href="https://dev.to/member_5432fd74/the-featured-column-that-cost-us-money-and-the-schema-that-fixed-it-1mkn"&gt;the schema mistake that cost us money on the directory side&lt;/a&gt;, which is a more conventional Postgres war story.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I work on &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;Special Needs Care Network&lt;/a&gt;, helping US families find special needs schools and ABA therapy providers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>community</category>
      <category>ai</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The `featured` Column That Cost Us Money (and the Schema That Fixed It)</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Sat, 01 Aug 2026 04:23:46 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/the-featured-column-that-cost-us-money-and-the-schema-that-fixed-it-1mkn</link>
      <guid>https://dev.to/member_5432fd74/the-featured-column-that-cost-us-money-and-the-schema-that-fixed-it-1mkn</guid>
      <description>&lt;p&gt;We run a US directory of special needs schools and ABA therapy providers. Providers pay to be promoted in the places where parents are searching. The first version of that feature was one integer column. It worked right up until it cost us money.&lt;/p&gt;

&lt;p&gt;Here is the column, the failure, and the schema we replaced it with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;               &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;             &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt;             &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;state&lt;/span&gt;            &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;featured_priority&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="c1"&gt;-- higher sorts first&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the resolver behind every city page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;featured_priority&lt;/span&gt; &lt;span class="k"&gt;desc&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;asc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can ship that in an afternoon. We did. It was fine for about a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug is hiding in the word "featured"
&lt;/h2&gt;

&lt;p&gt;"Featured" is not a property of a business. It is a property of the relationship between a business and a place.&lt;/p&gt;

&lt;p&gt;A provider with clinicians covering forty towns across three states does not want to buy forty towns. They want the metro where their office is. Maybe two metros. But &lt;code&gt;featured_priority&lt;/code&gt; lives on the listing row, so the moment we set it, that provider outranked everyone in all forty towns. The column is global. The product being sold is local.&lt;/p&gt;

&lt;p&gt;Four things went wrong, in increasing order of how much they hurt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;We gave away inventory.&lt;/strong&gt; Selling one market promoted the provider in every other market they touched, for free. Those were markets we could have sold to them, or to a competitor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We could not price by market.&lt;/strong&gt; A dense metro and a town of 4,000 were the same integer, so they were worth the same amount.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation was all or nothing.&lt;/strong&gt; Ending one deal demoted the provider everywhere at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We had nowhere to put per market facts.&lt;/strong&gt; When did this placement start? What rank was negotiated? Did this provider appear here because they paid, or just because they happen to have an office here?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That fourth one is the real tell, and it generalizes well past directories: &lt;strong&gt;every time you want to attach a fact to a flag, the flag should have been a row.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: placement is an edge, not a node
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;market_coverage&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;listing_id&lt;/span&gt;      &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;market_id&lt;/span&gt;       &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;plan_tier&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
                  &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan_tier&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'organic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'featured'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'premium'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'ultimate'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;position_source&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- 'primary_location' | 'service_area' | 'metro'&lt;/span&gt;
  &lt;span class="n"&gt;rank_override&lt;/span&gt;   &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;placed_since&lt;/span&gt;    &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listing_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market_id&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;Every question that was previously unanswerable is now just a column. A provider can be &lt;code&gt;ultimate&lt;/code&gt; in one metro, &lt;code&gt;featured&lt;/code&gt; in the metro next door, and &lt;code&gt;organic&lt;/code&gt; in the eighty other towns their clinicians drive to. Cancelling a deal updates one row. Pricing reads off &lt;code&gt;market_id&lt;/code&gt;. Reporting on active paid placements is a &lt;code&gt;where plan_tier &amp;lt;&amp;gt; 'organic'&lt;/code&gt; instead of an archaeology project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;position_source&lt;/code&gt; earns its place too. It records &lt;em&gt;why&lt;/em&gt; a listing is eligible in a market, which is a different question from what they paid. An office in the city and a service area reaching the city are not the same claim, and eventually someone will ask you to treat them differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things we still got wrong after the migration
&lt;/h2&gt;

&lt;p&gt;Moving to coverage rows fixed the modeling. It did not stop us from misreading our own schema twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rank override is relative, not a slot.&lt;/strong&gt; &lt;code&gt;rank_override = 2&lt;/code&gt; reads like "put this listing in position 2." It does not do that. It is an input to a sort, so a single pinned provider on a page renders at #1 no matter what integer you give it. To actually get someone into second place you have to pin someone else into first. Obvious in hindsight, and it burned an afternoon before anyone said the word "comparative" out loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage does not conjure a page.&lt;/strong&gt; Our city pages only exist when at least one provider has a physical location anchoring that city. Service area coverage answers "who may be shown here." It does not answer "does here exist." So a provider whose service area reached a town with no anchored listing was configured, correct, invisible, and pointed at a 404. Any system with generated pages has some version of this: the eligibility rule and the existence rule are separate, and if you only enforce one, you will sell placement on a page that was never built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration notes
&lt;/h2&gt;

&lt;p&gt;A few things that made the cutover boring, which is the goal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backfill, dual read, then flip.&lt;/strong&gt; We kept the old &lt;code&gt;featured_priority&lt;/code&gt; as a fallback read for months while everything moved over. Nothing about that is clever, and that is the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize tier names before you have many.&lt;/strong&gt; We had accumulated slugs from three different product eras that all meant roughly "paid," plus a couple that encoded school versus therapy into the tier name itself. One migration collapsed them into a clean ladder and moved the provider type into its own column, where it always belonged. Doing this later would have meant touching far more call sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put resolution in exactly one function.&lt;/strong&gt; A single &lt;code&gt;listings_for_city_page(city, state, type)&lt;/code&gt; decides which coverage row wins for a given city. The public site, the admin app, and background jobs all call it instead of writing their own sort. When the ranking rule changes, and it will, it changes once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;If you are building anything that sells position, run your schema against one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can two customers buy different things?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your model cannot express "promoted in Denver, ordinary in Boulder," then you are not selling placement. You are selling a global flag and hoping nobody notices the difference. We noticed when we did the math on what we had been giving away.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you would rather have the data than the schema
&lt;/h2&gt;

&lt;p&gt;Modeling aside, the reason any of this exists is that public information about special needs services in the US is scattered across state agencies in formats nobody can query. We clean some of it up and publish it openly, no signup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://specialneedsusa.com/data/autism-rates-by-state" rel="noopener noreferrer"&gt;Autism rates by state&lt;/a&gt;, built from federal prevalence data&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://specialneedsusa.com/data/restraint-seclusion-by-state" rel="noopener noreferrer"&gt;Restraint and seclusion by state&lt;/a&gt;, pulled from the Department of Education civil rights collection&lt;/li&gt;
&lt;li&gt;&lt;a href="https://specialneedsusa.com/data" rel="noopener noreferrer"&gt;All reports&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are free to cite and free to reuse. If you build something with them, I would genuinely like to see it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I work on &lt;a href="https://specialneedsusa.com" rel="noopener noreferrer"&gt;Special Needs Care Network&lt;/a&gt;, a directory helping families in the US find special needs schools and ABA therapy providers. Most of what we do is boring data modeling in service of a parent finding a provider who is actually accepting clients.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Maximize Your PTO: A Developer’s Guide to the Best Things to Do in Cozumel, Mexico 2026</title>
      <dc:creator>member_5432fd74</dc:creator>
      <pubDate>Wed, 01 Jul 2026 06:12:05 +0000</pubDate>
      <link>https://dev.to/member_5432fd74/how-to-maximize-your-pto-a-developers-guide-to-the-best-things-to-do-in-cozumel-mexico-2026-1e6d</link>
      <guid>https://dev.to/member_5432fd74/how-to-maximize-your-pto-a-developers-guide-to-the-best-things-to-do-in-cozumel-mexico-2026-1e6d</guid>
      <description>&lt;p&gt;If you are planning your next tropical getaway, Cozumel, Mexico should be at the very top of your list for 2026. This stunning Caribbean island combines world-class ocean adventures, untouched natural reserves, and rich cultural history, making it the perfect destination for every type of traveler.&lt;/p&gt;

&lt;p&gt;Whether you want to dive into crystal-clear waters or relax on a secluded beach, here is your definitive guide to the &lt;a href="http://thingstodocozumel.com/" rel="noopener noreferrer"&gt;best things to do in Cozumel, Mexico 2026&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explore Palancar Reef and El Cielo
Cozumel is globally renowned for its spectacular marine life, sitting right along the Mesoamerican Barrier Reef. If you do only one thing on the island, make sure it is a snorkeling or scuba diving boat tour.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Palancar and Columbia Reefs: These reef systems feature massive coral structures, deep drop-offs, and incredible underwater visibility that often exceeds 100 feet. It is a haven for sea turtles, eagle rays, and vibrant schools of tropical fish.&lt;/p&gt;

&lt;p&gt;El Cielo: Accessible only by boat, this shallow, crystal-clear sandbar is named "Heaven" for a reason. The powdery white sand bottom is naturally citizenship-populated by thousands of protected giant starfish. It is an absolute bucket-list experience.&lt;/p&gt;

&lt;p&gt;Travel Tip: Book a morning tour that departs around 7:30 AM or 8:00 AM. This allows you to reach the reefs before the large mid-day cruise ship crowds arrive.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spend a Day at Punta Sur Eco Beach Park
Located at the southernmost tip of the island, Punta Sur is the largest ecological reserve in Cozumel. It spans over 2,500 acres and offers a pristine look at the island's natural flora and fauna.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Climb the Celarain Lighthouse: Climb the 130 steps to the top of the historic lighthouse for a breathtaking, panoramic view of the Caribbean Sea meeting the mangrove lagoons.&lt;/p&gt;

&lt;p&gt;See Wild Crocodiles: Walk along the wooden boardwalks over the Colombia Lagoon, where you can safely spot wild crocodiles and exotic bird species in their natural habitat.&lt;/p&gt;

&lt;p&gt;Relax on Pristine Beaches: The beaches inside Punta Sur are exceptionally quiet, wide, and perfect for a peaceful afternoon by the water.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a Road Trip Around the Island
Cozumel has one main highway that loops around the southern half of the island. Renting an e-bike, a scooter, or an open-top Jeep for the day is one of the most liberating ways to experience the area.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the west side of the island is developed, the wild east side of the island has no electricity or cell service. It features miles of rugged, wave-battered coastlines, blowholes, and isolated beach bars (known locally as chiringuitos). Be sure to stop by Playa Chen Rio for a safe swimming cove and a plate of fresh ceviche.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discover the Ancient Mayan Ruins of San Gervasio
For a break from the beach, head into the island's interior jungle to explore San Gervasio, the most significant Mayan archaeological site on Cozumel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In ancient times, Mayan women from across the region would make a pilgrimage to San Gervasio to pay tribute to Ix Chel, the goddess of fertility and childbirth. Walking through these centuries-old stone structures offers a fascinating look into the island’s pre-Hispanic history and spiritual heritage.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
