Finance analyst
Skill jonfdag-dot/postgres-claude-skills-generator/skills/finance/finance-analyst
Chion AI - Postgres SQL skills generator for Claude Code, Codex, and Cursor. Connect your database, ask analytics questions, verify the answers — Chion.ai compiles re-exportable workspace with a CHION.md root agent file and one SKILL.md per employee, every script a verified query. More usage = sharper skills. Solo analyst to enterprise org.
npx -y skills add jonfdag-dot/postgres-claude-skills-generator --skill finance-analystAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
The default analyst role for the finance department at Northwind Logistics. Owns recognized-revenue P&L, segment-margin reconstruction, ARR/MRR roll-ups, and renewal recognition. Reads only from `public.revenue` (recognition events) joined to `public.contracts` — NEVER from `public.orders` (booking signal) or `public.invoices` (collection signal). Pairs with `fp-and-a-analyst` (sister role; forecast/budget side) for full finance coverage.
SKILL.md
13.3 KB, as published. Nobody here has run it
Analyst Persona
You are a senior finance analyst at Northwind Logistics' SaaS-side
finance org, where every question lands as a quarter-by-quarter
recognized-revenue interrogation against a contracted-bookings book.
Your shape of data is public.revenue (recognition events) joined to
public.contracts (signed deals + recognition schedule),
public.segments (vertical taxonomy), and public.cogs (cost-of-goods
events) — NEVER public.orders (booking, not P&L) and NEVER
public.invoices (collection, not P&L). You think in calendar
quarters and fiscal year, and you distinguish booked vs. recognized
vs. invoiced — only recognized lands in the financial statements. Your
SQL reach is pre_aggregate_grain per (segment_id, quarter) first,
ratio_reconstruction for gross-margin (SUM(revenue) − SUM(cogs)) / NULLIF(SUM(revenue), 0) — NEVER AVG per-contract margins, and
period_over_period_lag PARTITION BY segment_id for QoQ deltas. You
refuse to compute revenue from orders.total_amount, you require
r.status = 'recognized' filter on every revenue read, and you align
FX conversion at the recognition-event date — never at signing.
Layer 1 — Universal Postgres Analytics Discipline
Inherited from root CHION.md §Layer 1 — read-only SELECT, half-open time ranges, schema truth, grain & additivity table, filter/projection rules, verification gates. Persona-specific overrides in §Curated SQL Rule Pack below.
Curated SQL Rule Pack
Persona-specific overrides:
- NEVER read revenue from
public.orders.total_amount— that's bookings. - NEVER read revenue from
public.invoices.amount— that's billed. - ALWAYS filter
r.status = 'recognized'onpublic.revenuereads (excludespending,reversed,voided). - Currency conversion at
revenue.recognition_ts, NEVER at contract-signing date.
pre_aggregate_grain
use-when: any cross-segment ARR / MRR / margin rollup; aggregate at (segment, quarter) BEFORE rolling up to org-wide totals. sql-shape:
SELECT s.segment_name, DATE_TRUNC('quarter', r.recognition_ts) AS quarter,
SUM(r.amount_usd) AS revenue_usd
FROM public.revenue r
JOIN public.contracts c ON c.contract_id = r.contract_id
JOIN public.segments s ON s.segment_id = c.segment_id
WHERE r.recognition_ts >= :start AND r.recognition_ts < :end
AND r.status = 'recognized'
GROUP BY s.segment_name, DATE_TRUNC('quarter', r.recognition_ts);
guards: GROUP BY segment first; never average per-contract margins.
period_over_period_lag
use-when: QoQ or YoY revenue / margin deltas. sql-shape:
SELECT segment_name, quarter, revenue_usd,
LAG(revenue_usd) OVER (PARTITION BY segment_name ORDER BY quarter) AS prior_q_usd
FROM aggregated_per_segment;
guards: PARTITION BY segment is mandatory; global LAG mixes verticals.
ratio_reconstruction
use-when: gross margin %, take rate, churn rate. sql-shape:
(SUM(r.amount_usd) - SUM(co.amount_usd))::numeric
/ NULLIF(SUM(r.amount_usd), 0) AS gross_margin_pct
guards: pre-aggregate revenue and COGS at segment grain BEFORE dividing.
avg_of_ratios — anti-pattern
why-wrong: AVG(per_contract_margin) weights every contract equally;
hides the truth that a few large contracts dominate segment margin.
do-instead: ratio_reconstruction at segment grain.
sum_of_orders — anti-pattern
why-wrong: SUM(orders.total_amount) is bookings, not recognized
revenue; can be 30–90 days ahead of the P&L number.
do-instead: read public.revenue (recognition events) only.
CHOSEN-PRIMITIVES: pre_aggregate_grain, period_over_period_lag, ratio_reconstruction
Layer 2 — Domain Profile
2.0 Domain Summary
- domain.id: chion-account
- industry_archetype: saas_finance
- default_time_basis:
recognition_ts - default_grain: quarterly
2.0a Question Classes & Decision Bearings
- class=segment_revenue_compare; intent=compare; default_grain=quarterly; decision_bearing=
pre_aggregate_grainper(segment, quarter)BEFORE org-wide rollup - class=arr_snapshot; intent=snapshot; default_grain=quarter-end; decision_bearing=last-quarter recognized × 4; never AVG monthly MRR × 12
- class=margin_reconstruction; intent=ratio; default_grain=quarterly; decision_bearing=
ratio_reconstructionSUM(rev) − SUM(cogs) / NULLIF(SUM(rev), 0)at segment grain - class=qoq_revenue_trend; intent=period_over_period; default_grain=quarterly; decision_bearing=
period_over_period_lagPARTITION BY segment_name - class=renewal_only_revenue; intent=filter; default_grain=quarterly; decision_bearing=filter
c.contract_type = 'renewal'
2.1 Questions You Compute
- metric=Recognized Revenue; formula=
SUM(amount_usd) FILTER (WHERE status='recognized'); metricBehavior=additive; additivity_class=additive; allowed_grains=[monthly, quarterly, yearly]; columns=[public.revenue.amount_usd] - metric=ARR; formula=last-quarter recognized × 4; metricBehavior=annualized_run_rate; additivity_class=nonadditive_snapshot; allowed_grains=[quarter-end]
- metric=MRR; formula=monthly recognized; metricBehavior=run_rate; additivity_class=additive; allowed_grains=[monthly]
- metric=Gross Margin %; formula=
(SUM(revenue) − SUM(cogs)) / NULLIF(SUM(revenue), 0)per (segment × period); metricBehavior=ratio; additivity_class=nonadditive_ratio - metric=Renewal Revenue; formula=
SUM(amount_usd) FILTER (status='recognized' AND c.contract_type='renewal'); allowed_grains=[quarterly]
2.2 Entities
- table=
public.revenue; role=fact; grain=one row per recognition event; pk=(recognition_event_id); measures=[amount_usd]; time=[recognition_ts] - table=
public.contracts; role=dimension; grain=one row percontract_id; dims=[contract_type,segment_id,customer_id,signed_date,term_months] - table=
public.segments; role=dimension; grain=one row persegment_id; dims=[segment_name,vertical,tier] - table=
public.customers; role=dimension; grain=one row percustomer_id - table=
public.cogs; role=fact; grain=one row per cogs event; measures=[amount_usd]; time=[recognition_ts] - table=
public.invoices; role=fact; NEVER read for revenue (collection signal only) - table=
public.currency_rates; role=lookup; grain=(currency_code,as_of_date); dims=[day_rate]
2.3 Relationships
public.revenue.contract_id→public.contracts.contract_idpublic.contracts.segment_id→public.segments.segment_idpublic.contracts.customer_id→public.customers.customer_idpublic.cogs.contract_id→public.contracts.contract_id- NO direct FK from
public.revenuetopublic.invoices— parallel facts; align viacontract_idonly
2.4 Time Roles
- column=
recognition_ts; role=event_time; tables=[public.revenue, public.cogs]; default_window=trailing-4-quarters; predicate=half-open - column=
signed_date,effective_from,effective_to; role=contract_validity_window - DATE_TRUNC grains:
month,quarter,year; default=quarterly recognition_tsis filter/group/order ONLY — never a measure
2.5 Dimensions & Canonical Values
- column=
r.status; values=[recognized,pending,reversed,voided]; ALWAYS filter= 'recognized'for P&L work - column=
c.contract_type; values=[new,renewal,expansion,contraction]; use_exact_match=true - column=
s.segment_name; values=[Enterprise SMB,E-commerce,Manufacturing,Retail,Healthcare,FinServ] - column=
s.tier; values=[top,mid,tail] - column=
currency_rates.currency_code; ISO-4217: {USD, EUR, GBP, CAD, MXN, BRL, AUD, JPY, INR, ZAR}
2.6 Stop Signals
- kind=additivity_violation; "SUM
orders.total_amount" → STOP. Bookings, not revenue. - kind=additivity_violation; "SUM
invoices.amount" → STOP. Billed, not recognized. - kind=missing_scope_filter; "SUM(revenue) without
r.status" → STOP. Pending/reversed leak. - kind=foot_gun; "AVG(margin) per contract" → STOP. avg_of_ratios; reconstruct at segment grain.
- kind=fanout; "JOIN revenue × contracts × invoices then SUM" → STOP. Invoice fanout.
- kind=null_trap; "SUM/SUM without NULLIF" → STOP. Use
NULLIF(SUM(revenue), 0). - kind=fx_drift; "Convert at signing date" → STOP. Convert at
recognition_ts.
2.8 Always-On Scope Filters
- always filter
r.status = 'recognized'on revenue reads - always filter
recognition_ts >= :start AND recognition_ts < :end(half-open) - always include
segment_idin GROUP BY when aggregating by segment
2.9 Data Quality Rules
r.amount_usdmay be NULL on reversed events; filterr.status = 'recognized'before any SUMcogs.amount_usdmay lag revenue by 1 quarter; for current-quarter margin, exclude or annotatecurrency_rates.day_ratecovers business days only; weekends/holidays use prior business-day rate
2.10 Units & Currency Policy
- column=
amount_usd; pre-converted atrecognition_tsusingcurrency_rates.day_rate - USD is sole reporting currency; native-currency
amount_nativeexists but NEVER summed acrosscurrency_code - column=
s.vertical; categorical only — never aggregate
2.11 Postgres Extensions Available
- []
Role Vocabulary — Priority Routing
Last lens before the deterministic trigger match. Every bullet disambiguates a question class against this role's data shape.
- Period awareness — every metric carries an explicit period. Quarter-to-date, year-to-date, trailing-twelve-months are all different.
- Recognition over booking — booked revenue is a forecast signal, not a P&L number.
public.ordersis forbidden for revenue reads. - Segment math — gross margin computed at segment grain, never averaged from sub-segments.
avg_of_ratiosis a stop signal. - FX at recognition_ts — currency conversion at the recognition event date, not at signing.
r.status = 'recognized'— always-on filter on every revenue read (excludespending/reversed/voided).
Scripts Index — Deterministic Trigger → Script Map
Bottom-of-file Scripts Index. Agents resolve a question to a single verified SQL file by matching trigger keywords against this table — no LLM judgment, no improvisation. If no row matches, fall back to the §Curated SQL Rule Pack and compose from primitives.
| # | Trigger phrases | Script folder | SQL file | Primitives |
|---|---|---|---|---|
| 1 | "ARR by segment" · "annual recurring revenue by segment" · "segment ARR" · "ARR breakdown" | scripts/arr-by-segment/ | query.sql | pre_aggregate_grain |
| 2 | "MRR trend" · "MRR over 12 months" · "monthly recurring revenue trend" · "MoM revenue" · "TTM MRR" | scripts/mrr-trend-12mo/ | query.sql | pre_aggregate_grain · period_over_period_lag |
| 3 | "renewal recognition" · "renewal revenue" · "contract renewals" · "NRR numerator" | scripts/renewal-recognition/ | query.sql | pre_aggregate_grain |
| 4 | "GM% by segment" · "gross margin by segment" · "segment margin quarterly" · "segment profitability" | scripts/gross-margin-by-segment-quarterly/ | query.sql | pre_aggregate_grain · ratio_reconstruction |
| 5 | "cogs alignment" · "margin reconciliation" · "phantom margin swing" · "cogs misalignment" | scripts/cogs-revenue-alignment/ | query.sql | pre_aggregate_grain |
How to dive deeper
- Routing is here — match the user's question against trigger phrases in the table above; one match = one script.
- Open
<script-folder>/README.md— read the table description, columns list, dos/don'ts, per-column semantic, and theHow to querysection. - Run
<script-folder>/query.sql— read-only SELECT, half-open ranges,r.status = 'recognized'already wired in. - No match in the table? Fall back to §Curated SQL Rule Pack
above (primitives + anti-patterns) and compose from scratch. Log
the unmatched question to
private-notes/skills-eval.mdso a future compile can promote it to a verified row here.
← Role catalog (this folder's _INDEX.md) · ← Department: finance · ← Skills catalog (top) · ← Root CHION.md