<?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: Felipe Gambetta de Souza</title>
    <description>The latest articles on DEV Community by Felipe Gambetta de Souza (@felipegambettadesouza6jpg).</description>
    <link>https://dev.to/felipegambettadesouza6jpg</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%2F4122528%2F2a41d806-d864-4c84-960d-2519c497fc0e.png</url>
      <title>DEV Community: Felipe Gambetta de Souza</title>
      <link>https://dev.to/felipegambettadesouza6jpg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/felipegambettadesouza6jpg"/>
    <language>en</language>
    <item>
      <title>How I built a semantic layer over Brazil’s official economic data, and why the hardest part wasn’t the API</title>
      <dc:creator>Felipe Gambetta de Souza</dc:creator>
      <pubDate>Sat, 12 Sep 2026 19:19:50 +0000</pubDate>
      <link>https://dev.to/felipegambettadesouza6jpg/how-i-built-a-semantic-layer-over-brazils-official-economic-data-and-why-the-hardest-part-wasnt-16j</link>
      <guid>https://dev.to/felipegambettadesouza6jpg/how-i-built-a-semantic-layer-over-brazils-official-economic-data-and-why-the-hardest-part-wasnt-16j</guid>
      <description>&lt;p&gt;Brazil has a huge amount of high-quality official economic data.&lt;/p&gt;

&lt;p&gt;The problem is that using it programmatically is much harder than it should be.&lt;/p&gt;

&lt;p&gt;The Brazilian Central Bank has one API and data model. IBGE has another. Tesouro has another. SICONFI, Comex Stat, Novo Caged, ANP, EPE and CVM all expose data differently.&lt;/p&gt;

&lt;p&gt;At first, I thought the solution was straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Put all of these sources behind one API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While building it, I realized that API normalization was not actually the hardest problem.&lt;/p&gt;

&lt;p&gt;The hardest problem was preserving &lt;strong&gt;economic meaning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A correct number can still be the wrong answer
&lt;/h2&gt;

&lt;p&gt;Suppose someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the current Selic rate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are several legitimate official series related to Selic.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Selic target defined by Copom;&lt;/li&gt;
&lt;li&gt;the effective Selic rate;&lt;/li&gt;
&lt;li&gt;accumulated monthly Selic;&lt;/li&gt;
&lt;li&gt;annualized variants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them are real official data.&lt;/p&gt;

&lt;p&gt;But returning the wrong one still produces an incorrect answer.&lt;/p&gt;

&lt;p&gt;This became one of the core design principles behind &lt;strong&gt;Open Economics&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of treating search and availability as the same problem, I separated them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Semantic resolution
&lt;/h3&gt;

&lt;p&gt;First, determine what economic concept the user is actually asking for.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;current Selic target rate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;can resolve to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;selic-target&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data availability
&lt;/h3&gt;

&lt;p&gt;Then determine which official datasets actually represent that concept and whether Open Economics can query them correctly.&lt;/p&gt;

&lt;p&gt;For the Selic target, that leads to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bcb-sgs:432&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;which is the official BCB series:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taxa de juros - Meta Selic definida pelo Copom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the right concept is not currently supported, the system should say so.&lt;/p&gt;

&lt;p&gt;It should not silently return a similar-looking series just because it happens to contain the same keywords.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance became part of the data model
&lt;/h2&gt;

&lt;p&gt;Once multiple sources sit behind the same API, abstraction creates another problem.&lt;/p&gt;

&lt;p&gt;You can easily lose answers to basic questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who published this number?&lt;/li&gt;
&lt;li&gt;Which official dataset did it come from?&lt;/li&gt;
&lt;li&gt;What unit does it use?&lt;/li&gt;
&lt;li&gt;What date or period does it represent?&lt;/li&gt;
&lt;li&gt;Was anything transformed?&lt;/li&gt;
&lt;li&gt;Where can I inspect the original source?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Open Economics keeps provenance attached to the result.&lt;/p&gt;

&lt;p&gt;A response can preserve things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;official institution;&lt;/li&gt;
&lt;li&gt;dataset identity;&lt;/li&gt;
&lt;li&gt;source URL;&lt;/li&gt;
&lt;li&gt;metadata URL;&lt;/li&gt;
&lt;li&gt;unit;&lt;/li&gt;
&lt;li&gt;frequency;&lt;/li&gt;
&lt;li&gt;observation date;&lt;/li&gt;
&lt;li&gt;transformations;&lt;/li&gt;
&lt;li&gt;retrieval metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to build another database of copied economic numbers.&lt;/p&gt;

&lt;p&gt;The goal is to provide a consistent interface over official sources without hiding where the data came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I tried making the same layer usable by AI agents
&lt;/h2&gt;

&lt;p&gt;I also wanted agents to query the data directly.&lt;/p&gt;

&lt;p&gt;So I built a remote MCP server.&lt;/p&gt;

&lt;p&gt;It currently exposes &lt;strong&gt;19 tools&lt;/strong&gt; covering semantic discovery, dataset inspection, metadata and data retrieval.&lt;/p&gt;

&lt;p&gt;During testing, I found an interoperability bug that I did not expect.&lt;/p&gt;

