Portfolio work built on synthetic or public data. Client and employer projects remain private.
An STR-style monthly performance readout for an urban hotel: occupancy, ADR, RevPAR, and market share indices against a competitive set, built from 119,000 real bookings through a bronze/silver/gold Delta pipeline on Databricks.
Why this oneReading an STR report is a named skill in hotel revenue analyst postings, so this project uses that vocabulary throughout: RevPAR index, MPI, ARI, comp set, pace, pickup. Real booking data runs through a Databricks lakehouse built the way a production one would be, and the output is the report a GM receives each month, plus a one-page memo on what to do about it. The comp set is simulated from the source data and labeled as such on every exhibit.
Recommendation: hold rate. We are out-earning the comp set on RevPAR because our rate premium more than covers an occupancy gap, and the gap is specific: midweek, short-lead, corporate-adjacent business that the discounter across the street is buying. Close it with targeted corporate rate fences and a two-week booking window offer rather than blanket discounting. Cutting rate to chase MPI would give up rate we already earn to buy occupancy we can win more cheaply.
RevPAR Generation Index, trailing twelve months. Above 100 means the property earns more per available room than its comp set, so it is gaining share rather than riding the market.
ADR index. The share gain is coming entirely from rate. The property prices above the comp set and the market keeps paying it.
Occupancy index, and the warning in the readout. The rate premium costs heads in beds, and the gap concentrates midweek, where the discounter comp is buying occupancy.
119K raw bookings land in a Unity Catalog volume and are written to a bronze Delta table untouched, with an ingestion timestamp added. Bronze is just the landing zone.
Arrival dates assembled from parts, cancellations typed, zero-guest and negative-rate rows dropped, the famous €5,400 ADR outlier capped, market segments normalized to five clean values. Data-quality checks stop the run if any of those rules fail.
A readout needs a market to compare against, and the source data has none. Four competitor hotels are simulated from the subject's own daily history: one prices above the market, one discounts to fill rooms, one runs on group business, one tracks the average. Their results roll up room-weighted, the same way STR builds a comp set.
Stay-night explosion turns bookings into daily occupancy, ADR, and RevPAR. Monthly rollups add MPI, ARI, and RGI. Booking pace comes from real lead times: rooms on the books at 120, 60, 30 days out.
End-to-end cloud data pipeline pulling live stock data, processing it through AI, and visualizing trends in a Delta Lake data lakehouse.
Stock analytics is the classic first portfolio project. This one runs on real cloud infrastructure end to end, uses an LLM (Claude via AWS Bedrock) for the analysis layer, and stores the data in a lakehouse with ACID transactions instead of a local CSV.
Python pulls 6 months of daily price data for AAPL, TSLA, AMZN, GOOGL, MSFT via Yahoo Finance API. Cleaned, normalized, uploaded to S3 as structured CSV.
Lambda triggers on new S3 data. Reads CSV, sends to Claude via AWS Bedrock for pattern analysis, writes AI-generated insights back to S3.
Data loaded into Databricks and saved as a Delta Lake table with ACID transactions, schema enforcement, and time travel, so any earlier version can be restored.
Databricks SQL surfaces monthly trends, price ranges, best and worst trading days, year-over-year comparisons. Rendered as interactive dashboards.
Probabilistic CLV predictions for a synthetic golf ecommerce retailer. Turned 65,000 transactions into per-customer 12-month revenue forecasts using RFM features and BG/NBD + Gamma-Gamma models.
Tour Enthusiasts outspend Weekend Golfers by 10.2x in predicted lifetime value, and the top two segments drive 70% of forecasted revenue from just 32% of the customer base. They cost about the same to acquire.
Predicted next-12-month revenue, calculated one customer at a time. Drives acquisition budget and retention priorities.
Built a realistic golf ecommerce dataset in Python. 65,000 transactions across 5,000 customers and six behavioral segments, with seasonality, churn patterns, and upgrade cycles baked in.
Raw transactions uploaded to S3 and loaded into Databricks as a Delta table with schema enforcement, time travel, and ACID transactions.
SQL aggregations turn raw orders into per-customer Recency, Frequency, and Monetary features. Silver layer table, one row per customer, ready for modeling.
Industry-standard probabilistic CLV models. BG/NBD predicts future transaction counts. Gamma-Gamma predicts order value. Combined, they output expected 12-month revenue per customer.
A personal 5-year DCF valuation of Lululemon Athletica (NASDAQ: LULU), pulled from live SEC EDGAR filings and current market data. Built as a real data engineering pipeline through medallion architecture, SQL window functions, and a reverse DCF that backs out what the market is pricing in.
Why this oneThe valuation itself could be done in Excel in an afternoon. The point of the project is the pipeline: Lambda-shaped Python pulling raw XBRL from SEC, normalized through bronze/silver/gold layers, queried with SQL window functions in DuckDB (portable to a Databricks SQL warehouse), then valued with a base case plus bear/bull sensitivity. Live yfinance data anchors the reverse DCF to the current market price.
Python pulls every XBRL fact Lululemon has ever reported from SEC EDGAR in a single API call. Filtered to the 25 line items the model needs, landed in S3 Bronze. Lambda-ready for scheduled refreshes.
Pandas parser normalizes XBRL tag variants into clean column names. Handles old vs. new accounting standards, so the same parser works for Nike, Adidas, and Under Armour without edits. Output: 16 years of typed financial data as Parquet.
DuckDB runs SQL window functions over the silver table to compute YoY growth, 5-year rolling CAGR, margin compression vs peak, and FCF conversion. Same SQL ports to a Databricks SQL warehouse with a one-line connection swap.
5-year free cash flow projection, discounted at the cost of equity, with a Gordon-growth terminal value. Reverse DCF backs out the growth, margin, and discount rate that the current market price implies.
Three sets of growth and margin assumptions run through the same model. The output is a side-by-side valuation with every assumption listed.
Discount rate implied by the current $117.32 market price, against an 11.9% cost of equity from CAPM. A gap that size is a distressed-company risk premium.
Implied revenue growth at the current price. The market is pricing in five years of revenue decline, alongside margin compression and a distressed-company discount rate.
Long-term debt on the balance sheet. Lululemon runs net cash, so the model simplifies to equity-only valuation with operating leases as the only meaningful obligation.
A small forecasting and decision tool for a multi-channel DTC wellness brand. A KPI overview covers revenue, units, and top movers, and a five-state reorder engine turns the sales history into a daily action list.
The math behind reorder timing usually lives in a spreadsheet, and the spreadsheet breaks as soon as a SKU goes seasonal or a second sales channel shows up. This tool replaces it. A recency-weighted velocity model handles trend and noise. A five-state machine (reorder_now / soon / healthy / slow / dead, plus an on_order override) means every decision can be traced back to its inputs. The KPI overview at the top (revenue, units, average daily sales, SKUs in scope, reorder count) gives the operator the context around each recommendation. Underneath, two sales channels are reconciled into a single per-SKU view, with vendor lead times and outstanding POs joined in. It's a Streamlit application rather than a notebook.
Every recommendation can be opened to its inputs. The Forecast Detail view shows the velocity model, the rolling window, the days-of-supply calculation, and the recommended quantity formula side by side, so an operator who questions a number gets the answer in one click. Velocity spread between top- and bottom-quartile SKUs runs 9.0x, and recency weighting keeps fast movers reordering ahead of slow ones even when on-hand counts match.
SKUs flagged reorder_now this cycle. Operators work a short prioritized queue each morning instead of checking all 200 SKUs by hand.
Recommended purchase-order value for reorder_now SKUs this cycle, grouped by vendor with MOQ already applied. Operators copy the per-vendor list straight into the PO email.
Python pulls daily orders from Shopify Admin API and Square API, normalizes both into a single (sku, date, units, revenue) schema with a source column, and stores 365 days of rolling history.
Products, vendors, settings, purchase log, and forecast log live in a normalized schema with foreign keys and indexes. Schema enforces that every SKU has a vendor, lead time, MOQ, and unit cost, so the downstream math always has what it needs.
90-day rolling-average sales velocity per SKU, with linear recency weighting and an optional per-SKU growth factor. Cold-start and dead SKUs are handled as explicit cases instead of coming through as zeros.
Days of supply is on_hand divided by velocity. Compared against target cover days plus vendor lead time to produce one of reorder_now, reorder_soon, healthy, slow, or dead. Outstanding POs override to on_order. Recommended quantity respects vendor MOQ.
Six-page application: Overview KPIs, Reorder Alerts, All Products, Forecast Detail per SKU, Purchase Log, Vendors. Filters cascade across pages. Writes a forecast_log row on every refresh for historical audit.
Next up: a Vail Resorts Monte Carlo valuation this fall, then Yankees cost-per-win and Augusta analytics over the winter, and a Moneyball alpha-decay study in the new year.