Skip to content

feat: freeze product_name instead of line_revenue (+ prod trigger & drop-script fixes) - #43

Merged
andre-salvati merged 8 commits into
mainfrom
feat/freeze-product-name
Jul 3, 2026
Merged

feat: freeze product_name instead of line_revenue (+ prod trigger & drop-script fixes)#43
andre-salvati merged 8 commits into
mainfrom
feat/freeze-product-name

Conversation

@andre-salvati

@andre-salvati andre-salvati commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

Re-points the medallion "freeze at sale time" demonstration from a synthetic price column onto the product name, and cleans up a few prod/ops rough edges found along the way.

Data model — freeze product_name instead of line_revenue

  • Removed the synthetic line_revenue / unit_price_at_sale columns from order_enriched_schema. Gold total_value is now SUM(item_total) — the line value the source already freezes on the order.
  • The silver freeze (batch insert-only MERGE, SDP streaming table) now freezes the mutable product_name onto each order line at processing time. A later rename never relabels already-booked orders; a materialized view would restate it, which is why silver stays a streaming table on the SDP path.
  • The seeder now mutates product_name instead of unit_price: it renames 2 products per incremental run (Product NProduct N.k, where k is the cumulative rename count), day-anchored so reruns of a date are idempotent. unit_price is now a static attribute (set once at initial load, never bumped).

Dashboard

  • The AI/BI "by product" chart and Product filter identify a product by its latest name (consolidated by product_id, one line per physical product across renames); the frozen historical names remain in report.order_agg for audit.

Prod / ops

  • Removed the batch job1's standalone prod schedule (the SDP pipeline already had none), so job1_prod_integration is the single prod trigger orchestrating seed → batch + SDP.
  • sdk_drop_tables.py now falls back to DROP TABLE when a warehouse's parser rejects DROP STREAMING TABLE (kind-specific statement first, plain DROP TABLE as last resort).
  • Added a git_commit (${bundle.git.commit}) deploy tag on every job, so a deployed environment's exact commit is identifiable from databricks jobs list (alongside the existing git_branch tag) instead of diffing deployed bundle source file-by-file.

Tests & docs

  • Updated unit + integration tests, commonSchemas, the SDP pipeline/transforms, and the specs (data-model.md, test-plan.md, architecture.md) to match. All 16 unit tests pass.
  • specs/CHANGELOG.md entry added.

🤖 Generated with Claude Code

andre-salvati and others added 8 commits June 23, 2026 16:31
Remove the synthetic line_revenue/unit_price_at_sale columns; gold
total_value is now SUM(item_total) (the line value the source already
freezes on the order). Re-point the silver insert-only-MERGE / streaming-
table freeze at the mutable product_name, which the seed now changes by
renaming 2 products per run (Product N -> Product N.k); unit_price stays
a static attribute.

The AI/BI "by product" chart consolidates by product_id labeled with each
product's latest name (one line per physical product across renames),
while frozen historical names remain in report.order_agg and the Product
filter. Tests, schemas, SDP pipeline, and data-model/test-plan docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AI/BI wraps each dataset query as `WITH q AS (<query>) SELECT ... FROM q`
and concatenates queryLines with no separator. The multi-line query both
mashed tokens at line boundaries (total_orders+FROM) and produced a nested
WITH inside the wrapper. Rewrite ds_orders as one line using a derived-table
subquery (no leading WITH) for the latest-name lookup; verified the wrapped
by-country aggregation parses and runs on the dev warehouse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on is sole trigger