&lt;p&gt;The MCP tools worked.&lt;/p&gt;

&lt;p&gt;The client could discover them.&lt;/p&gt;

&lt;p&gt;The calls succeeded.&lt;/p&gt;

&lt;p&gt;But an agent using Cline still couldn't answer something as simple as the current Selic rate.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;The complete result was being returned in:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;structuredContent&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;while the normal textual MCP content contained little more than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Retrieved official BCB observations for bcb-sgs:432.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So technically the MCP call had succeeded.&lt;/p&gt;

&lt;p&gt;But that particular client wasn't exposing the structured result to the model.&lt;/p&gt;

&lt;p&gt;The agent knew that data had been retrieved, but couldn't actually see the values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting the protocol wasn't enough
&lt;/h2&gt;

&lt;p&gt;The fix wasn't to remove structured output.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;structuredContent&lt;/code&gt; is still the canonical machine-readable result.&lt;/p&gt;

&lt;p&gt;Instead, I added a bounded textual representation containing enough information for a model to actually use the result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;values;&lt;/li&gt;
&lt;li&gt;dates;&lt;/li&gt;
&lt;li&gt;units;&lt;/li&gt;
&lt;li&gt;dataset identity;&lt;/li&gt;
&lt;li&gt;official institution;&lt;/li&gt;
&lt;li&gt;provenance;&lt;/li&gt;
&lt;li&gt;official source URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small results can be represented completely.&lt;/p&gt;

&lt;p&gt;Larger results include a limited number of rows and explain that the complete machine-readable result remains available in &lt;code&gt;structuredContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After deploying the change, I repeated the Cline test with an important constraint:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use only Open Economics. No web search, curl, terminal, direct BCB API calls, or external sources.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent successfully:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;resolved the query semantically;&lt;/li&gt;
&lt;li&gt;identified &lt;code&gt;bcb-sgs:432&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;inspected the official metadata;&lt;/li&gt;
&lt;li&gt;retrieved the observations;&lt;/li&gt;
&lt;li&gt;read the actual value and date;&lt;/li&gt;
&lt;li&gt;answered using only Open Economics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That was a useful reminder that protocol compliance and real-world interoperability are not always the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Open Economics looks like today
&lt;/h2&gt;

&lt;p&gt;Open Economics is now a free and open-source layer for Brazilian official economic data.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;h3&gt;
  
  
  REST API
&lt;/h3&gt;

&lt;p&gt;For example, the latest Selic target observation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://open-economics-data.knbf982hkn.chatgpt.site/api/v1/indicators/br-selic-target/latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interactive interface
&lt;/h3&gt;

&lt;p&gt;You can try queries without writing code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://open-economics-data.knbf982hkn.chatgpt.site/en/ask?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;https://open-economics-data.knbf982hkn.chatgpt.site/en/ask?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=launch&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP
&lt;/h3&gt;

&lt;p&gt;Agents can connect directly to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://open-economics-data.knbf982hkn.chatgpt.site/api/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remote MCP server currently exposes 19 tools.&lt;/p&gt;

&lt;p&gt;No API key is required.&lt;/p&gt;

&lt;p&gt;No signup is required.&lt;/p&gt;

&lt;p&gt;Everything is read-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current sources
&lt;/h2&gt;

&lt;p&gt;The project currently integrates data from Brazilian official sources including BCB, IBGE, Tesouro, SICONFI, MDIC / Comex Stat, MTE / Novo Caged, ANP, EPE and CVM.&lt;/p&gt;

&lt;p&gt;It is not intended to pretend every possible Brazilian economic dataset is already normalized.&lt;/p&gt;

&lt;p&gt;One of the design goals is precisely to make missing coverage explicit instead of silently substituting something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source
&lt;/h2&gt;

&lt;p&gt;The code is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/felipegambettadesouza6-jpg/open-economics" rel="noopener noreferrer"&gt;https://github.com/felipegambettadesouza6-jpg/open-economics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a public Postman workspace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.postman.com/open-economics/open-economics" rel="noopener noreferrer"&gt;https://www.postman.com/open-economics/open-economics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the MCP server is published through the official MCP Registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want to test next
&lt;/h2&gt;

&lt;p&gt;At this point, I am less interested in hearing that the project "looks cool" and more interested in finding the cases where the abstraction breaks.&lt;/p&gt;

&lt;p&gt;Especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;revisions;&lt;/li&gt;
&lt;li&gt;tricky date semantics;&lt;/li&gt;
&lt;li&gt;inconsistent units;&lt;/li&gt;
&lt;li&gt;multidimensional datasets;&lt;/li&gt;
&lt;li&gt;similarly named economic series;&lt;/li&gt;
&lt;li&gt;methodology changes;&lt;/li&gt;
&lt;li&gt;provenance;&lt;/li&gt;
&lt;li&gt;queries requiring multiple official sources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work with Brazilian economic data, APIs, data engineering or AI agents:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What official-data query is still unnecessarily painful for you today?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Give me a concrete example.&lt;/p&gt;

&lt;p&gt;I want to run it against Open Economics and find where the system still fails.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>ai</category>
      <category>api</category>
    </item>
  </channel>
</rss>
