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. This project speaks that language end to end: RevPAR index, MPI, ARI, comp set, pace, pickup. Real booking data runs through a production-shaped Databricks lakehouse, and the output is the report a GM actually receives, plus the one-page memo that says 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, not blanket discounting. Cutting rate to chase MPI would trade index points we own for index points we rent.
RevPAR Generation Index, trailing twelve months. Above 100 means the property earns more per available room than its comp set: gaining share, not just growing with 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. Bronze is a faithful landing zone, nothing more.
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. A data-quality gate fails the run loudly if the layer is wrong.
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 goes further: it's wired on production cloud infrastructure end-to-end, uses an LLM (Claude via AWS Bedrock) for the analysis layer, and stores data in a real lakehouse with ACID transactions, not 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. Every version recoverable.
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. Same acquisition cost, dramatically different returns.
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 could be done in Excel in an afternoon. The portfolio value is in 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 Databricks SQL warehouse), then valued with a base case + bear/bull sensitivity. Live yfinance integration 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. Output is a side-by-side valuation, with explicit assumptions instead of hidden ones.
Discount rate implied by the current $117.32 market price, against an 11.9% cost of equity from CAPM. A gap that size reads as 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 focused, quick-to-build forecasting and decision tool for a multi-channel DTC wellness brand. A brief KPI overview frames the operating context, revenue, units, top movers, and a five-state reorder engine turns it into a concrete daily action list.
Tight scope, shipped fast. The math behind reorder timing usually lives in a spreadsheet and breaks the moment a SKU goes seasonal or a second sales channel is added. This replaces that. A recency-weighted velocity model handles trend and noise. An explicit five-state machine (reorder_now / soon / healthy / slow / dead, plus an on_order override) makes every decision auditable. A brief KPI overview at the top, revenue, units, average daily sales, SKUs in scope, reorder count, gives the operator the surrounding context they need to trust each recommendation. Underneath: two sales channels reconciled into a single per-SKU view, with vendor lead times and outstanding POs joined in. Built as a Streamlit application, not 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. Operators question a number and get the answer in one click, no spreadsheet archaeology required. The model earns the trust: 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. A tight, prioritized morning queue lets operators act in minutes instead of auditing the full 200-SKU catalog 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, making downstream math defensible.
90-day rolling-average sales velocity per SKU, with linear recency weighting and an optional per-SKU growth factor. Handles cold-start and dead SKUs as explicit cases, not silent 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.