<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Henry Marinho - Projects</title><description>Data analytics projects and case studies by Henry Marinho.</description><link>https://portfolio.romahomestore.com/</link><item><title>From a 100-Call API to a Public Dashboard: A Production Weather ELT Pipeline for Bucaramanga</title><link>https://portfolio.romahomestore.com/es/blog/bucaramanga-weather-elt-pipeline/</link><guid isPermaLink="true">https://portfolio.romahomestore.com/es/blog/bucaramanga-weather-elt-pipeline/</guid><description>How I built a tested, secure weather ELT pipeline with Airflow, dbt, PostgreSQL and Superset on a free 100-call API, then published a branded, embeddable dashboard.</description><pubDate>Wed, 11 Nov 2026 15:38:11 GMT</pubDate><content:encoded>&lt;p&gt;I wanted a small project that behaves like a real one: data arrives on a schedule, gets transformed and tested, lands in a dashboard people can use, and the whole thing runs on my own server without me babysitting it. The subject is the weather in Bucaramanga, Colombia, the city where I live. The constraint that shaped every decision: a free API that allows &lt;strong&gt;100 calls per month&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This article walks through what I built, the decisions behind it, the bugs I hit, and the data engineering skills each part demonstrates. The live dashboard is embedded at the end.&lt;/p&gt;
&lt;h2&gt;The stack at a glance&lt;/h2&gt;
&lt;p&gt;                                           &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Weatherstack API (free plan)&lt;/td&gt;
&lt;td&gt;Current conditions, air quality and astronomy for one city&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion&lt;/td&gt;
&lt;td&gt;Python + Apache Airflow 3&lt;/td&gt;
&lt;td&gt;Calls the API twice a day and stores the raw response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Raw JSONB payloads and the dbt models, with least-privilege roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation&lt;/td&gt;
&lt;td&gt;dbt (run by Airflow in its own container)&lt;/td&gt;
&lt;td&gt;Typed staging model, four marts, data tests and unit tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BI&lt;/td&gt;
&lt;td&gt;Apache Superset 6.1&lt;/td&gt;
&lt;td&gt;Dashboard defined as code, branded, public and embeddable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI access&lt;/td&gt;
&lt;td&gt;MCP servers (Superset, Airflow, dbt, Postgres)&lt;/td&gt;
&lt;td&gt;Let an AI assistant query and operate the stack through a private gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;td&gt;Docker Compose on a VPS, deployed with Coolify&lt;/td&gt;
&lt;td&gt;Merge to GitHub, the server redeploys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;GitHub Actions, Trivy, Dependabot&lt;/td&gt;
&lt;td&gt;Lint, tests, dbt parse, security scan and a full end-to-end run on every push&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;1. Start with the constraint: audit the API before writing code&lt;/h2&gt;
&lt;p&gt;The first version of the pipeline only stored six fields for New York. Before expanding it, I tested what the free plan really allows with a handful of real requests instead of trusting the pricing page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;/current&lt;/code&gt; endpoint works over HTTPS and returns about 30 fields, including air quality (PM2.5, PM10, ozone, US EPA index) and astronomy (sunrise, sunset, moon phase). We were throwing most of them away.&lt;/li&gt;
&lt;li&gt;Bulk queries (several cities in one call) return error &lt;code&gt;604&lt;/code&gt;. Forecasts return &lt;code&gt;609&lt;/code&gt;. Two calls back to back return &lt;code&gt;106 rate_limit_reached&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That audit turned &quot;we need a paid plan&quot; into &quot;we are wasting 80% of what the free plan already gives us&quot;. It also set the budget: one city, twice a day, 7 a.m. and 7 p.m. local time, about 62 calls a month, leaving room for retries.&lt;/p&gt;
&lt;h2&gt;2. Ingestion: store everything, decide later&lt;/h2&gt;
&lt;p&gt;Instead of adding 25 new columns to the raw table, the ingestion job now stores the &lt;strong&gt;complete API response&lt;/strong&gt; in a &lt;code&gt;JSONB&lt;/code&gt; column, plus a &lt;code&gt;source&lt;/code&gt; flag (&lt;code&gt;api&lt;/code&gt; or &lt;code&gt;mock&lt;/code&gt;). The migration is additive and idempotent, so the existing production table upgrades itself on the next run and the old rows stay valid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE dev.raw_weather_data ADD COLUMN IF NOT EXISTS payload JSONB;
ALTER TABLE dev.raw_weather_data ADD COLUMN IF NOT EXISTS source TEXT;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is schema-on-read: if tomorrow I want visibility or wind direction in a chart, it is a change in dbt, not in the ingestion code, and the history is already there.&lt;/p&gt;
&lt;p&gt;Two guards protect the quota:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A monthly budget check.&lt;/strong&gt; Before each real call, the job counts this month&apos;s successful API rows. At 90, it logs a warning and skips the call instead of failing. Failed calls insert nothing, which is why the limit sits below 100.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pause between calls&lt;/strong&gt; when more than one city is configured, because the free plan rejects back-to-back requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A mock mode returns a real Bucaramanga response with the current timestamp. CI, local development and the first deploy all run the full pipeline without spending a single API call.&lt;/p&gt;
&lt;h2&gt;3. Transformation with dbt: typed, tested, documented&lt;/h2&gt;
&lt;p&gt;The staging model turns the JSON into typed columns. Weatherstack sends some numbers as strings (&lt;code&gt;&quot;7.2&quot;&lt;/code&gt;) and some times as text (&lt;code&gt;&quot;05:41 AM&quot;&lt;/code&gt;, or &lt;code&gt;&quot;No moonrise&quot;&lt;/code&gt; on some days). Two small macros parse them defensively and return &lt;code&gt;NULL&lt;/code&gt; instead of breaking the run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{ safe_numeric(&quot;payload #&amp;gt;&amp;gt; &apos;{current,air_quality,pm2_5}&apos;&quot;) }} as aq_pm2_5,
{{ safe_time(&quot;payload #&amp;gt;&amp;gt; &apos;{current,astro,moonrise}&apos;&quot;) }}      as moonrise,&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rows from before the change have no payload, so the model falls back to the legacy columns and the New York history keeps working. On top of staging there are four marts: observations, daily averages (temperature, humidity, rain, UV, PM2.5), daily air quality and daily astronomy (day length, moon phase).&lt;/p&gt;
&lt;p&gt;Testing is where dbt pays off:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; feed a real JSON payload and a legacy row into the staging model and check the exact output: strings converted to numbers, &lt;code&gt;&quot;yes&quot;&lt;/code&gt; to a boolean, &lt;code&gt;&quot;No moonrise&quot;&lt;/code&gt; to &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data tests&lt;/strong&gt; check ranges (humidity and cloud cover between 0 and 100, EPA index between 1 and 6, UV not negative) and that each city has one row per day in the daily marts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source freshness&lt;/strong&gt; runs after every build and fails if the newest observation is too old.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Orchestration and CI: prove it works before it ships&lt;/h2&gt;
&lt;p&gt;Airflow runs three tasks: ingest, &lt;code&gt;dbt build&lt;/code&gt;, then the freshness check. A failed task sends a webhook alert. On every push, GitHub Actions runs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ruff and 57 pytest tests (ingestion, budget guard, migration, dashboard definitions).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dbt parse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A Trivy scan for vulnerable dependencies, committed secrets and container misconfigurations.&lt;/li&gt;
&lt;li&gt;An integration job that starts Postgres and Airflow from a fresh checkout, runs the whole DAG in mock mode, checks that every mart has data, then takes a backup and restores it to prove the backup is usable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CI caught two real bugs on the way. A pair of double quotes inside a YAML description broke &lt;code&gt;dbt parse&lt;/code&gt;. A unit test compared &lt;code&gt;7.13&lt;/code&gt; with the numeric &lt;code&gt;7.130&lt;/code&gt;, which dbt treats as different. When I could not run Docker locally, I reproduced the CI run with an embedded PostgreSQL and dbt in an isolated virtual environment: 43 of 43 dbt nodes passing before pushing again.&lt;/p&gt;
&lt;h2&gt;5. The dashboard as code, without taking it away from the UI&lt;/h2&gt;
&lt;p&gt;Superset is configured by a script that registers the database, datasets, charts and dashboard through its REST API. It runs automatically after every deploy as a one-shot container. Three design choices made it practical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idempotent and rename-aware.&lt;/strong&gt; When I translated every title to Spanish, existing charts were renamed in place instead of duplicated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The UI wins after creation.&lt;/strong&gt; The script creates what is missing but does not overwrite charts, layout or colors edited by hand. A reset flag restores the repository version when that is what I want.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Labels for humans.&lt;/strong&gt; Columns get readable Spanish names with units, such as &quot;Lluvia total (mm)&quot; or &quot;PM2.5 promedio (µg/m³)&quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. Data storytelling: the Atlético Bucaramanga theme&lt;/h2&gt;
&lt;p&gt;A weather dashboard for Bucaramanga should feel local, so the theme borrows the identity of the city&apos;s football club, Atlético Bucaramanga. Superset 6 themes are Ant Design tokens, so the brand became a design system rather than a pile of CSS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Color roles, not just colors.&lt;/strong&gt; The crest yellow &lt;code&gt;#FFED00&lt;/code&gt; is unreadable as text or as a chart line on a light background, so it became the brand accent (header band, card edges). The away-shirt green &lt;code&gt;#00522C&lt;/code&gt; is the primary color for actions and titles. Ink &lt;code&gt;#1A1A2C&lt;/code&gt; is for text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A chart palette that stays legible&lt;/strong&gt;, with the yellow darkened to gold, plus the crest green, olive and gray.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sports typography:&lt;/strong&gt; Bebas Neue for the headline and big numbers, Oswald for chart titles and Montserrat for everything else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The crest&lt;/strong&gt; is served from our Directus CMS, and the Content Security Policy allows exactly that host.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. Public, embeddable and still locked down&lt;/h2&gt;
&lt;p&gt;To publish the dashboard in this article, I avoided the common shortcut of giving anonymous users the broad &quot;Gamma&quot; role:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anonymous visitors get Superset&apos;s built-in minimal &lt;strong&gt;Public&lt;/strong&gt; role.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard-level access&lt;/strong&gt; (&lt;code&gt;DASHBOARD_RBAC&lt;/code&gt;) means only dashboards tagged with that role, and the data behind their charts, are visible without logging in. SQL Lab, other dashboards and raw datasets stay private.&lt;/li&gt;
&lt;li&gt;A CSP &lt;code&gt;frame-ancestors&lt;/code&gt; rule lets only this website embed it.&lt;/li&gt;
&lt;li&gt;On the website side, the article HTML is sanitized, and iframes are allowed only from an explicit list of hosts and paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. Running it like production&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Least privilege:&lt;/strong&gt; separate Postgres roles for the pipeline, Airflow&apos;s and Superset&apos;s own metadata, and a read-only reader that both the dashboards and the AI tools use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private by default:&lt;/strong&gt; only the dashboard has a public domain. Airflow, Postgres and the MCP servers are reachable only through an SSH tunnel. The server accepts SSH keys only, and unused ports are closed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recoverable:&lt;/strong&gt; daily database backups kept for 7 days, plus a restore test in CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintained:&lt;/strong&gt; Dependabot opens patch-only image updates, and resource limits keep one service from starving the others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging from evidence.&lt;/strong&gt; When the dashboard showed &quot;ChunkLoadError&quot;, the server logs proved the browser never requested the failing file. It was a stale cache after a redeploy, not a broken release.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. Working with AI agents, with a human in charge&lt;/h2&gt;
&lt;p&gt;I built this with Claude Code as a pair. I used parallel agents for ingestion, dbt and Superset, each working from an explicit contract of column names. MCP servers let the assistant query Postgres, trigger Airflow and edit Superset charts directly. AI sped up the typing. The engineering work stayed the same: define the constraints, write the contracts, review every diff, insist on tests, and refuse to ship until CI was green. When an agent&apos;s work broke the build, the tests caught it, not the dashboard&apos;s users.&lt;/p&gt;
&lt;h2&gt;Skills this project demonstrates&lt;/h2&gt;
&lt;p&gt;                                               &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Evidence in this project&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API integration under constraints&lt;/td&gt;
&lt;td&gt;Audited the free plan with real calls; budget guard, rate-limit pause, mock mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data modeling&lt;/td&gt;
&lt;td&gt;Raw JSONB plus typed staging and four marts; schema-on-read; backward-compatible migration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dbt&lt;/td&gt;
&lt;td&gt;Macros, unit tests, generic and singular data tests, source freshness, documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;Airflow 3 DAG with retries, timeouts, failure alerts and dbt in an isolated container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing and CI/CD&lt;/td&gt;
&lt;td&gt;57 Python tests, an end-to-end DAG run and a backup restore test on every push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BI as code and data storytelling&lt;/td&gt;
&lt;td&gt;Idempotent Superset setup, design tokens, accessible palette, localized labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Least-privilege roles, CSP, dashboard-level RBAC, HTML sanitization, Trivy, private services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;td&gt;Docker Compose, Coolify deploys from GitHub, VPS hardening, backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI-assisted engineering&lt;/td&gt;
&lt;td&gt;MCP servers for the data stack, parallel agents, human review and test gates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;The live dashboard&lt;/h2&gt;
&lt;p&gt;This is the real dashboard, updated at 7 a.m. and 7 p.m. Bogotá time. Use the filter to explore, or open it on its own page.&lt;/p&gt;
&lt;figure&gt;    
&lt;figcaption&gt;Live Superset dashboard: temperature, humidity, rain, UV, air quality, and sun and moon for Bucaramanga.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The code is open on GitHub, including the dbt models, the Airflow DAG, the Superset setup script and the CI pipeline: &lt;a href=&quot;https://github.com/henrymarinho90/weather-data-project&quot;&gt;henrymarinho90/weather-data-project&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Ecommerce Returns &amp; Profitability Analytics</title><link>https://portfolio.romahomestore.com/es/blog/ecommerce-returns-profitability-analytics/</link><guid isPermaLink="true">https://portfolio.romahomestore.com/es/blog/ecommerce-returns-profitability-analytics/</guid><description>End-to-end ecommerce analytics project focused on returns, profitability, product performance, acquisition quality, and fulfillment. I built the analytical pipeline from PostgreSQL and Python through reusable data marts and an interactive Metabase dashboard, then reconciled dashboard metrics directly against SQL before publishing the final analysis.</description><pubDate>Sat, 03 Oct 2026 18:56:27 GMT</pubDate><content:encoded>&lt;h2&gt;Ecommerce Returns &amp;amp; Profitability Analytics&lt;/h2&gt;
&lt;p&gt;I built an end-to-end ecommerce analytics project to understand how revenue, returns, product performance, traffic sources, and fulfillment affect profitability.&lt;/p&gt;
&lt;p&gt;The project goes beyond creating a dashboard. I built the data pipeline from PostgreSQL and SQL analysis through Python exploration, reusable analytics marts, Metabase visualization, and final SQL validation.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a href=&quot;http://bi.romahomestore.com/public/dashboard/91e02296-ee2e-41c3-b389-84115d1771c3&quot; target=&quot;_blank&quot;&gt;Interactive Dashboard&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;http://bi.romahomestore.com/public/dashboard/91e02296-ee2e-41c3-b389-84115d1771c3 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Use the filters to explore performance by date, category, brand, and traffic source.&lt;/p&gt;
&lt;div&gt; &lt;/div&gt;
&lt;p&gt;&lt;em&gt; The dashboard is powered by analytics marts built in PostgreSQL and validated directly against SQL before publication. &lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;What I Built&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Loaded and modeled ecommerce data in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Created relational tables, keys, joins, and an enriched analytical layer.&lt;/li&gt;
&lt;li&gt;Used Python, pandas, and Jupyter for exploratory and statistical analysis.&lt;/li&gt;
&lt;li&gt;Built reusable analytics marts for business intelligence.&lt;/li&gt;
&lt;li&gt;Created an interactive Metabase dashboard with global filters.&lt;/li&gt;
&lt;li&gt;Validated dashboard calculations directly against PostgreSQL queries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Analytics Architecture&lt;/h3&gt;
&lt;pre&gt;Raw Ecommerce Data
        ↓
PostgreSQL
        ↓
Relational / Enriched Data Model
        ↓
SQL + Python Analysis
        ↓
Analytics Marts
        ↓
Metabase Dashboard
        ↓
SQL QA &amp;amp; Reconciliation
&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Key Results&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$5.74M&lt;/strong&gt; analyzed revenue&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;66,019&lt;/strong&gt; orders&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10.16%&lt;/strong&gt; item return rate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;51.91%&lt;/strong&gt; margin rate proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Revenue increased considerably over the analyzed period while the overall return rate remained relatively stable around 10%.&lt;/p&gt;
&lt;p&gt;Search generated approximately &lt;strong&gt;$4.05M&lt;/strong&gt;, or about &lt;strong&gt;71% of analyzed revenue&lt;/strong&gt;. Its return rate, however, was similar to the other major traffic sources, suggesting that its larger return exposure was primarily a result of its much larger sales volume.&lt;/p&gt;
&lt;p&gt;I also tested whether longer delivery times were associated with more returns. The item-level correlation between ship-to-deliver time and return behavior was approximately &lt;strong&gt;0.0027&lt;/strong&gt;, indicating essentially no meaningful linear relationship in this dataset.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;A Data Modeling Problem I Had to Solve&lt;/h3&gt;
&lt;p&gt;One of the most useful challenges came from the Top Products dashboard.&lt;/p&gt;
&lt;p&gt;My original product mart was aggregated too early. It worked for a static chart, but once I added dashboard filters for date, category, brand, and traffic source, the BI tool no longer had the dimensions required to correctly recalculate the product rankings.&lt;/p&gt;
&lt;p&gt;I redesigned the mart to preserve those dimensions and moved the Top 25 ranking to the dashboard layer so products are ranked &lt;strong&gt;after filters are applied&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I also stored additive components such as revenue, margin, returned items, and order items instead of relying on pre-calculated percentages. This allows metrics such as return rate and margin rate to be recalculated correctly for any filtered population.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Quality Assurance&lt;/h3&gt;
&lt;p&gt;I did not consider the dashboard complete just because the charts looked correct. I reconciled Metabase against PostgreSQL for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Revenue, orders, return rate, and margin rate&lt;/li&gt;
&lt;li&gt;Date, category, brand, and traffic-source filters&lt;/li&gt;
&lt;li&gt;Category × brand analysis&lt;/li&gt;
&lt;li&gt;Margin burn calculations&lt;/li&gt;
&lt;li&gt;Traffic-source metrics&lt;/li&gt;
&lt;li&gt;Fulfillment-time bins&lt;/li&gt;
&lt;li&gt;Top-product rankings and profitability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final dashboard values reconciled with the PostgreSQL source of truth.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Skills Demonstrated&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SQL · PostgreSQL · Python · pandas · Jupyter · Data Modeling · Data Marts · ETL/ELT · Metabase · Business Intelligence · Data Visualization · Statistical Analysis · Data Validation · Git · GitHub · Data Storytelling&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;What I Learned&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Analytical grain directly affects what a BI dashboard can do.&lt;/li&gt;
&lt;li&gt;Ratios should often be recalculated from additive components after filtering.&lt;/li&gt;
&lt;li&gt;Updating SQL in Git does not automatically update the database.&lt;/li&gt;
&lt;li&gt;Metric definitions must remain consistent across every layer of the stack.&lt;/li&gt;
&lt;li&gt;A dashboard should be validated against its database source before it is trusted.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/henrymarinho90/ecommerceproject&quot; target=&quot;_blank&quot;&gt; &lt;strong&gt;View the project on GitHub →&lt;/strong&gt; &lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Beyond Syntax: How I Used &apos;Vibe Coding&apos; to Build an Enterprise-Grade Data Cleaning Pipeline in Google Sheets</title><link>https://portfolio.romahomestore.com/es/blog/google-sheets-cleaning-algorithm/</link><guid isPermaLink="true">https://portfolio.romahomestore.com/es/blog/google-sheets-cleaning-algorithm/</guid><description>Discover how Vibe Coding transforms data analysis. See how I automated a complex CRM data cleaning pipeline in Google Sheets using AI-assisted Google Apps Script, reducing hours of manual work to seconds.</description><pubDate>Wed, 14 Jan 2026 18:54:49 GMT</pubDate><content:encoded>&lt;header&gt;
&lt;h1&gt;Beyond Syntax: How I Used &apos;Vibe Coding&apos; to Build an Enterprise-Grade Data Cleaning Pipeline in Google Sheets&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Meta Description:&lt;/strong&gt; Discover how Vibe Coding transforms data analysis. See how I automated a complex CRM data cleaning pipeline in Google Sheets using AI-assisted Google Apps Script, reducing hours of manual work to seconds.&lt;/p&gt;
&lt;/header&gt;&lt;hr /&gt;
&lt;h2&gt;The Business Challenge: CRM Data Normalization Nightmare&lt;/h2&gt;
&lt;p&gt;Every data analyst knows the pain. You have three different data sources, and they all speak a different language.&lt;/p&gt;
&lt;p&gt;For this project, the objective was clear but the path was messy. We needed to consolidate monthly sales reports from three major automotive CRMs into a single, unified format for Meta Conversion API (CAPI) upload.&lt;/p&gt;
&lt;h3&gt;The Constraints:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source 1 (VinSolutions):&lt;/strong&gt; Inconsistent address columns and date formats.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source 2 (DriveCentric):&lt;/strong&gt; Missing &quot;Gross Profit&quot; columns and messy headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source 3 (eLead):&lt;/strong&gt; The header row wasn&apos;t even on the first line (it was on row 5), and names were combined in a single cell (Last, First).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Goal:&lt;/strong&gt; A clean CSV with 15 specific columns (e.g., &lt;code&gt;event_time&lt;/code&gt;, &lt;code&gt;event_name&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;order_id&lt;/code&gt;) formatted strictly (dates as &lt;code&gt;YYYY/MM/DD&lt;/code&gt;, Zips trimmed to 5 digits, Phones normalized to &lt;code&gt;+1XXXXXXXXXX&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manual cleaning would take hours per file. Using standard &lt;strong&gt;Google Apps Script Automation&lt;/strong&gt;, I built a tool that does it in seconds.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What is Vibe Coding in the Context of Data Analytics?&lt;/h2&gt;
&lt;p&gt;Before diving into the code, we must define the methodology. &lt;strong&gt;Vibe Coding&lt;/strong&gt; is continuous iteration with an LLM to produce code that feels right—it handles edge cases, includes error logging, and creates a user-friendly UI.&lt;/p&gt;
&lt;p&gt;Unlike the generic snippets found in articles like &lt;em&gt;5 JavaScript Data Cleaning Patterns&lt;/em&gt;, Vibe Coding allows us to handle context. It allows us to say, &quot;The Zip code column keeps reverting to currency format because of ghost formatting; fix it.&quot;&lt;/p&gt;
&lt;p&gt;The result is not just a script; it is a software application living inside a spreadsheet.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Solution: A Universal Bulk Automation Tool&lt;/h2&gt;
&lt;p&gt;The final product was a menu-driven interface inside Google Sheets entitled &quot;⭐ Drive Manager.&quot; It allows the user to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Scan Google Drive folders for the latest files.&lt;/li&gt;
&lt;li&gt;Detect which CRM the file belongs to (Vin, DriveCentric, or eLead).&lt;/li&gt;
&lt;li&gt;Apply the specific cleaning logic required for that CRM.&lt;/li&gt;
&lt;li&gt;Calculate average gross profit to fill in missing data gaps.&lt;/li&gt;
&lt;li&gt;Export the clean data to a tracking sheet.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. The Traffic Controller (CRM Detection)&lt;/h3&gt;
&lt;p&gt;The core of the script is a function I call the &quot;Traffic Controller.&quot; It reads a &quot;CRM&quot; column in our master list and routes the file to the correct cleaning engine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Pseudo-code representation of the routing logic
if (crm.includes(&quot;vin&quot;)) {
   cleanVinSheet(targetSheet, avgGross);
} 
else if (crm.includes(&quot;drive&quot;)) {
   cleanDriveCentricSheet(targetSheet, avgGross);
} 
else if (crm.includes(&quot;elead&quot;)) {
   cleanEleadSheet(targetSheet, avgGross);
} &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures that we aren&apos;t applying VinSolutions logic (which expects headers on row 1) to eLead files (where headers are on row 5).&lt;/p&gt;
&lt;h3&gt;2. The &quot;Nuclear Option&quot; for Data Hygiene&lt;/h3&gt;
&lt;p&gt;One of the most persistent issues in &lt;strong&gt;Excel to Google Sheets workflows&lt;/strong&gt; is &quot;ghost formatting.&quot; We found that when converting &lt;code&gt;Total Gross&lt;/code&gt; (Currency) columns into &lt;code&gt;Zip Code&lt;/code&gt; columns during column reordering, the spreadsheet would force the Zip codes into currency format (e.g., turning &lt;code&gt;65010&lt;/code&gt; into &lt;code&gt;$65,010.00&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Through Vibe Coding, we developed &lt;code&gt;reorderColumnsNuclear&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of simply moving columns, this function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reads the data into memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wipes the entire sheet clean&lt;/strong&gt; (Content and Formatting).&lt;/li&gt;
&lt;li&gt;Sets the entire sheet format to &lt;code&gt;@&lt;/code&gt; (Plain Text) to create a &quot;defense shield&quot; against auto-formatting.&lt;/li&gt;
&lt;li&gt;Writes the data back.&lt;/li&gt;
&lt;li&gt;Selectively applies Date formatting (&lt;code&gt;YYYY/MM/DD&lt;/code&gt;) only to the specific date column.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Handling Edge Cases: The Logic Injection&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Avg Gross&quot; Injection:&lt;/strong&gt; We encountered files where the &lt;code&gt;Value&lt;/code&gt; column was empty or contained negative numbers (which breaks Meta ads optimization). The script automatically fetches a calculated &quot;Average Gross&quot; from a separate &quot;Tracker&quot; tab and injects it into any cell with missing or invalid data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phone Number Normalization:&lt;/strong&gt; A Regex cleaner iterates through &lt;code&gt;Cell&lt;/code&gt;, &lt;code&gt;Home&lt;/code&gt;, and &lt;code&gt;Work&lt;/code&gt; phones, stripping non-integers and formatting them to &lt;code&gt;+1...&lt;/code&gt; for perfect matching rates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zip Trimming:&lt;/strong&gt; eLead files often included 9-digit zips (e.g., &lt;code&gt;12345-6789&lt;/code&gt;). The script detects this and trims them to the standard 5 digits required for matching.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;The Results&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Efficiency:&lt;/strong&gt; Reduced monthly reporting time from ~4 hours to &amp;lt; 5 minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accuracy:&lt;/strong&gt; Eliminated human error in column mapping and date formatting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; The script handles bulk operations, processing 50+ files in a single run without user intervention.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Data cleaning doesn&apos;t have to be a chore. With the right approach to &lt;strong&gt;AI-Assisted Google Sheets Automation&lt;/strong&gt;, we can build tools that are robust, scalable, and incredibly fast. &quot;Vibe Coding&quot; isn&apos;t just a buzzword; it&apos;s a methodology that allows data analysts to deliver value exponentially faster.&lt;/p&gt;
&lt;div&gt;
&lt;h3&gt;Let&apos;s Automate Your Workflow&lt;/h3&gt;
&lt;p&gt;Are you looking for a Data Analyst who combines technical depth with modern AI workflows to solve business problems?&lt;/p&gt;
&lt;a href=&quot;https://www.linkedin.com/in/henrymarinho90/&quot; target=&quot;_blank&quot;&gt; Connect with me on LinkedIn &lt;/a&gt;
&lt;p&gt;Or email me directly at: &lt;strong&gt;henrymarinho90@gmail.com&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;</content:encoded></item><item><title>The Rise of Vibe Coding in Data Analytics: Building a Full-Stack Portfolio with AI and Intent</title><link>https://portfolio.romahomestore.com/es/blog/vibe-coding-a-portfolio-for-data-analytics/</link><guid isPermaLink="true">https://portfolio.romahomestore.com/es/blog/vibe-coding-a-portfolio-for-data-analytics/</guid><description>Discover how &quot;Vibe Coding&quot; is revolutionizing how Data Analysts build high-performance portfolios. Learn to integrate Metabase, Directus, and Astro using AI-driven development to impress top tier recruiters.</description><pubDate>Wed, 14 Jan 2026 03:42:08 GMT</pubDate><content:encoded>&lt;header&gt;
&lt;p&gt;Expert Analysis&lt;/p&gt;
&lt;h1&gt;The Future of Decision Intelligence: Mastering Vibe Coding to Architect Elite Data Analytics Portfolios&lt;/h1&gt;
&lt;/header&gt;
&lt;p&gt;In the rapidly evolving landscape of 2026, the traditional definition of a &quot;Data Analyst&quot; has undergone a radical transformation. The barrier between data insight and technical execution has all but vanished. Today, the most sought-after professionals are &lt;strong&gt;Systems Thinkers&lt;/strong&gt; who can architect entire data ecosystems.&lt;/p&gt;
&lt;p&gt;This shift hasn&apos;t occurred because analysts suddenly mastered CSS grids or backend management. Instead, we have entered the era of &lt;strong&gt;Vibe Coding&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;
&lt;h4&gt;Recruiter&apos;s Perspective&lt;/h4&gt;
&lt;p&gt;This portfolio itself is a living proof of concept. It was architected using &lt;strong&gt;Astro&lt;/strong&gt;, &lt;strong&gt;Directus&lt;/strong&gt;, and &lt;strong&gt;Metabase&lt;/strong&gt;, orchestrated through AI-driven development to demonstrate maximum technical fluency and efficiency.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;What is Vibe Coding? A New Paradigm for Data Professionals&lt;/h2&gt;
&lt;p&gt;Vibe Coding is a philosophy where the professional acts as a &lt;strong&gt;Director&lt;/strong&gt; rather than a typist. It is the art of using high-level AI orchestration—leveraging tools like Cursor, Claude 3.5, and GPT-5—to build complex infrastructures through pure &quot;vibe&quot; or vision.&lt;/p&gt;
&lt;p&gt;For a Data Analyst, this means a transition from syntax-focused labor to &lt;strong&gt;intent-focused architecture&lt;/strong&gt;. We focus on the logic of the data and the user experience, allowing AI to handle the boilerplate execution.&lt;/p&gt;
&lt;h3&gt;Beyond the Static Resume&lt;/h3&gt;
&lt;p&gt;A &quot;Vibe Coded&quot; portfolio demonstrates that you can manage modern tools at scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Headless CMS:&lt;/strong&gt; Orchestrating dynamic content via &lt;a href=&quot;https://directus.io&quot; target=&quot;_blank&quot;&gt;Directus&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static Site Generation:&lt;/strong&gt; Leveraging &lt;a href=&quot;https://astro.build&quot; target=&quot;_blank&quot;&gt;Astro&lt;/a&gt; for lightning-fast SEO performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive BI:&lt;/strong&gt; Integrating &lt;a href=&quot;https://www.metabase.com&quot; target=&quot;_blank&quot;&gt;Metabase&lt;/a&gt; for real-time data storytelling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why Recruiters Care About Your Technical &quot;Vibe&quot;&lt;/h2&gt;
&lt;p&gt;Top-tier firms are looking for &lt;strong&gt;Technical Fluency&lt;/strong&gt;. When an analyst presents a portfolio built with a modern &quot;Bento Grid&quot; stack, it signals an understanding of the &lt;strong&gt;Full-Stack Data Lifecycle&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As an &lt;strong&gt;Industrial Engineer (UIS)&lt;/strong&gt;, my approach is rooted in optimization and high-performance leadership. Building through Vibe Coding allows me to apply these principles directly to software and data architecture, bridging the gap between operations and IT.&lt;/p&gt;
&lt;h2&gt;Interactive Case Study: Used Cars in USA Analysis&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Used Cars in USA&lt;/strong&gt; analysis project demonstrates a complete automated pipeline: from Python-based data cleaning to interactive deployment.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;live_dashboard_v2.sql&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Future-Proofing Your Career with AI&lt;/h2&gt;
&lt;p&gt;The reality of 2026 is simple: &lt;strong&gt;Analysts who use AI will take the jobs of those who don&apos;t.&lt;/strong&gt; Vibe Coding allows me to do the work of a three-person development team, focusing my energy on &lt;strong&gt;Requirement Engineering&lt;/strong&gt; and &lt;strong&gt;Actionable Insights&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By mastering the &quot;Vibe,&quot; we ensure we are not just spreadsheet jockeys, but architects of the modern data stack.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;
&lt;h2&gt;Ready to Elevate Your Data Strategy?&lt;/h2&gt;
&lt;p&gt;I am currently open to new challenges in &lt;strong&gt;Data Analytics, Project Management, and AI-Driven Development&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;a href=&quot;https://www.linkedin.com/in/henrymarinho90/&quot; target=&quot;_blank&quot;&gt; LinkedIn Profile ↗ &lt;/a&gt; &lt;a href=&quot;mailto:henrymarinho90@gmail.com&quot;&gt; Email Me Directly &lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;</content:encoded></item><item><title>Used cars in USA Analytis</title><link>https://portfolio.romahomestore.com/es/blog/analisis-autos-usa/</link><guid isPermaLink="true">https://portfolio.romahomestore.com/es/blog/analisis-autos-usa/</guid><description>Analytics review of used cars sourcing for the United states automotive market	</description><pubDate>Tue, 13 Jan 2026 15:30:01 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;used-cars-analysis&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;&amp;lt;iframe   src=&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item></channel></rss>