Generate an audit-support evidence package over FinanceOS data - completeness, reconciliation, mapping-integrity, and substantive-sample checks with a PDF report and Excel evidence workbook. Not a SOX certification - access-control, change-management, and IT-general-control evidence is out of scope.
Generate an audit-support evidence package over FinanceOS data — the control checks that this data surface can actually evidence, packaged for management and auditors.
Creates both a PDF report (for management) and an Excel evidence workbook (for the audit trail).
Honest scope — read first. This is not a SOX certification. In scope are the data-evidencable control families: completeness & period integrity, consistency/reconciliation, account-mapping integrity, and substantive sampling — each backed by a real query this skill can run. Access control, change management, and IT general controls are out of scope: the FinanceOS MCP surface has no audit-log, access-history, or user-activity endpoint, so those control families require system-administration evidence outside this tool's reach. Never present a control result this skill cannot substantiate with a tool call — the evidence workbook carries a mandatory "Out of scope — requires external evidence" sheet so no reader mistakes the package for full SOX coverage.
| Argument | Description | Default |
|---|---|---|
--year <YYYY> | REQUIRED Calendar year | — |
--quarter <Q#> | REQUIRED Quarter: Q1, Q2, Q3, Q4 | — |
--output-pdf <file> | PDF output path | tmp/Audit_Report_YYYY_QX_DATE.pdf |
--output-xlsx <file> | Excel evidence path | tmp/Audit_Evidence_YYYY_QX_DATE.xlsx |
Each check maps to tools this skill can actually call — nothing goes in the evidence package without a query behind it:
Completeness & period integrity. Discover the date field's range and
confirm every expected period in the audited quarter/year is present
(distinct values of the reporting-month field). Then verify integrity with
two grouped calls over the audited window — by scenario and by period:
sum each response's rows (including [null]) to get that slicing's
total — responses carry one row per group and no total row (Data-scope
preamble, item 4) — and the two totals must equal each other, since both
describe the same window sliced two ways. Tools:
start_distinct_values_by_alias/_by_id → poll
get_distinct_values_result_by_alias/_by_id, and
start_aggregation_by_alias/_by_id → poll
get_aggregation_result_by_alias/_by_id (async-fetch pattern).
Consistency / reconciliation. The reconciliation control is the
/dr-reconcile skill's four independent-source checks — cross-endpoint
agreement, balance-sheet identity, cross-grain roll-up, and
scenario/period integrity. That skill's SKILL.md is the single source of
the method (query shapes, tolerance, pass/fail rules) — but its native
run is year-scoped (--year is its only window argument), while this
audit is quarter-scoped. So do not delegate a bare full-year run: apply
the four checks' method with the date filter narrowed to the audited
window (--year + --quarter, the same advanced date-range filter used
by every other family in this evidence package), and record in the
evidence which window each check actually covered. A full-year
reconciliation pass does not evidence a Q-scoped control — a
quarter-local inconsistency can net out over the year.
Account-mapping integrity. Deliberately reuses the roll-up
mechanics from the reconciliation control's Check 3 — but for a
different verdict: not "does the pipeline roll up consistently" (that
pass/fail belongs to check family 2 above) but "which accounts are
unmapped". Two aggregates over the same scope —
dimensions=[<parent_level>] and dimensions=[<parent_level>, <child_level>], both SUM(<amount>), with the same parameters,
tolerance, and audited-window date filter as family 2's roll-up check
(year + quarter — never the full year) so both comparisons cover the same
period. Sharing the scope aligns the comparison; it does not make the
totals agree — a mismatch is exactly the finding. For each parent bucket,
the sum of its child rows — including the [null] bucket — must equal
the parent's own row to the cent.
Then name the accounts — the two aggregates above cannot. They return
bucket totals, so the presence of a [null] child bucket tells you a
parent has unmapped rows, not which accounts carry them. An exception log
that names no account is not auditable evidence.
Trigger on row presence, never on a non-zero amount. Unmapped rows with
offsetting positive and negative amounts net to zero, so a [null] bucket
summing to 0.00 can still hold unmapped accounts — gating on the amount
would silently drop those exceptions from the evidence package. Add a
row count to the family-3 aggregate alongside the sum — metrics=[{SUM of <amount>}, {COUNT of <non_null_row_identifier>}] — and treat
count > 0 as the trigger. SUM(<amount>) reports the net unmapped
amount only; it never decides whether to look.
Choosing <non_null_row_identifier> is load-bearing: COUNT skips nulls,
so it must be a field the discovered schema populates on every source
row (a system row id, or the reporting-date field). It must not be the
account identifier or the child level — those are exactly the fields that
are null on the rows being hunted, so counting them can return 0 while
unmapped rows exist, reintroducing the miss this rule prevents. It must
also differ from <amount>, since one call may carry only one aggregation
per field. Verify non-nullness from the field's profile (null rate 0)
before relying on it; if no such field exists, fall back to the bounded
row-level query below and treat any returned row as the trigger.
For each parent whose [null] child bucket contains rows, run one
follow-up query under the same period + scenario filters:
dimensions=[<parent_level>, <child_level>, <account_id_or_name_field>], SUM(<amount>), filtered to that parent.
Read the rows whose <child_level> is the [null] bucket — those
accounts, with their amounts, are the exception-log entries.get_data_by_alias /
get_data_by_id with select on the account + amount + date columns
and filters scoping that parent, the audited window, and the scenario
(the is null advanced condition isolates the unmapped child set).
Respect the 500-row cap and the truncated envelope, per family 4.The exception log records account identifier, parent bucket, amount, and the window. The roll-up pass/fail itself is reported once, under family 2 — this family reports only the exception list.
Substantive sampling. For the material buckets (largest by absolute
amount in the audited window), pull row-level detail via
get_data_by_alias / get_data_by_id with select on the load-bearing
columns and filters scoping bucket + period, so a human auditor can
trace reported figures to source line items. Respect the 500-row cap —
sample per bucket, never attempt a full extract. If a response arrives
with "truncated": true, the returned rows are an incomplete prefix —
narrow the query per its guidance (more filters / fewer columns / lower
limit+offset paging) and re-fetch; never present the prefix as a complete
sample.
Out of scope — requires external evidence: access control, change management, and IT general controls. No tool available to this skill can observe user access, permission grants, or change history — do not test, score, or opine on these families; list them on the out-of-scope sheet instead.
All of these checks aggregate live data. Run this discovery before any check that queries or aggregates:
Async fetch — aggregations and distinct values run as start → poll.
start_aggregation_by_id/_by_aliasandstart_distinct_values_by_id/_by_aliastake the same arguments as the retired blocking calls (dimensions/metrics/filters; table id + field id, or alias + field alias) and return immediately with{"status": "pending", "handle": {...}}. Echo thathandleback verbatim to the matchingget_aggregation_result_by_*/get_distinct_values_result_by_*tool: a{"status": "running", "retry_after_seconds": N}response means poll again with the same handle after ~N seconds (≈5s) — it is not an error, and large jobs may take several polls; when ready, the result arrives in the familiar shape (for distinct values, passlimitto the result tool). An expired/unknown-handle error means restart with thestart_*tool. Transitional fallback: if thestart_*tools aren't available on the connector (older server), the blocking twinsget_aggregated_data_by_*/get_distinct_values_by_*still work with the same arguments.
Data-scope discovery — run before any aggregate (reuse anything already discovered this conversation).
- Scenario domain. Pull distinct values of the scenario field (
start_distinct_values_by_alias/_by_id→ poll the matching result tool) — never assume a scenario name exists (Budgetfrequently doesn't; many orgs carry only{Actuals, Forecast}). For budget/plan questions, if no budget-like scenario exists, look for a planning-version-like field (alias/name matching/plan|version|cycle|budget/i) and use its versions as the plan side; if neither exists, say so and offer a comparison across the scenarios that do exist.- Account grain. Pull distinct values of each account-hierarchy level field (L0/L1/L2-like). Use the level whose values partition P&L flows into revenue/COGS/opex-like buckets — on many orgs the top level is the balance-sheet equation (ASSET/LIABILITY/EQUITY/INCOME) and P&L line items live one level deeper. For P&L work, scope to P&L flows and exclude balance-sheet buckets; never present asset/liability/equity totals as revenue or expenses.
- Period scope. Discover the date field's range (distinct values of the reporting-month field, or MIN and MAX in two separate calls — one aggregation per field per call). Default every P&L question to the latest complete fiscal year (or trailing 12 closed months) — never an unscoped all-time total: financials tables are multi-year cumulative and mix balance-sheet stock with P&L flow. Label every output with the period + scenario it covers.
- Reading GROUP BY responses. Each response returns exactly one row per requested group — no subtotal rows and no grand-total row. A total is obtained by summing the rows — there is no total row to read. Null groups arrive explicitly labeled
[null]and are real groups; read null counts from that bucket. Defensive filter: keep only rows in which every requested dimension key is present — a roll-up row omits one or more keys entirely, whereas a genuine null is present with the value[null]. On a correct response this is a no-op; it guards against a stale cached response still carrying legacy subtotal and grand-total rows, each of which equals the whole total and would inflate any sum. When COUNT-ing rows per group, aggregate a different field than the GROUP BY dimension itself — a same-field COUNT of the grouped dimension can 500.- Truncated results. Any data tool may return
{"data": [...], "truncated": true, "total_rows": N, "returned_rows": M, "guidance": "..."}when the result exceeds the response size limit (~100 KB). Thedataprefix is incomplete — never compute totals, shares, or trends from it, and never present it as the full result. Follow theguidance: narrow the query (fewer dimensions, more filters, fewer selected columns) or use a business metric for a named KPI, then re-fetch.
In particular, never test a control against a budget-named scenario filter without first confirming it exists in the discovered scenario domain — if the plan side lives in a planning-version-like field, route budget-related evidence through that field's versions instead, and record the actual scenario/version used in the evidence package and audit trail.
When generating Excel or PowerPoint files, apply Datarails brand styling:
Font: Poppins (fall back to Calibri if unavailable). Weights: 400 regular, 600 semibold, 700 bold.
Colors:
| Role | Hex | Use |
|---|---|---|
| Navy | 0C142B | Header/banner background |
| Main text | 333333 | Primary text |
| Secondary | 6D6E6F | Muted/subtitle text |
| Border | 9EA1AA | Cell borders |
| Section bg | F2F2FB | Section header / row header background (lavender) |
| Input bg | EAEAFF | Editable/input cell background |
| Input text | 4646CE | Editable cell text (indigo) |
| Favorable | 2ECC71 | Positive variance / good KPI delta |
| Unfavorable | E74C3C | Negative variance / bad KPI delta |
| Chart 1 | 0C142B | Actuals (navy) |
| Chart 2 | F93576 | Budget (hot pink) |
| Chart 3 | 00B4D8 | Teal |
| Chart 4 | FFA30F | Amber |
Excel layout:
Number formats: _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_) (default), $#,##0 (dollars), $#,##0.0,,"M" (millions), 0.0% (percent)
Variance coloring: Any cell showing a delta/change: green (2ECC71) if favorable, red (E74C3C) if unfavorable. Apply automatically based on value sign and metric context.
PowerPoint: Navy (0C142B) background, 16:9 widescreen, Poppins font, white text, amber (FFA30F) accent lines, card backgrounds 001F37.
If asked to add live / refreshable Datarails formulas (DR.GET) to a generated workbook, the only valid form is:
=DR.GET(Value, "[DimensionName]", CellRef, "[DimensionName]", CellRef, ...)=DR.GET(Value,"financials","Amount","SUM",...)
is invented syntax that the Datarails Add-in cannot parse or refresh."[Scenario]").
Dimension values are always cell references, never hardcoded strings.Value
referring to the string constant "Value"
(wb.defined_names.add(DefinedName("Value", attr_text='"Value"'))) —
otherwise Excel autocorrects the bare token to its built-in VALUE() and
the formula breaks.=DR.GET(...) only — never wrapped in IFERROR/IF/ROUND.The get-formula skill (/dr-get-formula) is the full reference — parameter
cells, validated dimension values, report layouts. Prefer it for whole formula
workbooks; apply this contract when adding DR.GET formulas to a workbook here.
Sheets map one-to-one to the in-scope checks:
/dr-reconcile four-check results (including any
noted skips)[null]-bucket
(unmapped account) flags/dr-audit --year 2025 --quarter Q4/dr-audit --year 2025 --quarter Q2/dr-audit --year 2025 --quarter Q4 \
--output-pdf audits/audit_q4_2025.pdf \
--output-xlsx audits/evidence_q4_2025.xlsx# Year-end evidence package — in-scope control families only
/dr-audit --year 2025 --quarter Q4# Regular check of completeness, reconciliation, and mapping controls
/dr-audit --year 2025 --quarter Q3# Data evidence feeding management's SOX 404 process —
# does NOT substitute for access-control or ITGC testing
/dr-audit --year 2025 --quarter Q4# Provide auditors with the report and traceable evidence
/dr-audit --year 2025 --quarter Q4Mapped to COSO components only where FinanceOS data provides the evidence:
/dr-reconcile)Professional audit recommendations:
/dr-reconcile)[null] roll-up bucketWorks with:
/dr-anomalies-report - Data quality validation/dr-reconcile - Consistency checking/dr-extract - Data extraction/dr-dashboard - Control monitoring/dr-reconcile - Ongoing reconciliation/dr-anomalies-report - Data quality/dr-extract - Data sourcing/dr-dashboard - KPI monitoring0a038a3
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.