<?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: Arritola92</title>
    <description>The latest articles on DEV Community by Arritola92 (@arritola92).</description>
    <link>https://dev.to/arritola92</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%2F4074291%2Fb59c46f0-8f0b-43bc-9650-8298e7a50334.png</url>
      <title>DEV Community: Arritola92</title>
      <link>https://dev.to/arritola92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arritola92"/>
    <language>en</language>
    <item>
      <title>The Fare Observation Schema I'd Build Before a Flight Scraper</title>
      <dc:creator>Arritola92</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:45:03 +0000</pubDate>
      <link>https://dev.to/arritola92/the-fare-observation-schema-id-build-before-a-flight-scraper-2kjn</link>
      <guid>https://dev.to/arritola92/the-fare-observation-schema-id-build-before-a-flight-scraper-2kjn</guid>
      <description>&lt;p&gt;I would design the record before choosing a travel API or public-page collector. A flight price becomes comparable only when its itinerary and fare assumptions are preserved.&lt;/p&gt;

&lt;p&gt;My default output would be a time-stamped observation. A transaction-ready offer would have a separate state and confirmation path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The record carries the query
&lt;/h2&gt;

&lt;p&gt;This illustrative example uses fictional itinerary details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-observation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-public-search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BBB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"departure_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"return_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-08"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"adults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-market"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itinerary_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-reviewed-itinerary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cabin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"economy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"baggage_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not specified in this sample"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"500.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"displayed_trip_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"taxes_included"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"observed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revalidated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I represent monetary values using a decimal-safe storage type, not a binary floating-point value. An unknown tax or baggage condition should remain unknown.&lt;/p&gt;

&lt;p&gt;The example airports and amount are placeholders. They are not a collected fare or a booking offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity requires more than flight number
&lt;/h2&gt;

&lt;p&gt;My itinerary key would reflect legs, dates and times with timezone context, airports, and relevant carrier information. The comparison key would additionally account for passenger assumptions, cabin, and the fare bundle required by the use case.&lt;/p&gt;

&lt;p&gt;I preserve operating and marketing carriers separately when the source exposes them. Similarly, I retain the provider's native offer ID without assuming it is stable across repeated searches.&lt;/p&gt;

&lt;p&gt;Different offers for the same flight can have different conditions. I don't merge them merely because the schedule matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  I use an explicit lifecycle
&lt;/h2&gt;

&lt;p&gt;For page observations, &lt;code&gt;observed&lt;/code&gt; means the collector captured a displayed result. It does not mean the amount is available for purchase.&lt;/p&gt;

&lt;p&gt;For an authorized booking integration, I add states such as &lt;code&gt;revalidated&lt;/code&gt;, &lt;code&gt;expired&lt;/code&gt;, and &lt;code&gt;unavailable&lt;/code&gt;, based on the provider's actual responses. The &lt;a href="https://duffel.com/docs/api/v2/offers" rel="noopener noreferrer"&gt;Duffel offer model&lt;/a&gt; is one reference for understanding that different contract.&lt;/p&gt;

&lt;p&gt;I would never set &lt;code&gt;revalidated&lt;/code&gt; simply because I fetched the same search page again. Confirmation needs the appropriate authoritative flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  My fixture matrix exercises price mapping
&lt;/h2&gt;

&lt;p&gt;I would include a direct trip, a connection, a return journey, different passenger counts, baggage variations, multiple currencies where relevant, and no results.&lt;/p&gt;

&lt;p&gt;For rendered pages, I also test late-loaded cards, market redirects, a consent screen, and a layout containing fares beside ancillary fees. The parser must attach the correct amount to the correct itinerary.&lt;/p&gt;

&lt;p&gt;A no-results response and a failed extraction get different states. Otherwise a parser regression can look like disappearing inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  I compare providers under controlled conditions
&lt;/h2&gt;

&lt;p&gt;I would run equivalent route queries close in time, preserve all relevant parameters, and compare only records meeting the same eligibility rules. My report would include missing comparable results, not merely the rows each source returned.&lt;/p&gt;

&lt;p&gt;I would distinguish observed fare, revalidated offer, and final checkout amount in downstream interfaces. A monitoring dataset may only have the first, which is still useful when labeled accurately.&lt;/p&gt;

&lt;p&gt;For research collection, I would exclude traveler names, account-only data, payment information, and booking records. Those belong to a separate authorized transaction system.&lt;/p&gt;

