Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/skills/analytics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ FROM account_txns;
- **"For each X / per X / by X"** returns exactly one row per X. Do not collapse to a single value unless the question says "overall" or "total across X".
- **A named business measure means its amount, not a row count.** When a question asks for "sales", "revenue", "spend", "value", or "volume" of money/goods without an explicit "number / count of", aggregate the monetary/quantity **amount** (`SUM(price)` / `SUM(amount)`), not `COUNT(*)` of rows. *Why:* "toy sales" reads as sales revenue; counting order rows silently answers a different question.
- **Answer literally — do not add unrequested transformations.** Apply exactly the filters, joins, grouping, and computation the question (and any `external_knowledge` doc) states; do not add "helpful" extras the task never asked for — extra status/category predicates, area/residential *weighting* of an average the question states plainly, entity-name *normalization* that forces joins the source leaves unmatched, or a re-derived value where the question names a specific stored measure/column. When the wording bounds an **aggregate** ("committees whose *total* is between $0 and $200", "entities with 5+ orders"), filter the aggregate with `HAVING`, not each row with `WHERE`. When an `external_knowledge` doc gives an explicit formula or function/UDF definition, implement it **verbatim** — same operators, constants, and ordering — rather than substituting your own "more correct" math. *Why:* each unrequested predicate silently drops valid rows, each unrequested weighting/normalization or re-derivation changes the value, and a row-level filter for an aggregate bound answers a different question — so a more-sophisticated-looking query is wrong against the literal ask. Prefer the simplest reading that satisfies the question.
- **Return stored values verbatim — reproduce them exactly as they appear in the data, do not clean them up.** When the answer lists or names values read from a column (titles, names, labels, codes, categories), copy each one **character-for-character as stored** — including apparent misspellings, odd casing, punctuation, trailing tags, and encoding quirks. Do not silently "correct" a typo (`Thorton`→`Thornton`), expand an abbreviation, fix capitalization, translate, or otherwise editorialize the source string; and return the **actual value the question asks for** (the title/name), never a surrogate key or id in its place. If you notice a likely error in the data, you may *note* it separately, but the reported value must remain the stored one. *Why:* the source string **is** the ground truth for a retrieval question — a "corrected" or normalized value is a different string that no longer matches the data (and any consumer matching on it), so a more-polished answer is simply wrong; faithfulness to the stored form beats looking right.
- **Don't project free-text columns the question didn't ask for.** A description/body/comment/notes column whose values contain commas or newlines corrupts the row-delimited output and is almost never the requested value — leave it out of the final projection unless the question explicitly asks for it.
- **"Inter-event duration / gap / interval" is the time between consecutive events, not a magnitude.** When the question asks the typical gap/interval/time *between* occurrences (releases, visits, orders), order rows by the event timestamp and take `LEAD`/`LAG` date differences, then aggregate — never a duration/length/runtime *column*.
- **Anchor a period bucket to the lifecycle event the wording names.** When a record carries several lifecycle timestamps (created/placed, approved, shipped, delivered, completed, settled) and the question counts/measures records in a *named completed state* by period ("delivered orders by month", "shipped items per week", "completed payments by day"), bucket the period by that named event's own timestamp (`order_delivered_customer_date`, `shipped_at`, `settled_at`) — the state value is the qualifying filter, the matching timestamp is the time anchor. Use the creation/placed/purchased/submitted timestamp only when the question names that *start* event (purchased, placed, created, ordered, submitted) or no matching event timestamp exists. If several timestamps fit, pick the one for the event as experienced by the question's subject (customer delivery = the customer-receipt date, not the carrier-handoff or estimated date). If the named state is used only as a non-temporal filter (counts by customer/city/seller with no period bucket), it is just a filter — introduce no date anchor. Confirm each timestamp's meaning from column names, semantic-layer descriptions, and sample rows first. *Why:* bucketing a completed-state count by the record's creation date silently answers a different question — "records that later reached that state, grouped by when they started" — than the one asked.
Expand Down
1 change: 1 addition & 0 deletions packages/cli/test/skills/analytics-skill-content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('analytics SKILL.md SQL craft', () => {
'Default by additivity', // COALESCE 0 for additive, NULL otherwise
'Keep the inputs to a derived value', // inputs alongside ratio
'Project BOTH identity and label', // entity identifier
'Return stored values verbatim', // copy source strings character-for-character, no cleanup
'Diagnose empty results', // relax filters one at a time
'Cumulative / running total', // explicit unbounded-preceding frame (spec 11)
'Rolling window over calendar time', // calendar range, not row count (spec 11)
Expand Down
Loading