A Three-Layer Architecture for AI Reporting Skills
January 2026
A design pattern for AI-generated business reports: separate the data layer, the analysis-standards layer, and the presentation layer so that a metric's definition only has to change in one place, not five.
I. Background and the Problem
I’d built a handful of AI “skills” for generating recurring business reports — a weekly business review, an industry-contribution breakdown, a monthly business review, and a few others. Each one worked, but as the set grew, some structural problems showed up:
| Problem | Description |
|---|---|
| Duplicated data pulls | Every skill had its own data-fetching logic, so the same metric could get pulled — and computed — more than once. |
| Definition drift | Metric definitions were scattered across skills, so numbers would occasionally stop matching each other. |
| High maintenance cost | Changing one definition (say, a reporting-week boundary or an org-level filter) meant hunting down and editing it in multiple places. |
| Not shareable | Hardcoded local paths and parameters meant that even if someone else installed a skill, it wouldn’t actually run for them. |
II. Target Architecture: Three-Layer Decoupling
The application layer (application-skill) depends on the analysis-standards layer (analysis-skill), which in turn depends on the data layer (data-skill).
- data-skill (data layer): the single source of truth for data access — encapsulates all data-fetching logic and metric-definition constraints.
- analysis-skill (analysis-standards layer): a unified analytical knowledge base. Does no data-fetching of its own — pure written standards.
- application-skill (presentation layer): focused purely on output/presentation, calling the two layers above as needed.
III. Responsibilities by Layer
3.1 data-skill (Data Layer)
Responsibility: the sole entry point for data, encapsulating all fetching logic and metric definitions.
What it contains:
- Dashboard data extraction (browser-automation against internal BI tools)
- SQL-based data pulls (e.g. gross transaction value or order count broken out by channel)
- BI-API-based data pulls (including the quirks of the internal query syntax)
- Metric definitions: SQL/API filter conditions (org-unit filters, business-group filters, date ranges), calculation formulas (e.g. take-rate = revenue ÷ gross transaction value)
- Local cache management (SQLite, partitioned by brand × metric × week/day)
- Cache-refresh policy (scheduled weekly refresh, or manual force-refresh)
Output: a standard JSON schema that any downstream consumer can parse.
{
"brand": "brand_a",
"week": "WK11",
"begin_date": "2026-03-05",
"end_date": "2026-03-11",
"metrics": {
"gross_transaction_value": {"this_week": 2104000000, "wow": 0.0258, "yoy": 0.155},
"order_count": {"this_week": 123456, "wow": -0.01, "yoy": 0.08}
},
"industry_breakdown": [
{"industry": "category_a", "gross_transaction_value": 350000000, "wow": 0.03, "yoy": 0.12},
{"industry": "category_b", "gross_transaction_value": 280000000, "wow": -0.01, "yoy": 0.08}
],
"channel_breakdown": [
{"channel": "search", "gross_transaction_value": 620000000, "wow": 0.02, "yoy": 0.18},
{"channel": "recommendation_feed", "gross_transaction_value": 410000000, "wow": -0.03, "yoy": 0.05}
]
}
Cache storage design (tiered SQLite)
Data lands in a single analytics.db, with table names following a {granularity}_{dimension} convention — weekly_org, weekly_industry, weekly_org_industry, monthly_org, daily_org, and so on.
A metadata table (cache_meta) governs the refresh policy per table:
CREATE TABLE cache_meta (
table_name TEXT PRIMARY KEY,
refresh_mode TEXT, -- 'scheduled' | 'on_demand'
cron_expr TEXT, -- e.g. '0 8 * * 4' (Thursday 8am)
last_updated TEXT, -- ISO timestamp
ttl_hours INTEGER, -- cache validity window in on-demand mode
source_type TEXT, -- 'bi_api' | 'hive_sql' | 'browser_extract'
source_config TEXT -- JSON, records the query parameters
);
Two refresh strategies:
- Scheduled: for data a weekly report always needs. Register a cron job that periodically overwrites (
REPLACE INTO) or appends by time partition. - On-demand: for dimensions only needed during deeper analysis, not worth refreshing every week. Before use, check
last_updated+ttl_hours; if the cache is still valid, read directly — if expired, trigger a fresh query, write it, and update the metadata.
def get_data(table_name, query_fn):
if is_cache_valid(table_name): # check ttl
return read_from_db(table_name)
else:
data = query_fn() # trigger BI API / Hive SQL
write_to_db(table_name, data)
update_meta(table_name)
return data
Why SQLite over CSV/JSON: supports WHERE/JOIN queries across dimensions; multi-dimension queries can join tables at different granularities; appending historical data doesn’t overwrite old data; metadata can be managed independently; and the whole data layer stays to one or two files instead of sprawling into dozens.
3.2 analysis-skill (Analysis-Standards Layer)
Responsibility: a unified analytical knowledge base — does no data-fetching or generation itself (pure written standards).
Where a given standard belongs (data-skill vs. analysis-skill):
| Standard Type | Belongs To | Why |
|---|---|---|
| SQL/API filter conditions (org-unit filters, business-group filters, date-range definitions) | data-skill | Needed at fetch time — a lower-layer concern. |
| Metric calculation formulas (take-rate = revenue ÷ GTV) | data-skill | Affects the computed result itself. |
| Metric business definitions (what “precise-match search volume” actually means) | analysis-skill | Shapes the analytical narrative and perspective. |
| How a conclusion should be written, and which framework to use | analysis-skill | Pure analytical standard. |
Core principle: filter/calculation constraints live in
data-skill; analytical perspective lives inanalysis-skill. Data flows one direction between the two layers (data → analysis).
What analysis-skill contains:
| Module | Content |
|---|---|
| Metric business definitions | Definitions of key metrics, and the analytical lens for each |
| Analytical frameworks | The weekly-review’s four-section structure, the monthly-review framework, the industry-contribution breakdown method |
| Business knowledge base | Seasonality patterns, competitive-analysis lenses, intra-month rhythms |
| Formatting standards | Decimal-place rules, color conventions (positive = blue, negative = red), unit conversions |
Where report templates live Different reports need different specific formats, so templates split into two tiers:
- The analysis layer (analysis-skill) owns: structural standards, independent of output format
- The business-overview report’s module composition: totals → volume/price drivers → health-check summary → keyword highlights per section
- The deep-dive report’s four-part structure: channel → industry → business levers → key watch items
- Priority-flagging convention (🔴/🟡/🟢)
- The analytical lens for each section
- Each report-layer skill owns: format constraints (defined independently per report)
| Report | Format Constraint |
|---|---|
| Weekly-review text summary | Concise, no more than five bullets, roughly 300 words |
| Weekly-review HTML report | Populates a --panorama-text / --insight-text parameter |
| Monthly review | Monthly view, must cover both month-over-month and year-over-year, can run longer |
| Industry-contribution dashboard | Pure chart output, no text template needed |
Core principle: the analysis layer defines structure; the report layer defines format. Report-layer skills reference the analysis layer’s structural standard but decide their own word count and presentation on their own.
3.3 Application Layer (Report Layer)
Responsibility: focused purely on “presentation” — agnostic to where the data comes from or how metrics are defined.
| Skill | Input | Output |
|---|---|---|
| weekly-report-skill | data-skill’s JSON + analysis-skill’s structural standard | HTML report (channel/industry/operations/traffic, four sections) |
| dashboard-skill | data-skill’s JSON + analysis-skill’s structural standard | Interactive industry-contribution dashboard |
| monthly-report-skill | data-skill’s JSON + analysis-skill’s structural standard | Monthly HTML report |
| peak-analysis-skill | data-skill’s historical data (no analysis-skill needed) | Historical peak-analysis table |
Each report skill’s own manifest declares its dependency chain up front: run data-skill first to fetch data, then load analysis-skill’s standards, then generate and send the report.
IV. Designing for Shareability
Self-containment principle: no skill depends on a local absolute path; every script lives inside its own skill directory; dataset IDs are passed as parameters, never hardcoded.
Before installing a report skill, install data-skill and analysis-skill first — then everything runs end to end.
Directory structure:
~/.skills/
├── data-skill/
│ ├── SKILL.md <- metric definitions + fetch logic docs
│ └── scripts/
│ ├── query_overview.py
│ ├── query_industry.py
│ ├── run_channel_sql.py
│ └── cache_manager.py
├── analysis-skill/
│ ├── SKILL.md <- analytical framework + report structure + formatting standards
│ └── templates/
│ ├── panorama_structure.md <- business-overview structural standard (no word-count/param constraints)
│ └── insight_structure.md <- deep-dive structural standard (no word-count/param constraints)
└── weekly-report-skill/
├── SKILL.md <- declares dependencies + execution steps + format constraints (word count/params)
└── scripts/
└── generate_report.py
V. Migration Path (Phased)
- Phase 1 (within a week, low risk): extract analysis-skill
- Pull the scattered analytical standards out of working notes and organize them into a standalone skill
- Leave all existing data-fetching logic untouched — just restructure the standards documents
- Validation: have the weekly-report skill load analysis-skill’s standards before writing its analysis
- Phase 2 (2–4 weeks): build the data-skill skeleton
- Extract an existing report’s data-fetching scripts into a shared module
- Build the SQLite cache, starting with weekly report data (high-frequency) and historical peak data (low-frequency)
- Validation: have both the weekly report and industry-contribution report pull from the shared cache
- Phase 3 (after ~1 month): slim down the report layer
- Gradually replace each report skill’s own data-fetching logic with calls into data-skill
- Keep each skill’s own generation scripts (
generate_report.py, etc.), just swap out where the data comes from
VI. Sharing and Maintenance
6.1 How It’s Shared
The three core skill types (data-skill, analysis-skill, and each report-layer skill) get published to a shared internal skill marketplace, with access and edit permissions opened up to everyone who needs them, supporting collaborative maintenance.
6.2 Version-Update Process
During day-to-day local use, a version update is triggered whenever:
- A data definition changes (a filter field’s logic changes, a metric is added or retired)
- The analytical framework or a report template changes meaningfully
- A bug affecting data accuracy gets fixed
Update steps:
- Validate the fix locally (run it, compare the numbers)
- Publish a new version to the marketplace with clear release notes (what changed, what’s affected)
- Notify everyone downstream to pull the update locally
- Once everyone confirms they’ve updated, archive the old version
6.3 Things to Watch For
| Item | Note |
|---|---|
| Backward compatibility | Changes to data-skill’s JSON schema need versioning (v1/v2) so the report layer doesn’t break outright. |
| Scope of change communicated | Every release clearly states which skills are affected and whether data needs to be re-pulled. |
| No silent local changes | Definition-level changes never get quietly absorbed locally — they must go through the marketplace’s version-update process, to keep everyone consistent. |
VII. Risks and Mitigations
| Risk | Description | Mitigation |
|---|---|---|
| Stale cache | Report runs on stale data if the cache didn’t refresh | Timestamp everything; auto-check on each call; auto-refresh if expired |
| Breaking schema change | data-skill’s JSON schema changes and the report layer breaks | Version the schema (v1/v2), maintain backward compatibility |
| Heavy agent context | Loading three SKILL.md files at once |
Keep analysis-skill lean — only the must-follow standards, with background material moved to an appendix |
| Mismatched definitions after sharing | Someone else’s dataset IDs differ from yours | Make data-skill’s dataset IDs a parameter, never hardcoded |
VIII. Conclusion
Recommended approach: the three-layer architecture (data-skill + analysis-skill + report-layer skills), migrated incrementally through Phase 1 → Phase 2 → Phase 3.
What to do right now: Phase 1 — extract analysis-skill. Low risk, high payoff, and doable within a week.