&lt;p&gt;I accept the first collector when it can explain each amount and its missingness on this fixture matrix. Only after that would I increase routes, schedules, or source coverage.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>data</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>My Data Contract for a Gemini Answer-Observation Pipeline</title>
      <dc:creator>Arritola92</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:44:16 +0000</pubDate>
      <link>https://dev.to/arritola92/my-data-contract-for-a-gemini-answer-observation-pipeline-862</link>
      <guid>https://dev.to/arritola92/my-data-contract-for-a-gemini-answer-observation-pipeline-862</guid>
      <description>&lt;p&gt;I make the observed surface a required field. A generated Gemini API response and a captured consumer-product answer should never become indistinguishable after ingestion.&lt;/p&gt;

&lt;p&gt;The pipeline would have two stages: answer observation and cited-source verification. Each produces evidence with its own timestamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  The observation preserves the experiment
&lt;/h2&gt;

&lt;p&gt;Here is an illustrative record, not output from a provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-observation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemini_api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-provider"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-category-question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Illustrative research prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not_applicable_to_this_example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Illustrative answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"citations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"complete_without_citations"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a consumer-surface observer, I record the available context and explicitly mark unavailable conditions. I don't populate a model ID based on a guess.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://ai.google.dev/gemini-api/docs/google-search" rel="noopener noreferrer"&gt;Gemini Search grounding documentation&lt;/a&gt; is my reference for an official grounded-generation path. It should not be used as evidence that API output reproduces the consumer interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Citation extraction needs its own acceptance test
&lt;/h2&gt;

&lt;p&gt;I preserve citation URL, visible label, position or supported relationship, and any provider reference ID. If the provider does not expose attachment to a passage, I don't invent that mapping.&lt;/p&gt;

&lt;p&gt;My fixtures would include an answer with no citations, one citation, several citations, repeated domains, redirecting URLs, and a brand mentioned only in a cited source.&lt;/p&gt;

&lt;p&gt;The last case checks whether answer-text mentions and source mentions remain separate. A flattened text field can silently turn source material into an apparent generated recommendation.&lt;/p&gt;

&lt;p&gt;I compare normalized URLs while retaining originals. Tracking parameters may be removable, but path or query differences can identify distinct documents or versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  I repeat a versioned prompt panel
&lt;/h2&gt;

&lt;p&gt;A single prompt result is one observation. I create a small panel of relevant factual, category, comparison, and navigational prompts, then repeat part of it across scheduled windows.&lt;/p&gt;

&lt;p&gt;I hold controllable conditions constant and record what cannot be controlled. Prompt wording, provider configuration, parser version, and observation time belong in the experiment history.&lt;/p&gt;

&lt;p&gt;If a provider changes extraction behavior, I would distinguish that event from a change in the answer source. Saving permitted raw outputs and a small reviewed panel helps diagnose the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source support is downstream work
&lt;/h2&gt;

&lt;p&gt;After answer collection, I would fetch authorized public citation URLs and store final URL, fetch time, content hash, and relevant passage evidence. A separate result would say whether the passage supports the specific claim under review.&lt;/p&gt;

&lt;p&gt;I use states such as &lt;code&gt;supported&lt;/code&gt;, &lt;code&gt;not_supported_by_reviewed_passage&lt;/code&gt;, &lt;code&gt;ambiguous&lt;/code&gt;, and &lt;code&gt;source_unavailable&lt;/code&gt;. An unavailable page is not proof of a false citation, and topical relevance is not enough to set &lt;code&gt;supported&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The source fetch timestamp must remain distinct from the answer timestamp. A later edit can change what a reviewer sees.&lt;/p&gt;

&lt;h2&gt;
  
  
  I keep incomplete outcomes visible
&lt;/h2&gt;

&lt;p&gt;Timeout, provider error, partial answer, no answer, and complete answer without citations should have separate states. An empty citations array by itself cannot tell me which condition occurred.&lt;/p&gt;

&lt;p&gt;My metrics would include answer fidelity on the reviewed sample, citation precision, record completeness, failure distribution, and natural variation across repeated prompts. I avoid calling all of these one success rate.&lt;/p&gt;

&lt;p&gt;I also avoid confidential prompts and another user's chats or uploads. The collection scope should match the research question.&lt;/p&gt;

&lt;p&gt;My first milestone would be a small prompt panel whose observations and citations can be manually traced. Automation becomes useful when that trace survives every export and transformation, rather than disappearing behind a visibility score.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>architecture</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