Remove the daily 0 0 5 CronSchedule from _build_job's prod branch so the
batch job1 (job1_prod) no longer self-triggers. In prod, job1_prod_integration
(0 0 6 Sao_Paulo) is the single scheduled entry point: it seeds, then triggers
the batch job (RunJobTask) and the SDP pipeline (PipelineTask) in parallel. The
SDP pipeline already had no schedule. Prod failure/duration alerting on job1 is
retained. Documented in architecture.md Jobs DAG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some SQL warehouse channels reject the DROP STREAMING TABLE grammar with
PARSE_SYNTAX_ERROR (while accepting a plain DROP TABLE on the streaming
table), which aborted `make drop` mid-loop and left the catalog half-
dropped. Try the kind-specific statement first, then fall back to DROP
TABLE. Keeps DROP MATERIALIZED VIEW for MVs (DROP TABLE type-mismatches there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ta-model.md

Add a narrative overview of the medallion flow (external_source -> bronze ->
silver -> gold -> dashboard), explaining frozen item_total revenue and how a
product rename keeps both old and new names. Document the field naming rules
(entity_id suffix, entity-qualified names, item_*/total_* prefixes, no
abbreviations, DateType dates, _sdp suffix) that were previously implicit.
Point CLAUDE.md at both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ever edit

Add an explicit constraint mirroring the jobs.yml rule: the _deploy copy is
gitignored and regenerated from orders_dashboard.lvdash.json on every deploy
(DABs can't substitute bundle vars inside .lvdash.json), so edit the source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… history

The filter was bound to the frozen per-row product_name, so selecting a
renamed product's current name (e.g. "Product 11.19") matched only the rows
physically stamped with it (today's post-rename orders) and dropped the
pre-rename history (stamped "Product 11"). Bind the filter to the consolidated
latest name (`product`) instead, matching the chart's color encoding: the
dropdown lists each product once and selecting it shows full pre-/post-rename
history. Old names stay in report.order_agg (frozen product_name) for audit.
Verified on prod: filtering "Product 11.19" now returns 1820 rows spanning
2025-06-27..today.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add "git_commit": "${bundle.git.commit}" to _tags() so bundle deploy
substitutes the HEAD SHA into every job's tags. This makes a deployed
environment's exact commit identifiable from `databricks jobs list`
(alongside the existing git_branch tag), instead of diffing deployed
bundle source file-by-file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@andre-salvati
andre-salvati merged commit 8bf5006 into main Jul 3, 2026
1 check passed
andre-salvati added a commit that referenced this pull request Jul 16, 2026
…ixes (#48)

## What?

Three related things, all in the docs/repo-hygiene layer — no runtime behavior changes.

1. **Star-history chart is generated locally now.** New `scripts/star_history.py` + `make star-history` render committed light/dark SVGs into `assets/`; the README's `<picture>` points at them.
2. **README refresh + a setup-breaking fix.** Resolved the two `#TODO`s (keyword-first H1; badges stay at top) and corrected the CI secret names in step 8.
3. **Docs reconciled with the code**, plus `scripts/` added to the ruff lint gate.
4. **Changelog conventions tightened**: entries are now written at merge time and capped at ~5 rendered lines; `#42`/`#43` headers standardized onto the PR-URL format.

## Why?

The chart stopped rendering because `api.star-history.com` returns `503 All GitHub API tokens are rate-limited` — for *every* repo, reproduced against `facebook/react`. Nothing was wrong with our markup; the free tier had exhausted its GitHub token pool. Caching their SVG would have kept the dependency, so the chart is now built from the GitHub API directly.

The most important fix is the smallest diff: **README step 8 told people to configure `DATABRICKS_PRINCIPAL_ID` / `DATABRICKS_SECRET`, but `onpush.yml` reads `DATABRICKS_CLIENT_ID` / `DATABRICKS_CLIENT_SECRET` / `TEMPLATE_ALERT_EMAILS`.** Anyone following the setup guide configured CI auth that silently failed. For a template whose pitch is "fork this and go," that's the worst place to be wrong. `specs/architecture.md` already had the correct names, so the docs also contradicted each other.

The rest came out of a documentation audit: the specs claimed a `bundle validate` CI step that doesn't exist, an `--env local` value the parser rejects, omitted `--load-test` and `job1_sdp/` entirely, and over-generalized the retry rule. CLAUDE.md is loaded every session, and the `resources/jobs.yml` rule was stated in it four separate times.

## How?

- **No new dependencies.** The SVG is hand-rolled; stargazer timestamps come from `gh api` (`Accept: application/vnd.github.star+json`).
- **Two committed SVGs, not one**, to keep the existing `<picture>` light/dark switch.
- **Chart form**: single-series line, so no legend (the title carries identity) and exactly one direct label (the current total) — a static README image has no hover layer to carry values.
- **`known-first-party = ["_sdk_sql"]`** was needed once `scripts/` was linted: isort otherwise files that local sibling under the third-party `databricks` imports.
- **Changelog rule** (`CLAUDE.md`): an entry is written *immediately before merge* rather than during development (scope grows; an early entry just gets rewritten), and is capped at **~5 rendered lines (~475 chars)** on top of the 3-sentence rule — "3 sentences" alone had drifted into 17-line run-ons. Applies to new entries only; the changelog stays append-only, so the other 47 entries are untouched.
- Constraints and Keep It Simple in CLAUDE.md were left verbatim — they're the rules most often broken. Only genuine duplication was cut (~90 → 88 lines).

## Validation?

- `make unit-test` — **16 passed**.
- `pre-commit run --all-files` — both hooks pass; `ruff check src/ scripts/` clean; 23 files formatted.
- **Lint gate proven, not assumed**: a deliberately-broken probe in `scripts/` initially reported "Passed" — pre-commit skips *untracked* files. Once git-added, the hook caught it and failed. The gate genuinely covers `scripts/`.
- Both SVGs rendered in a real browser and inspected (no label collisions, clean ticks, correct in both themes); the committed files are byte-identical to what was reviewed.
- Downsampling (`MAX_POINTS`) and `nice_ticks` edge cases exercised with 1,000 synthetic stars.
- Merged `origin/main` (which brought in #47, merged mid-branch); the only conflict was both branches adding a changelog entry at the top — resolved keeping #47.
- Every corrected doc claim re-checked against source — README secret names now diff-match `onpush.yml` exactly.

**Verified on the pushed branch:** GitHub *does* rewrite relative paths inside `<source srcset>` — both sources resolve to `/andre-salvati/databricks-template/raw/<branch>/assets/…` and serve `200 image/svg+xml`. This had been an open risk (`<picture>` has no fallback if a matched source fails, so a bad path would break dark mode outright); it is now confirmed working in both themes, and no absolute-URL workaround is needed.

## Impact in prod

- [x] No table schema/data change — no production impact.

No table, schema, cluster key, or task logic touched. The only `src/` change is a corrected comment in `job1_sdp/pipeline.py` (it said the streaming silver freezes *price*; it freezes `product_name` since #43). Docs-only pushes are skipped by `paths-ignore`, but this PR also touches `scripts/`, `pyproject.toml`, and `src/`, so CI will run.

**Chosen strategy:** leave as-is (no schema change).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant