altimate-code 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +13 -13
- package/postinstall.mjs +35 -0
- package/skills/cost-report/SKILL.md +134 -0
- package/skills/data-viz/SKILL.md +135 -0
- package/skills/data-viz/references/component-guide.md +394 -0
- package/skills/dbt-analyze/SKILL.md +130 -0
- package/skills/dbt-analyze/references/altimate-dbt-commands.md +66 -0
- package/skills/dbt-analyze/references/lineage-interpretation.md +58 -0
- package/skills/dbt-develop/SKILL.md +151 -0
- package/skills/dbt-develop/references/altimate-dbt-commands.md +66 -0
- package/skills/dbt-develop/references/common-mistakes.md +49 -0
- package/skills/dbt-develop/references/incremental-strategies.md +118 -0
- package/skills/dbt-develop/references/layer-patterns.md +158 -0
- package/skills/dbt-develop/references/medallion-architecture.md +125 -0
- package/skills/dbt-develop/references/yaml-generation.md +90 -0
- package/skills/dbt-docs/SKILL.md +99 -0
- package/skills/dbt-docs/references/altimate-dbt-commands.md +66 -0
- package/skills/dbt-docs/references/documentation-standards.md +94 -0
- package/skills/dbt-test/SKILL.md +121 -0
- package/skills/dbt-test/references/altimate-dbt-commands.md +66 -0
- package/skills/dbt-test/references/custom-tests.md +59 -0
- package/skills/dbt-test/references/schema-test-patterns.md +103 -0
- package/skills/dbt-test/references/unit-test-guide.md +121 -0
- package/skills/dbt-troubleshoot/SKILL.md +187 -0
- package/skills/dbt-troubleshoot/references/altimate-dbt-commands.md +66 -0
- package/skills/dbt-troubleshoot/references/compilation-errors.md +57 -0
- package/skills/dbt-troubleshoot/references/runtime-errors.md +71 -0
- package/skills/dbt-troubleshoot/references/test-failures.md +95 -0
- package/skills/lineage-diff/SKILL.md +64 -0
- package/skills/pii-audit/SKILL.md +117 -0
- package/skills/query-optimize/SKILL.md +86 -0
- package/skills/schema-migration/SKILL.md +119 -0
- package/skills/sql-review/SKILL.md +118 -0
- package/skills/sql-translate/SKILL.md +68 -0
- package/skills/teach/SKILL.md +54 -0
- package/skills/train/SKILL.md +51 -0
- package/skills/training-status/SKILL.md +45 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Component Library Reference
|
|
2
|
+
|
|
3
|
+
Non-obvious patterns, gotchas, and custom implementations. Standard library usage (basic bar/line/area/pie/scatter) is well-documented — this covers what agents get wrong or can't infer.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [shadcn/ui Charts](#shadcnui-charts) — Config pattern & key rules
|
|
8
|
+
2. [Tremor Essentials](#tremor-essentials) — KPI cards, dashboard grid
|
|
9
|
+
3. [Nivo Gotchas](#nivo-gotchas) — Height wrapper, common props
|
|
10
|
+
4. [D3 + React Pattern](#d3--react-pattern) — Force-directed DAG
|
|
11
|
+
5. [Layout Patterns](#layout-patterns) — Dashboard grid, card component
|
|
12
|
+
6. [Color Systems](#color-systems) — Semantic, sequential, diverging, categorical
|
|
13
|
+
7. [Data Transformations](#data-transformations) — Recharts pivot, KPI aggregate, treemap, time bucketing
|
|
14
|
+
8. [Waterfall Chart](#waterfall-chart) — Custom Recharts pattern
|
|
15
|
+
9. [Radar / Spider Chart](#radar--spider-chart) — Key rules
|
|
16
|
+
10. [Calendar Heatmap](#calendar-heatmap) — Nivo setup
|
|
17
|
+
11. [Annotation Patterns](#annotation-patterns) — Goal lines, highlights, callouts, anomaly dots
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## shadcn/ui Charts
|
|
22
|
+
|
|
23
|
+
Built on Recharts with themed, accessible wrappers. **Unique config pattern — don't skip this.**
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { type ChartConfig } from "@/components/ui/chart"
|
|
27
|
+
|
|
28
|
+
const chartConfig = {
|
|
29
|
+
revenue: { label: "Revenue", color: "hsl(var(--chart-1))" },
|
|
30
|
+
expenses: { label: "Expenses", color: "hsl(var(--chart-2))" },
|
|
31
|
+
} satisfies ChartConfig
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Key rules:**
|
|
35
|
+
- Always `min-h-[VALUE]` on `ChartContainer` (required for responsiveness)
|
|
36
|
+
- Use `accessibilityLayer` prop on the main chart component
|
|
37
|
+
- Colors via CSS variables `var(--color-{key})`, never hardcoded
|
|
38
|
+
- Use `ChartTooltip` + `ChartTooltipContent`, not Recharts defaults
|
|
39
|
+
- Use `ChartLegend` + `ChartLegendContent` for interactive legends
|
|
40
|
+
|
|
41
|
+
### Area Chart Gradient (common pattern)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<defs>
|
|
45
|
+
<linearGradient id="fillRevenue" x1="0" y1="0" x2="0" y2="1">
|
|
46
|
+
<stop offset="5%" stopColor="var(--color-revenue)" stopOpacity={0.8} />
|
|
47
|
+
<stop offset="95%" stopColor="var(--color-revenue)" stopOpacity={0.1} />
|
|
48
|
+
</linearGradient>
|
|
49
|
+
</defs>
|
|
50
|
+
<Area type="monotone" dataKey="revenue" stroke="var(--color-revenue)" fill="url(#fillRevenue)" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Tremor Essentials
|
|
56
|
+
|
|
57
|
+
### KPI Card Pattern
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Card, BadgeDelta, SparkAreaChart } from "@tremor/react"
|
|
61
|
+
|
|
62
|
+
<Card className="max-w-sm">
|
|
63
|
+
<div className="flex items-center justify-between">
|
|
64
|
+
<p className="text-tremor-default text-tremor-content">Revenue</p>
|
|
65
|
+
<BadgeDelta deltaType="increase" size="xs">+12.3%</BadgeDelta>
|
|
66
|
+
</div>
|
|
67
|
+
<p className="text-tremor-metric font-semibold mt-1">$1.24M</p>
|
|
68
|
+
<SparkAreaChart data={sparkData} categories={["value"]} index="date"
|
|
69
|
+
colors={["emerald"]} className="h-8 w-full mt-4" />
|
|
70
|
+
</Card>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Dashboard Grid
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
77
|
+
{kpis.map(kpi => <KPICard key={kpi.id} {...kpi} />)}
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Tremor AreaChart / BarList
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<AreaChart data={data} index="month" categories={["Revenue", "Expenses"]}
|
|
85
|
+
colors={["blue", "red"]} valueFormatter={(v) => `$${(v/1000).toFixed(0)}k`}
|
|
86
|
+
className="h-72 mt-4" showAnimation />
|
|
87
|
+
|
|
88
|
+
<BarList data={[{ name: "Google", value: 45632 }, ...]} className="mt-4" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Nivo Gotchas
|
|
94
|
+
|
|
95
|
+
- **Always set `height` on the wrapper div**, not on the Nivo component: `<div style={{ height: 400 }}><ResponsiveHeatMap ... /></div>`
|
|
96
|
+
- Use `emptyColor="#f3f4f6"` for missing data cells
|
|
97
|
+
- For heatmaps: `colors={{ type: "sequential", scheme: "blues" }}`
|
|
98
|
+
- For treemaps: `identity="name"` + `value="value"` + `labelSkipSize={12}`
|
|
99
|
+
- For Sankey: `enableLinkGradient` + `linkBlendMode="multiply"` for polish
|
|
100
|
+
- For Choropleth: needs GeoJSON features, `projectionScale={150}`, `projectionTranslation={[0.5, 0.5]}`
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## D3 + React Pattern: Force-Directed DAG
|
|
105
|
+
|
|
106
|
+
Use for lineage graphs, dependency trees, pipeline DAGs. **D3 computes positions, React renders SVG.**
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useEffect, useRef } from "react"
|
|
110
|
+
import * as d3 from "d3"
|
|
111
|
+
|
|
112
|
+
interface DagNode { id: string; label: string; type: "source" | "middle" | "output" }
|
|
113
|
+
interface DagLink { source: string; target: string }
|
|
114
|
+
|
|
115
|
+
const NODE_COLORS: Record<DagNode["type"], { fill: string; stroke: string }> = {
|
|
116
|
+
source: { fill: "#dbeafe", stroke: "#3b82f6" },
|
|
117
|
+
middle: { fill: "#f1f5f9", stroke: "#94a3b8" },
|
|
118
|
+
output: { fill: "#dcfce7", stroke: "#22c55e" },
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function ForceDAG({ nodes, links }: { nodes: DagNode[]; links: DagLink[] }) {
|
|
122
|
+
const svgRef = useRef<SVGSVGElement>(null)
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!svgRef.current) return
|
|
126
|
+
const width = svgRef.current.clientWidth || 800, height = 500
|
|
127
|
+
const svg = d3.select(svgRef.current).attr("height", height)
|
|
128
|
+
svg.selectAll("*").remove()
|
|
129
|
+
|
|
130
|
+
// Arrowhead marker
|
|
131
|
+
svg.append("defs").append("marker")
|
|
132
|
+
.attr("id", "dag-arrow").attr("viewBox", "0 -5 10 10")
|
|
133
|
+
.attr("refX", 22).attr("refY", 0)
|
|
134
|
+
.attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto")
|
|
135
|
+
.append("path").attr("d", "M0,-5L10,0L0,5").attr("fill", "#94a3b8")
|
|
136
|
+
|
|
137
|
+
// CRITICAL: Copy arrays — D3 mutates them with x/y/vx/vy
|
|
138
|
+
const nodesCopy = nodes.map(n => ({ ...n }))
|
|
139
|
+
const linksCopy = links.map(l => ({ ...l }))
|
|
140
|
+
|
|
141
|
+
const sim = d3.forceSimulation(nodesCopy as any)
|
|
142
|
+
.force("link", d3.forceLink(linksCopy).id((d: any) => d.id).distance(140))
|
|
143
|
+
.force("charge", d3.forceManyBody().strength(-350))
|
|
144
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
145
|
+
.force("collision", d3.forceCollide().radius(50))
|
|
146
|
+
|
|
147
|
+
const linkSel = svg.append("g").selectAll("line")
|
|
148
|
+
.data(linksCopy).join("line")
|
|
149
|
+
.attr("stroke", "#cbd5e1").attr("stroke-width", 1.5)
|
|
150
|
+
.attr("marker-end", "url(#dag-arrow)")
|
|
151
|
+
|
|
152
|
+
const nodeSel = svg.append("g").selectAll<SVGGElement, DagNode>("g")
|
|
153
|
+
.data(nodesCopy).join("g")
|
|
154
|
+
.call(d3.drag<SVGGElement, any>()
|
|
155
|
+
.on("start", (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y })
|
|
156
|
+
.on("drag", (e, d) => { d.fx = e.x; d.fy = e.y })
|
|
157
|
+
.on("end", (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null }))
|
|
158
|
+
|
|
159
|
+
nodeSel.append("rect").attr("x", -54).attr("y", -18)
|
|
160
|
+
.attr("width", 108).attr("height", 36).attr("rx", 6)
|
|
161
|
+
.attr("fill", (d: any) => NODE_COLORS[d.type].fill)
|
|
162
|
+
.attr("stroke", (d: any) => NODE_COLORS[d.type].stroke).attr("stroke-width", 1.5)
|
|
163
|
+
|
|
164
|
+
nodeSel.append("text").attr("text-anchor", "middle").attr("dy", "0.35em")
|
|
165
|
+
.attr("font-size", 11).attr("fill", "#374151")
|
|
166
|
+
.text((d: any) => d.label.length > 16 ? d.label.slice(0, 15) + "…" : d.label)
|
|
167
|
+
|
|
168
|
+
sim.on("tick", () => {
|
|
169
|
+
linkSel.attr("x1", (d: any) => d.source.x).attr("y1", (d: any) => d.source.y)
|
|
170
|
+
.attr("x2", (d: any) => d.target.x).attr("y2", (d: any) => d.target.y)
|
|
171
|
+
nodeSel.attr("transform", (d: any) => `translate(${d.x},${d.y})`)
|
|
172
|
+
})
|
|
173
|
+
return () => { sim.stop() }
|
|
174
|
+
}, [nodes, links])
|
|
175
|
+
|
|
176
|
+
return <svg ref={svgRef} className="w-full" style={{ minHeight: 500 }} />
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Rules:** Always copy nodes/links before D3. Use `clientWidth` for responsive width. Truncate labels, show full on hover. `alphaTarget(0)` on drag end lets sim cool naturally.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Layout Patterns
|
|
185
|
+
|
|
186
|
+
### Dashboard Grid (Tailwind)
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<div className="min-h-screen bg-background p-6">
|
|
190
|
+
<div className="mb-8 flex items-center justify-between">
|
|
191
|
+
<div>
|
|
192
|
+
<h1 className="text-2xl font-bold tracking-tight">Analytics</h1>
|
|
193
|
+
<p className="text-muted-foreground">Your performance overview</p>
|
|
194
|
+
</div>
|
|
195
|
+
<DateRangePicker />
|
|
196
|
+
</div>
|
|
197
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-8">
|
|
198
|
+
{kpis.map(kpi => <KPICard key={kpi.id} {...kpi} />)}
|
|
199
|
+
</div>
|
|
200
|
+
<div className="grid gap-4 md:grid-cols-7 mb-8">
|
|
201
|
+
<Card className="col-span-4">{/* Primary chart */}</Card>
|
|
202
|
+
<Card className="col-span-3">{/* Secondary chart */}</Card>
|
|
203
|
+
</div>
|
|
204
|
+
<Card>{/* DataTable */}</Card>
|
|
205
|
+
</div>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### shadcn-style Card
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<div className="rounded-xl border bg-card p-6 shadow-sm">
|
|
212
|
+
<div className="flex items-center justify-between">
|
|
213
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
214
|
+
<Icon className="h-4 w-4 text-muted-foreground" />
|
|
215
|
+
</div>
|
|
216
|
+
<div className="mt-2">
|
|
217
|
+
<p className="text-2xl font-bold">{value}</p>
|
|
218
|
+
<p className={cn("text-xs mt-1", delta > 0 ? "text-green-600" : "text-red-600")}>
|
|
219
|
+
{delta > 0 ? "+" : ""}{delta}% from last period
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Color Systems
|
|
228
|
+
|
|
229
|
+
**Semantic (default):**
|
|
230
|
+
```css
|
|
231
|
+
--chart-1: 221.2 83.2% 53.3%; /* Blue */
|
|
232
|
+
--chart-2: 142.1 76.2% 36.3%; /* Green */
|
|
233
|
+
--chart-3: 24.6 95% 53.1%; /* Orange */
|
|
234
|
+
--chart-4: 346.8 77.2% 49.8%; /* Red */
|
|
235
|
+
--chart-5: 262.1 83.3% 57.8%; /* Purple */
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Sequential** (heatmaps/gradients): Single hue light→dark. Tailwind `blue-100`→`blue-900` or Nivo schemes: `blues`, `greens`, `oranges`.
|
|
239
|
+
|
|
240
|
+
**Diverging** (+/- values): Red ↔ White ↔ Green, or Red ↔ Grey ↔ Blue. Center on zero.
|
|
241
|
+
|
|
242
|
+
**Categorical** (distinct groups): Max 7. Tailwind `500` shades: `blue`, `emerald`, `amber`, `rose`, `violet`, `cyan`, `orange`.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Data Transformations
|
|
247
|
+
|
|
248
|
+
### Pivot for Recharts
|
|
249
|
+
|
|
250
|
+
Recharts needs flat arrays with all series as keys per data point:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
// { date, category, value } rows → { date, cat_A: val, cat_B: val }
|
|
254
|
+
const pivoted = _.chain(rawData).groupBy("date")
|
|
255
|
+
.map((items, date) => ({ date, ..._.fromPairs(items.map(i => [i.category, i.value])) }))
|
|
256
|
+
.value()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### KPI Aggregation
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const kpis = {
|
|
263
|
+
total: _.sumBy(data, "revenue"), average: _.meanBy(data, "revenue"),
|
|
264
|
+
max: _.maxBy(data, "revenue"), count: data.length,
|
|
265
|
+
growth: ((current - previous) / previous * 100).toFixed(1),
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Flat → Hierarchical (Treemaps)
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const tree = { name: "root", children: _.chain(data).groupBy("category")
|
|
273
|
+
.map((items, name) => ({ name, children: items.map(i => ({ name: i.label, value: i.amount })) })).value() }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Time Bucketing
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { format, startOfWeek } from "date-fns"
|
|
280
|
+
const weekly = _.chain(data).groupBy(d => format(startOfWeek(new Date(d.date)), "yyyy-MM-dd"))
|
|
281
|
+
.map((items, week) => ({ week, total: _.sumBy(items, "value"), count: items.length })).value()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Waterfall Chart
|
|
287
|
+
|
|
288
|
+
Recharts has no native waterfall. Use stacked Bar with invisible spacer:
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
function toWaterfallSeries(items: { name: string; value: number }[]) {
|
|
292
|
+
let running = 0
|
|
293
|
+
return items.map(item => {
|
|
294
|
+
const start = item.value >= 0 ? running : running + item.value
|
|
295
|
+
running += item.value
|
|
296
|
+
return { name: item.name, value: Math.abs(item.value), start, _raw: item.value }
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// In ComposedChart:
|
|
301
|
+
<Bar dataKey="start" stackId="wf" fill="transparent" isAnimationActive={false} />
|
|
302
|
+
<Bar dataKey="value" stackId="wf" radius={[4,4,0,0]}>
|
|
303
|
+
{data.map((e, i) => <Cell key={i} fill={e._raw >= 0 ? "#22c55e" : "#ef4444"} />)}
|
|
304
|
+
</Bar>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Critical:** Spacer bar must have `isAnimationActive={false}` (animating it reveals the trick). Hide spacer from tooltip by returning `null` in formatter. For "total" bar: `start: 0`, `value: runningTotal`, distinct color (slate).
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Radar / Spider Chart
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
<RadarChart cx="50%" cy="50%" outerRadius="75%" data={data}>
|
|
315
|
+
<PolarGrid stroke="#e2e8f0" />
|
|
316
|
+
<PolarAngleAxis dataKey="dimension" tick={{ fontSize: 12, fill: "#64748b" }} />
|
|
317
|
+
<PolarRadiusAxis angle={90} domain={[0, 100]} tickCount={5} />
|
|
318
|
+
<Radar name="Current" dataKey="score" stroke="#6366f1" fill="#6366f1" fillOpacity={0.25} strokeWidth={2} />
|
|
319
|
+
<Radar name="Benchmark" dataKey="benchmark" stroke="#e2e8f0" fill="none" strokeDasharray="5 3" strokeWidth={1.5} />
|
|
320
|
+
<Tooltip /><Legend />
|
|
321
|
+
</RadarChart>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Rules:** `domain={[0, 100]}` for consistent comparison. Dashed benchmark gives context. Max 2 series.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Calendar Heatmap
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import { ResponsiveCalendar } from "@nivo/calendar"
|
|
332
|
+
|
|
333
|
+
<div style={{ height: 200 }}>
|
|
334
|
+
<ResponsiveCalendar data={data} from={from} to={to}
|
|
335
|
+
emptyColor="#f8fafc" colors={["#dbeafe", "#93c5fd", "#3b82f6", "#1d4ed8"]}
|
|
336
|
+
margin={{ top: 24, right: 20, bottom: 8, left: 20 }}
|
|
337
|
+
yearSpacing={40} dayBorderWidth={2} dayBorderColor="#ffffff" />
|
|
338
|
+
</div>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Rules:** Height on wrapper div, not component. Single-hue sequential palette. `emptyColor` near-white for sparse data.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Annotation Patterns
|
|
346
|
+
|
|
347
|
+
Annotations turn charts into stories. **Limit 3 per chart.**
|
|
348
|
+
|
|
349
|
+
**Color by type:** amber `#f59e0b` = target/goal, red `#ef4444` = incident/risk, indigo `#6366f1` = event/release, green `#22c55e` = achievement.
|
|
350
|
+
|
|
351
|
+
### Goal/Threshold Line
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
<ReferenceLine y={targetValue} stroke="#f59e0b" strokeDasharray="6 3" strokeWidth={1.5}
|
|
355
|
+
label={{ value: `Target: ${targetValue.toLocaleString()}`, position: "insideTopRight", fontSize: 11, fill: "#f59e0b" }} />
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Time Range Highlight
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
<ReferenceArea x1={start} x2={end} fill="#fee2e2" fillOpacity={0.5}
|
|
362
|
+
label={{ value: "Incident", position: "insideTopLeft", fontSize: 10, fill: "#ef4444" }} />
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Floating Callout Label
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
const CalloutLabel = ({ viewBox, label, color = "#1e293b" }: { viewBox?: { x: number; y: number }; label: string; color?: string }) => {
|
|
369
|
+
if (!viewBox) return null
|
|
370
|
+
const { x, y } = viewBox, w = label.length * 7 + 16
|
|
371
|
+
return (<g>
|
|
372
|
+
<rect x={x - w/2} y={y - 34} width={w} height={20} rx={4} fill={color} />
|
|
373
|
+
<text x={x} y={y - 20} textAnchor="middle" fontSize={11} fill="white" fontWeight={500}>{label}</text>
|
|
374
|
+
<line x1={x} y1={y - 14} x2={x} y2={y} stroke={color} strokeWidth={1} />
|
|
375
|
+
</g>)
|
|
376
|
+
}
|
|
377
|
+
// Usage: <ReferenceLine x={date} stroke="#6366f1" strokeDasharray="4 4" label={<CalloutLabel label="v2.0 shipped" color="#6366f1" />} />
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Anomaly Dot Highlight
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
<Line dataKey="value" strokeWidth={2} dot={(props) => {
|
|
384
|
+
const { cx, cy, payload, key } = props
|
|
385
|
+
if (!payload?.isAnomaly) return <circle key={key} cx={cx} cy={cy} r={3} fill="#6366f1" />
|
|
386
|
+
return (<g key={key}>
|
|
387
|
+
<circle cx={cx} cy={cy} r={10} fill="#ef4444" opacity={0.15} />
|
|
388
|
+
<circle cx={cx} cy={cy} r={4} fill="#ef4444" />
|
|
389
|
+
<text x={cx} y={cy - 14} textAnchor="middle" fontSize={11} fill="#ef4444">▲</text>
|
|
390
|
+
</g>)
|
|
391
|
+
}} />
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Rules:** Never overlap data. Use `position: "insideTopRight"/"insideTopLeft"` on labels. Pair annotations with tooltips — annotation names the event, tooltip shows the value.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dbt-analyze
|
|
3
|
+
description: Analyze downstream impact of dbt model changes using column-level lineage and the dependency graph. Use when evaluating the blast radius of a change before shipping. Powered by altimate-dbt.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dbt Impact Analysis
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
**Agent:** any (read-only analysis)
|
|
10
|
+
**Tools used:** bash (runs `altimate-dbt` commands), read, glob, dbt_manifest, lineage_check, dbt_lineage, sql_analyze, altimate_core_extract_metadata
|
|
11
|
+
|
|
12
|
+
## When to Use This Skill
|
|
13
|
+
|
|
14
|
+
**Use when the user wants to:**
|
|
15
|
+
- Understand what breaks if they change a model
|
|
16
|
+
- Evaluate downstream impact before shipping
|
|
17
|
+
- Find all consumers of a model or column
|
|
18
|
+
- Assess risk of a refactoring
|
|
19
|
+
|
|
20
|
+
**Do NOT use for:**
|
|
21
|
+
- Creating or fixing models → use `dbt-develop` or `dbt-troubleshoot`
|
|
22
|
+
- Adding tests → use `dbt-test`
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
### 1. Identify the Changed Model
|
|
27
|
+
|
|
28
|
+
Accept from the user, or auto-detect:
|
|
29
|
+
```bash
|
|
30
|
+
# From git diff
|
|
31
|
+
git diff --name-only | grep '\.sql$'
|
|
32
|
+
|
|
33
|
+
# Or user provides a model name
|
|
34
|
+
altimate-dbt compile --model <name> # verify it exists
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Map the Dependency Graph
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
altimate-dbt children --model <name> # direct downstream
|
|
41
|
+
altimate-dbt parents --model <name> # what feeds it
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For the full downstream tree, recursively call `children` on each downstream model.
|
|
45
|
+
|
|
46
|
+
### 3. Run Column-Level Lineage
|
|
47
|
+
|
|
48
|
+
**With manifest (preferred):** Use `dbt_lineage` to compute column-level lineage for a dbt model. This reads the manifest.json, extracts compiled SQL and upstream schemas, and traces column flow via the Rust engine. More accurate than raw SQL lineage because it resolves `ref()` and `source()` to actual schemas.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
dbt_lineage(model: <model_name>)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Without manifest (fallback):** Use `lineage_check` on the raw SQL to understand:
|
|
55
|
+
- Which source columns flow to which output columns
|
|
56
|
+
- Which columns were added, removed, or renamed
|
|
57
|
+
|
|
58
|
+
**Extract structural metadata:** Use `altimate_core_extract_metadata` on the SQL to get tables referenced, columns used, CTEs, subqueries — useful for mapping the full dependency surface.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### 4. Cross-Reference with Downstream
|
|
62
|
+
|
|
63
|
+
For each downstream model:
|
|
64
|
+
1. Read its SQL
|
|
65
|
+
2. Check if it references any changed/removed columns
|
|
66
|
+
3. Classify impact:
|
|
67
|
+
|
|
68
|
+
| Classification | Meaning | Action |
|
|
69
|
+
|---------------|---------|--------|
|
|
70
|
+
| **BREAKING** | Removed/renamed column used downstream | Must fix before shipping |
|
|
71
|
+
| **SAFE** | Added column, no downstream reference | Ship freely |
|
|
72
|
+
| **UNKNOWN** | Can't determine (dynamic SQL, macros) | Manual review needed |
|
|
73
|
+
|
|
74
|
+
### 5. Generate Impact Report
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Impact Analysis: stg_orders
|
|
78
|
+
════════════════════════════
|
|
79
|
+
|
|
80
|
+
Changed Model: stg_orders (materialized: view)
|
|
81
|
+
Columns: 5 → 6 (+1 added)
|
|
82
|
+
Removed: total_amount (renamed to order_total)
|
|
83
|
+
|
|
84
|
+
Downstream Impact (3 models):
|
|
85
|
+
|
|
86
|
+
Depth 1:
|
|
87
|
+
[BREAKING] int_order_metrics
|
|
88
|
+
Uses: total_amount → COLUMN RENAMED
|
|
89
|
+
Fix: Update column reference to order_total
|
|
90
|
+
|
|
91
|
+
[SAFE] int_order_summary
|
|
92
|
+
No references to changed columns
|
|
93
|
+
|
|
94
|
+
Depth 2:
|
|
95
|
+
[BREAKING] mart_revenue
|
|
96
|
+
Uses: total_amount via int_order_metrics → CASCADING
|
|
97
|
+
Fix: Verify after fixing int_order_metrics
|
|
98
|
+
|
|
99
|
+
Tests at Risk: 4
|
|
100
|
+
- not_null_stg_orders_order_total
|
|
101
|
+
- unique_int_order_metrics_order_id
|
|
102
|
+
|
|
103
|
+
Summary: 2 BREAKING, 1 SAFE
|
|
104
|
+
Recommended: Fix int_order_metrics first, then:
|
|
105
|
+
altimate-dbt build --model stg_orders --downstream
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Without Manifest (SQL-Only Mode)
|
|
109
|
+
|
|
110
|
+
If no manifest is available:
|
|
111
|
+
1. Run `lineage_check` on the changed SQL
|
|
112
|
+
2. Show column-level data flow
|
|
113
|
+
3. Note: downstream impact requires a manifest
|
|
114
|
+
4. Suggest: `altimate-dbt build-project` to generate one
|
|
115
|
+
|
|
116
|
+
## Common Mistakes
|
|
117
|
+
|
|
118
|
+
| Mistake | Fix |
|
|
119
|
+
|---------|-----|
|
|
120
|
+
| Only checking direct children | Always trace the FULL downstream tree recursively |
|
|
121
|
+
| Ignoring test impacts | Check which tests reference changed columns |
|
|
122
|
+
| Shipping without building downstream | Always `altimate-dbt build --model <name> --downstream` |
|
|
123
|
+
| Not considering renamed columns | A rename is a break + add — downstream still references the old name |
|
|
124
|
+
|
|
125
|
+
## Reference Guides
|
|
126
|
+
|
|
127
|
+
| Guide | Use When |
|
|
128
|
+
|-------|----------|
|
|
129
|
+
| [references/altimate-dbt-commands.md](references/altimate-dbt-commands.md) | Need the full CLI reference |
|
|
130
|
+
| [references/lineage-interpretation.md](references/lineage-interpretation.md) | Understanding lineage output |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# altimate-dbt Command Reference
|
|
2
|
+
|
|
3
|
+
All dbt operations use the `altimate-dbt` CLI. Output is JSON to stdout; logs go to stderr.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
altimate-dbt <command> [args...]
|
|
7
|
+
altimate-dbt <command> [args...] --format text # Human-readable output
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## First-Time Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
altimate-dbt init # Auto-detect project root
|
|
14
|
+
altimate-dbt init --project-root /path # Explicit root
|
|
15
|
+
altimate-dbt init --python-path /path # Override Python
|
|
16
|
+
altimate-dbt doctor # Verify setup
|
|
17
|
+
altimate-dbt info # Project name, adapter, root
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Build & Run
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
altimate-dbt build --model <name> [--downstream] # compile + run + test
|
|
24
|
+
altimate-dbt run --model <name> [--downstream] # materialize only
|
|
25
|
+
altimate-dbt test --model <name> # run tests only
|
|
26
|
+
altimate-dbt build-project # full project build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Compile
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
altimate-dbt compile --model <name>
|
|
33
|
+
altimate-dbt compile-query --query "SELECT * FROM {{ ref('stg_orders') }}" [--model <context>]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Execute SQL
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
altimate-dbt execute --query "SELECT count(*) FROM {{ ref('orders') }}" --limit 100
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Schema & DAG
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
altimate-dbt columns --model <name> # column names and types
|
|
46
|
+
altimate-dbt columns-source --source <src> --table <tbl> # source table columns
|
|
47
|
+
altimate-dbt column-values --model <name> --column <col> # sample values
|
|
48
|
+
altimate-dbt children --model <name> # downstream models
|
|
49
|
+
altimate-dbt parents --model <name> # upstream models
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Packages
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
altimate-dbt deps # install packages.yml
|
|
56
|
+
altimate-dbt add-packages --packages dbt-utils,dbt-expectations
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Error Handling
|
|
60
|
+
|
|
61
|
+
All errors return JSON with `error` and `fix` fields:
|
|
62
|
+
```json
|
|
63
|
+
{ "error": "dbt-core is not installed", "fix": "Install it: python3 -m pip install dbt-core" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run `altimate-dbt doctor` as the first diagnostic step for any failure.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Lineage Interpretation Guide
|
|
2
|
+
|
|
3
|
+
## Understanding Column-Level Lineage
|
|
4
|
+
|
|
5
|
+
Column-level lineage traces how data flows from source columns through transformations to output columns.
|
|
6
|
+
|
|
7
|
+
### Direct Lineage
|
|
8
|
+
```
|
|
9
|
+
source.customers.name → stg_customers.customer_name → dim_customers.full_name
|
|
10
|
+
```
|
|
11
|
+
Column was renamed at each step. A change to the source column affects all downstream.
|
|
12
|
+
|
|
13
|
+
### Aggregation Lineage
|
|
14
|
+
```
|
|
15
|
+
source.orders.amount → (SUM) → fct_daily_revenue.total_revenue
|
|
16
|
+
```
|
|
17
|
+
Multiple source rows feed into one output value. The column type changes from row-level to aggregate.
|
|
18
|
+
|
|
19
|
+
### Conditional Lineage
|
|
20
|
+
```
|
|
21
|
+
source.orders.status → (CASE WHEN) → fct_orders.is_completed
|
|
22
|
+
```
|
|
23
|
+
The source column feeds a derived boolean. The relationship is logical, not direct.
|
|
24
|
+
|
|
25
|
+
## Impact Classification
|
|
26
|
+
|
|
27
|
+
### BREAKING Changes
|
|
28
|
+
- **Column removed**: Downstream models referencing it will fail
|
|
29
|
+
- **Column renamed**: Same as removed — downstream still uses the old name
|
|
30
|
+
- **Type changed**: May cause cast errors or silent data loss downstream
|
|
31
|
+
- **Logic changed**: Downstream aggregations/filters may produce wrong results
|
|
32
|
+
|
|
33
|
+
### SAFE Changes
|
|
34
|
+
- **Column added**: No downstream model can reference what didn't exist
|
|
35
|
+
- **Description changed**: No runtime impact
|
|
36
|
+
- **Test added/modified**: No impact on model data
|
|
37
|
+
|
|
38
|
+
### REQUIRES REVIEW
|
|
39
|
+
- **Filter changed**: May change which rows appear → downstream counts change
|
|
40
|
+
- **JOIN type changed**: LEFT→INNER drops rows, INNER→LEFT adds NULLs
|
|
41
|
+
- **Materialization changed**: view→table has no logical impact but affects freshness
|
|
42
|
+
|
|
43
|
+
## Reading the DAG
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
altimate-dbt parents --model <name> # what this model depends on
|
|
47
|
+
altimate-dbt children --model <name> # what depends on this model
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A model with many children has high blast radius. A model with many parents has high complexity.
|
|
51
|
+
|
|
52
|
+
## Depth Matters
|
|
53
|
+
|
|
54
|
+
- **Depth 1**: Direct consumers — highest risk, most likely to break
|
|
55
|
+
- **Depth 2+**: Cascading impact — will break IF depth 1 breaks
|
|
56
|
+
- **Depth 3+**: Usually only affected by breaking column removals/renames
|
|
57
|
+
|
|
58
|
+
Focus investigation on depth 1 first.
|