optimal-cli 1.0.0 → 1.1.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/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +10 -0
- package/.env.example +17 -0
- package/CLAUDE.md +67 -0
- package/COMMANDS.md +264 -0
- package/PUBLISH.md +70 -0
- package/agents/content-ops.md +2 -2
- package/agents/financial-ops.md +2 -2
- package/agents/infra-ops.md +2 -2
- package/apps/.gitkeep +0 -0
- package/bin/optimal.ts +278 -591
- package/docs/MIGRATION_NEEDED.md +37 -0
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/optimal-cli-config-registry-v1.md +71 -0
- package/hooks/.gitkeep +0 -0
- package/lib/config/registry.ts +5 -4
- package/lib/kanban-obsidian.ts +232 -0
- package/lib/kanban-sync.ts +258 -0
- package/lib/kanban.ts +239 -0
- package/lib/obsidian-tasks.ts +231 -0
- package/package.json +5 -19
- package/pnpm-workspace.yaml +3 -0
- package/scripts/check-table.ts +24 -0
- package/scripts/create-tables.ts +94 -0
- package/scripts/migrate-kanban.sh +28 -0
- package/scripts/migrate-v2.ts +78 -0
- package/scripts/migrate.ts +79 -0
- package/scripts/run-migration.ts +59 -0
- package/scripts/seed-board.ts +203 -0
- package/scripts/test-kanban.ts +21 -0
- package/skills/audit-financials/SKILL.md +33 -0
- package/skills/board-create/SKILL.md +28 -0
- package/skills/board-update/SKILL.md +27 -0
- package/skills/board-view/SKILL.md +27 -0
- package/skills/delete-batch/SKILL.md +77 -0
- package/skills/deploy/SKILL.md +40 -0
- package/skills/diagnose-months/SKILL.md +68 -0
- package/skills/distribute-newsletter/SKILL.md +58 -0
- package/skills/export-budget/SKILL.md +44 -0
- package/skills/export-kpis/SKILL.md +52 -0
- package/skills/generate-netsuite-template/SKILL.md +51 -0
- package/skills/generate-newsletter/SKILL.md +53 -0
- package/skills/generate-newsletter-insurance/SKILL.md +59 -0
- package/skills/generate-social-posts/SKILL.md +67 -0
- package/skills/health-check/SKILL.md +42 -0
- package/skills/ingest-transactions/SKILL.md +51 -0
- package/skills/manage-cms/SKILL.md +50 -0
- package/skills/manage-scenarios/SKILL.md +83 -0
- package/skills/migrate-db/SKILL.md +79 -0
- package/skills/preview-newsletter/SKILL.md +50 -0
- package/skills/project-budget/SKILL.md +60 -0
- package/skills/publish-blog/SKILL.md +70 -0
- package/skills/publish-social-posts/SKILL.md +70 -0
- package/skills/rate-anomalies/SKILL.md +62 -0
- package/skills/scrape-ads/SKILL.md +49 -0
- package/skills/stamp-transactions/SKILL.md +62 -0
- package/skills/upload-income-statements/SKILL.md +54 -0
- package/skills/upload-netsuite/SKILL.md +56 -0
- package/skills/upload-r1/SKILL.md +45 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/migrations/.gitkeep +0 -0
- package/supabase/migrations/20250305000001_create_agent_configs.sql +36 -0
- package/supabase/migrations/20260305111300_create_cli_config_registry.sql +22 -0
- package/supabase/migrations/20260306195000_create_kanban_tables.sql +97 -0
- package/tests/config-command-smoke.test.ts +395 -0
- package/tests/config-registry.test.ts +173 -0
- package/tsconfig.json +19 -0
- package/agents/profiles.json +0 -5
- package/docs/CLI-REFERENCE.md +0 -361
- package/lib/assets/index.ts +0 -225
- package/lib/assets.ts +0 -124
- package/lib/auth/index.ts +0 -189
- package/lib/board/index.ts +0 -309
- package/lib/board/types.ts +0 -124
- package/lib/bot/claim.ts +0 -43
- package/lib/bot/coordinator.ts +0 -254
- package/lib/bot/heartbeat.ts +0 -37
- package/lib/bot/index.ts +0 -9
- package/lib/bot/protocol.ts +0 -99
- package/lib/bot/reporter.ts +0 -42
- package/lib/bot/skills.ts +0 -81
- package/lib/errors.ts +0 -129
- package/lib/format.ts +0 -120
- package/lib/returnpro/validate.ts +0 -154
- package/lib/social/meta.ts +0 -228
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: manage-scenarios
|
|
3
|
+
description: Save, load, compare, and delete named budget scenarios for Wes projections
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Manages named budget scenarios for the Wes dashboard budget projection system. Scenarios are snapshots of `fpa_yield_assumptions` data (unit forecasts, WIP units, yield rates) that can be saved, loaded, compared side-by-side, and shared across users. This enables what-if analysis — e.g., "optimistic Q2" vs "conservative Q2" scenarios.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **action** (required): One of `save`, `load`, `list`, `compare`, `delete`.
|
|
11
|
+
- **name** (required for save/load/delete): Scenario name (e.g., `"optimistic-q2-2026"`, `"baseline-fy25"`).
|
|
12
|
+
- **fiscal-year** (optional): Fiscal year filter. Default: current fiscal year.
|
|
13
|
+
- **user-id** (optional): User UUID for scenario ownership. Default: Carlos's user ID.
|
|
14
|
+
- **compare-with** (required for compare): Second scenario name to compare against.
|
|
15
|
+
- **format** (optional): Output format for compare/list — `table` (default) or `csv`.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
1. Call `lib/budget/scenarios.ts::manageScenarios(action, options)` to orchestrate
|
|
19
|
+
2. **save**: Snapshot current `fpa_yield_assumptions` rows (filtered by user + fiscal year) into a named scenario record, stored as JSON blob with metadata (name, created_at, user_id, row_count)
|
|
20
|
+
3. **load**: Restore a saved scenario by overwriting `fpa_yield_assumptions` rows for the target user + fiscal year with the snapshot data. Creates a backup of current data first.
|
|
21
|
+
4. **list**: Show all saved scenarios with name, created_at, fiscal_year, row_count, user
|
|
22
|
+
5. **compare**: Load two scenarios side-by-side and compute deltas — total units, revenue projection, per-program differences
|
|
23
|
+
6. **delete**: Remove a named scenario (with confirmation)
|
|
24
|
+
7. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
**list**:
|
|
28
|
+
```
|
|
29
|
+
| Name | FY | Created | Rows | User |
|
|
30
|
+
|------|----|---------|------|------|
|
|
31
|
+
| baseline-fy25 | FY25 | 2026-01-15 | 1,264 | Carlos |
|
|
32
|
+
| optimistic-q2 | FY26 | 2026-02-20 | 1,310 | Carlos |
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**compare**:
|
|
36
|
+
```
|
|
37
|
+
Comparing: baseline-fy25 vs optimistic-q2
|
|
38
|
+
|
|
39
|
+
| Program | Baseline Units | Optimistic Units | Delta | Delta % |
|
|
40
|
+
|---------|---------------|-----------------|-------|---------|
|
|
41
|
+
| BRTON-WM | 42,000 | 48,500 | +6,500 | ↑15.5% |
|
|
42
|
+
| FORTX-POOL | 18,200 | 16,800 | -1,400 | ↓-7.7% |
|
|
43
|
+
| Total | 7,352,022 | 7,891,450 | +539,428 | ↑7.3% |
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**save**: `Saved scenario "optimistic-q2" (1,310 rows, FY26)`
|
|
47
|
+
|
|
48
|
+
**load**: `Loaded scenario "baseline-fy25" → overwrote 1,264 rows (backup: auto-backup-20260301T100000)`
|
|
49
|
+
|
|
50
|
+
## CLI Usage
|
|
51
|
+
```bash
|
|
52
|
+
# Save current assumptions as a named scenario
|
|
53
|
+
optimal manage-scenarios save --name optimistic-q2 --fiscal-year FY26
|
|
54
|
+
|
|
55
|
+
# Load a saved scenario
|
|
56
|
+
optimal manage-scenarios load --name baseline-fy25
|
|
57
|
+
|
|
58
|
+
# List all scenarios
|
|
59
|
+
optimal manage-scenarios list
|
|
60
|
+
|
|
61
|
+
# Compare two scenarios
|
|
62
|
+
optimal manage-scenarios compare --name baseline-fy25 --compare-with optimistic-q2
|
|
63
|
+
|
|
64
|
+
# Delete a scenario
|
|
65
|
+
optimal manage-scenarios delete --name old-test-scenario
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Environment
|
|
69
|
+
Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
|
|
70
|
+
|
|
71
|
+
## Tables Touched
|
|
72
|
+
- `fpa_yield_assumptions` — read/write unit forecasts and yield data. Unique key: `(user_id, fiscal_year, month, master_program_id)`
|
|
73
|
+
- `wes_imports` — baseline reference for sync validation
|
|
74
|
+
- Scenario storage: TBD — either a new `budget_scenarios` table or JSON blobs in an existing metadata table
|
|
75
|
+
|
|
76
|
+
## Gotchas
|
|
77
|
+
- **fpa_yield_assumptions refactor**: Table was refactored Feb 2026 — dropped 3 WIP% columns, added `wip_units INTEGER`. Unique key is `(user_id, fiscal_year, month, master_program_id)`.
|
|
78
|
+
- **Wes sync pattern**: When baselines diverge, copy from Carlos to all other users. FY25 baseline: 7,352,022 units, 1,264 rows, 97 masters.
|
|
79
|
+
- **Load creates backup**: Loading a scenario automatically saves the current state as `auto-backup-{timestamp}` before overwriting.
|
|
80
|
+
- **User isolation**: Scenarios are per-user by default. Cross-user scenario sharing requires explicit user-id parameter.
|
|
81
|
+
|
|
82
|
+
## Status
|
|
83
|
+
Implementation status: Not yet implemented. Spec only. Lib function `lib/budget/scenarios.ts` to be built on top of existing `lib/budget/projections.ts` in wes-dashboard.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrate-db
|
|
3
|
+
description: Run Supabase database migrations across ReturnPro or OptimalOS instances
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Executes pending database migrations against the linked Supabase instance using `supabase db push --linked`. This is the only sanctioned way to modify database schemas — Carlos never runs SQL manually. Supports both the ReturnPro instance (financial data) and the OptimalOS instance (kanban, transactions).
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **instance** (required): Target Supabase instance — `returnpro` or `optimalos`.
|
|
11
|
+
- **migration** (optional): Specific migration file name to create before pushing (e.g., `add-budget-scenarios-table`). If provided, creates the file in `supabase/migrations/` first.
|
|
12
|
+
- **sql** (optional): SQL content for a new migration file. Required if `--migration` is specified.
|
|
13
|
+
- **dry-run** (optional): Show pending migrations without applying them.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
1. Call `lib/infra/migrate.ts::migrateDb(instance, options?)` to orchestrate
|
|
17
|
+
2. **Resolve instance** — map `returnpro` to `/home/optimal/dashboard-returnpro` supabase config, `optimalos` to `/home/optimal/optimalos` supabase config (or the consolidated `/home/optimal/optimal-cli/supabase` config)
|
|
18
|
+
3. **Create migration file** (if `--migration` + `--sql` provided):
|
|
19
|
+
- Generate timestamped filename: `{YYYYMMDDHHmmss}_{migration-name}.sql`
|
|
20
|
+
- Write SQL content to `supabase/migrations/{filename}`
|
|
21
|
+
4. **List pending** — show which migrations haven't been applied yet
|
|
22
|
+
5. **Push** — run `supabase db push --linked` from the appropriate project directory
|
|
23
|
+
6. **Verify** — confirm migration was applied successfully
|
|
24
|
+
7. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
```
|
|
28
|
+
Instance: returnpro
|
|
29
|
+
Project dir: /home/optimal/dashboard-returnpro
|
|
30
|
+
Pending migrations: 1
|
|
31
|
+
|
|
32
|
+
Applying: 20260301100000_add-budget-scenarios-table.sql
|
|
33
|
+
Migration applied successfully.
|
|
34
|
+
|
|
35
|
+
Current schema version: 20260301100000
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Dry-run:
|
|
39
|
+
```
|
|
40
|
+
[DRY RUN] Instance: returnpro
|
|
41
|
+
Pending migrations:
|
|
42
|
+
1. 20260301100000_add-budget-scenarios-table.sql (new)
|
|
43
|
+
Would run: supabase db push --linked
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## CLI Usage
|
|
47
|
+
```bash
|
|
48
|
+
# Push pending migrations to ReturnPro
|
|
49
|
+
optimal migrate-db --instance returnpro
|
|
50
|
+
|
|
51
|
+
# Push to OptimalOS
|
|
52
|
+
optimal migrate-db --instance optimalos
|
|
53
|
+
|
|
54
|
+
# Create a new migration and push
|
|
55
|
+
optimal migrate-db --instance returnpro --migration add-budget-scenarios --sql "CREATE TABLE budget_scenarios (...);"
|
|
56
|
+
|
|
57
|
+
# Dry run
|
|
58
|
+
optimal migrate-db --instance returnpro --dry-run
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Environment
|
|
62
|
+
Requires: `supabase` CLI installed (Homebrew, v2.72.7+). Authentication is handled internally by `supabase db push --linked`.
|
|
63
|
+
|
|
64
|
+
## Supabase Instances
|
|
65
|
+
|
|
66
|
+
| Instance | Supabase URL | Project Directory |
|
|
67
|
+
|----------|-------------|-------------------|
|
|
68
|
+
| returnpro | vvutttwunexshxkmygik.supabase.co | /home/optimal/dashboard-returnpro |
|
|
69
|
+
| optimalos | hbfalrpswysryltysonm.supabase.co | /home/optimal/optimalos |
|
|
70
|
+
|
|
71
|
+
## Gotchas
|
|
72
|
+
- **Never run SQL manually**: Always use migration files + `supabase db push --linked`. This is a hard rule.
|
|
73
|
+
- **No `supabase db execute`**: The `db execute` subcommand does not exist in Supabase CLI v2.72.7. Use migration files instead.
|
|
74
|
+
- **No psql directly**: The pooler has permission issues and direct IPv6 is unreachable. `db push --linked` handles auth internally.
|
|
75
|
+
- **stg_financials_raw.amount is TEXT**: If a migration touches this column, remember it stores amounts as TEXT strings, not NUMERIC.
|
|
76
|
+
- **Reserved fields in Strapi**: If migrating Strapi's underlying Postgres, avoid `status`, `published_at`, `locale`, `meta` as column names.
|
|
77
|
+
|
|
78
|
+
## Status
|
|
79
|
+
Implementation status: Not yet implemented. Spec only. Lib function `lib/infra/migrate.ts` to be built as a cross-repo migration runner.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: preview-newsletter
|
|
3
|
+
description: Deploy the newsletter preview site to Vercel for client review
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Deploys the newsletter-preview Next.js site to Vercel so clients and stakeholders can review newsletters and social posts in a branded web interface. This is a thin wrapper around the `deploy` skill, pre-configured for the `newsletter-preview` app. The preview site renders published Strapi content at `https://newsletter.op-hub.com`.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **prod** (optional): Deploy to production. Default: preview deployment.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
1. Call `lib/infra/deploy.ts::deploy('newsletter-preview', options?)` to deploy
|
|
14
|
+
2. Resolves to path `/home/optimal/projects/newsletter-preview`
|
|
15
|
+
3. Runs `vercel --cwd /home/optimal/projects/newsletter-preview` (or `vercel --prod` for production)
|
|
16
|
+
4. Waits for deployment to complete (up to 2 minutes)
|
|
17
|
+
5. Returns the deployment URL
|
|
18
|
+
6. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
19
|
+
|
|
20
|
+
## Output
|
|
21
|
+
```
|
|
22
|
+
Deploying newsletter-preview...
|
|
23
|
+
Preview URL: https://newsletter-preview-abc123.vercel.app
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or for production:
|
|
27
|
+
```
|
|
28
|
+
Deploying newsletter-preview to production...
|
|
29
|
+
Production URL: https://newsletter.op-hub.com
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## CLI Usage
|
|
33
|
+
```bash
|
|
34
|
+
# Preview deployment
|
|
35
|
+
optimal preview-newsletter
|
|
36
|
+
|
|
37
|
+
# Production deployment
|
|
38
|
+
optimal preview-newsletter --prod
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Environment
|
|
42
|
+
Requires: `vercel` CLI installed globally and authenticated.
|
|
43
|
+
|
|
44
|
+
## Gotchas
|
|
45
|
+
- **This is just a deploy**: No content generation happens here. Use `generate-newsletter` or `generate-newsletter-insurance` first to create content, then deploy the preview.
|
|
46
|
+
- **Strapi must be reachable**: The preview site fetches content from Strapi at build time and runtime. Strapi must be running at `https://strapi.op-hub.com`.
|
|
47
|
+
- **Brand routes**: CRE-11TRUST content at `/cre-11trust`, LIFEINSUR content at `/lifeinsur`.
|
|
48
|
+
|
|
49
|
+
## Status
|
|
50
|
+
Implementation status: Not yet implemented. Spec only. Uses existing `lib/infra/deploy.ts` with `newsletter-preview` app name.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-budget
|
|
3
|
+
description: Run FY26 budget projections with percentage or flat adjustments on FY25 checked-in units
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Generates FY26 budget projections by applying uniform adjustments (percentage or flat) to FY25 actual checked-in units. Supports both unit volume and average retail price projections, with revenue calculations (units x retail).
|
|
8
|
+
|
|
9
|
+
Data comes from either:
|
|
10
|
+
1. ReturnPro Supabase `fpa_wes_imports` table (default)
|
|
11
|
+
2. A JSON file of `CheckedInUnitsSummary[]` (via `--file` flag or stdin)
|
|
12
|
+
|
|
13
|
+
## Inputs
|
|
14
|
+
- **adjustment-type** (optional): `percent` or `flat`. Default: `percent`.
|
|
15
|
+
- **adjustment-value** (optional): Numeric value for the adjustment. Default: `0` (no change).
|
|
16
|
+
- **format** (optional): Output format — `table` (Bloomberg-dense markdown) or `csv`. Default: `table`.
|
|
17
|
+
- **fiscal-year** (optional): Base fiscal year for actuals. Default: `2025`.
|
|
18
|
+
- **user-id** (optional): Supabase user UUID to filter imports by.
|
|
19
|
+
- **file** (optional): Path to a JSON file containing `CheckedInUnitsSummary[]` array. If provided, skips Supabase fetch.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
1. Load FY25 data: fetch from `fpa_wes_imports` or parse from JSON file
|
|
23
|
+
2. Call `initializeProjections(summary)` to create baseline entries
|
|
24
|
+
3. Call `applyUniformAdjustment(projections, type, value)` if adjustment specified
|
|
25
|
+
4. Format output as table or CSV via `formatProjectionTable()` or `exportToCSV()`
|
|
26
|
+
|
|
27
|
+
## Output
|
|
28
|
+
Table format (default) — Bloomberg-dense:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
FY25 Actual: 7.35M units | FY26 Projected: 7.65M units | +4.0%
|
|
32
|
+
Revenue: $180.2M -> $187.4M | +4.0%
|
|
33
|
+
|
|
34
|
+
| Client | Program | FY25 Units | FY26 Units | Delta | Avg Retail | Proj Retail | Rev Delta |
|
|
35
|
+
|--------|---------|------------|------------|-------|------------|-------------|-----------|
|
|
36
|
+
| Walmart | BRTON-WM-LIQ | 120.5K | 125.3K | +4.8K (4.0%) | $45.20 | $45.20 | +$217.0K |
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
CSV format: full-precision export with unit, retail, and inventory value columns.
|
|
40
|
+
|
|
41
|
+
## CLI Usage
|
|
42
|
+
```bash
|
|
43
|
+
# Default: no adjustment, table format, FY25 from Supabase
|
|
44
|
+
npx tsx bin/optimal.ts project-budget
|
|
45
|
+
|
|
46
|
+
# 4% growth projection
|
|
47
|
+
npx tsx bin/optimal.ts project-budget --adjustment-type percent --adjustment-value 4
|
|
48
|
+
|
|
49
|
+
# Flat +500 units per program
|
|
50
|
+
npx tsx bin/optimal.ts project-budget --adjustment-type flat --adjustment-value 500
|
|
51
|
+
|
|
52
|
+
# CSV output
|
|
53
|
+
npx tsx bin/optimal.ts project-budget --adjustment-type percent --adjustment-value 4 --format csv
|
|
54
|
+
|
|
55
|
+
# From JSON file instead of Supabase
|
|
56
|
+
npx tsx bin/optimal.ts project-budget --file ./fy25-actuals.json --adjustment-type percent --adjustment-value 10
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Environment
|
|
60
|
+
Requires (when using Supabase): `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish-blog
|
|
3
|
+
description: Publish a blog post to Strapi CMS and deploy the portfolio site to Vercel
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
End-to-end blog publishing workflow: creates or updates a blog post in Strapi CMS, publishes it, and deploys the portfolio-2026 site to Vercel so the post goes live at carloslenis.com. Supports both manual blog posts and automated research reports (AI-generated content with specific formatting rules).
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **title** (required): Blog post title.
|
|
11
|
+
- **slug** (optional): URL slug. Default: auto-generated from title + year (e.g., `copper-investment-thesis-ai-data-centers-2026`).
|
|
12
|
+
- **content** (required): Markdown content for the blog post body.
|
|
13
|
+
- **site** (required): Site filter — `portfolio` (carloslenis.com) or `insurance` (future).
|
|
14
|
+
- **tags** (optional): Comma-separated tag names (e.g., `"Automated Report,AI,Finance"`).
|
|
15
|
+
- **documentId** (optional): If provided, updates an existing post instead of creating a new one.
|
|
16
|
+
- **deploy** (optional): Auto-deploy portfolio site after publishing. Default: true.
|
|
17
|
+
- **draft** (optional): Create as draft without publishing. Default: false (publish immediately).
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
1. Call `lib/cms/strapi-client.ts` functions to manage the blog post lifecycle
|
|
21
|
+
2. **Build payload** — construct Strapi `blog-post` data: title, slug, content, site, tags
|
|
22
|
+
3. **Create or update** — if `--documentId` given, `strapiPut('/api/blog-posts', documentId, data)`; otherwise `strapiPost('/api/blog-posts', data)`
|
|
23
|
+
4. **Publish** — unless `--draft` is set, call `publish('blog-posts', documentId)`
|
|
24
|
+
5. **Deploy** — unless `--deploy false`, call `lib/infra/deploy.ts::deploy('portfolio', { prod: true })` to push to Vercel production
|
|
25
|
+
6. **Return** — documentId, slug, and live URL
|
|
26
|
+
7. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
27
|
+
|
|
28
|
+
## Output
|
|
29
|
+
```
|
|
30
|
+
Blog post published: "Copper Investment Thesis for AI Data Centers"
|
|
31
|
+
Slug: copper-investment-thesis-ai-data-centers-2026
|
|
32
|
+
DocumentId: abc123-def456
|
|
33
|
+
Site: portfolio
|
|
34
|
+
Tags: Automated Report, AI, Finance
|
|
35
|
+
Deploy: production → https://carloslenis.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## CLI Usage
|
|
39
|
+
```bash
|
|
40
|
+
# Publish a new blog post and deploy
|
|
41
|
+
optimal publish-blog --title "My Post" --content ./post.md --site portfolio --tags "Finance"
|
|
42
|
+
|
|
43
|
+
# Create as draft (no deploy)
|
|
44
|
+
optimal publish-blog --title "Draft Post" --content ./draft.md --site portfolio --draft
|
|
45
|
+
|
|
46
|
+
# Update existing post
|
|
47
|
+
optimal publish-blog --documentId abc123 --content ./updated.md --deploy
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Automated Report Format
|
|
51
|
+
When publishing AI-generated research reports, the content MUST follow these rules:
|
|
52
|
+
- **Tag**: `"Automated Report"` (never pretend Carlos wrote it)
|
|
53
|
+
- **Structure**: Disclosure blockquote at top, `---` between every section, `## ` headings for section cards
|
|
54
|
+
- **References**: Numbered inline refs `[[1]](#ref-1)` with `## Sources & References` section at bottom
|
|
55
|
+
- **Tables**: Use heavily to break up text (data tables, comparisons, scorecards)
|
|
56
|
+
- **Links**: Every data claim must be hyperlinked to its source
|
|
57
|
+
- **Slug format**: `topic-keywords-YYYY`
|
|
58
|
+
|
|
59
|
+
The portfolio site's `BlogContent` component auto-detects multi-section posts (4+ sections with `---`) and renders each `## ` section as a themed card with colored borders.
|
|
60
|
+
|
|
61
|
+
## Environment
|
|
62
|
+
Requires: `STRAPI_URL`, `STRAPI_API_TOKEN`, `vercel` CLI (for deploy)
|
|
63
|
+
|
|
64
|
+
## Gotchas
|
|
65
|
+
- **documentId for updates**: Strapi v5 uses documentId (UUID), not numeric id.
|
|
66
|
+
- **Site field**: The `site` enum in Strapi blog-post schema determines which site renders the post. Add new sites by editing the schema.
|
|
67
|
+
- **Deploy after publish**: By default, the portfolio site is redeployed to production after publishing. Pass `--deploy false` to skip.
|
|
68
|
+
|
|
69
|
+
## Status
|
|
70
|
+
Implementation status: Not yet implemented. Spec only. Uses existing `lib/cms/strapi-client.ts` for Strapi operations and `lib/infra/deploy.ts` for Vercel deployment.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish-social-posts
|
|
3
|
+
description: Push social post drafts from Strapi to live platforms (Instagram, Facebook, LinkedIn) via distribution hub
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Publishes social post drafts from Strapi CMS to their target platforms (Instagram, Facebook, LinkedIn, Twitter/X). Works through the distribution hub: publishes the Strapi entry (triggers webhook), which n8n picks up to post to the actual social platforms via Meta Marketing API and other platform APIs. Handles both immediate publishing and scheduled posts.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **brand** (required): Brand key — `CRE-11TRUST` or `LIFEINSUR`
|
|
11
|
+
- **documentIds** (optional): Comma-separated Strapi documentIds to publish. If omitted, publishes all pending drafts for the brand.
|
|
12
|
+
- **schedule-only** (optional): Only publish posts with a `scheduled_date` in the future (let the 15-minute cron handle delivery). Default: false.
|
|
13
|
+
- **test** (optional): Post to test/sandbox accounts instead of live pages.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
1. Call `lib/cms/strapi-client.ts::publishSocialPosts(brand, options?)` to orchestrate
|
|
17
|
+
2. **Fetch pending posts** — if `--documentIds` provided, fetch those specific posts; otherwise `listByBrand('social-posts', brand, 'draft')` to get all pending drafts
|
|
18
|
+
3. **Validate posts** — ensure each post has required fields: headline, body, image_url, platform, scheduled_date
|
|
19
|
+
4. **Publish in Strapi** — `publish('social-posts', documentId)` for each post (sets publishedAt, triggers webhook)
|
|
20
|
+
5. **n8n distribution** — Strapi publish webhook fires n8n workflow which:
|
|
21
|
+
- Reads `brand-config` for platform IDs (IG page ID, FB page ID, etc.)
|
|
22
|
+
- Posts to target platform via respective API
|
|
23
|
+
- Updates `delivery_status` (pending → scheduled/delivered/failed)
|
|
24
|
+
- Writes `platform_post_id` back to Strapi on success
|
|
25
|
+
6. **Report results** — summarize published vs scheduled vs failed
|
|
26
|
+
7. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
27
|
+
|
|
28
|
+
## Output
|
|
29
|
+
```
|
|
30
|
+
Brand: LIFEINSUR
|
|
31
|
+
Posts processed: 9
|
|
32
|
+
|
|
33
|
+
| # | Headline | Platform | Status | Post ID |
|
|
34
|
+
|---|----------|----------|--------|---------|
|
|
35
|
+
| 1 | "Protect What Matters" | instagram | delivered | 17899... |
|
|
36
|
+
| 2 | "Your Family's Future" | facebook | delivered | 61294... |
|
|
37
|
+
| 3 | "Life Insurance Myths" | instagram | scheduled (3/4) | — |
|
|
38
|
+
| ... | ... | ... | ... | ... |
|
|
39
|
+
|
|
40
|
+
Delivered: 4 | Scheduled: 5 | Failed: 0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## CLI Usage
|
|
44
|
+
```bash
|
|
45
|
+
# Publish all pending LIFEINSUR posts
|
|
46
|
+
optimal publish-social-posts --brand LIFEINSUR
|
|
47
|
+
|
|
48
|
+
# Publish specific posts
|
|
49
|
+
optimal publish-social-posts --brand CRE-11TRUST --documentIds abc123,def456,ghi789
|
|
50
|
+
|
|
51
|
+
# Only set up scheduled posts (don't post immediately)
|
|
52
|
+
optimal publish-social-posts --brand LIFEINSUR --schedule-only
|
|
53
|
+
|
|
54
|
+
# Test mode
|
|
55
|
+
optimal publish-social-posts --brand LIFEINSUR --test
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Environment
|
|
59
|
+
Requires: `STRAPI_URL`, `STRAPI_API_TOKEN`
|
|
60
|
+
Platform credentials managed in n8n (Meta access token, Twitter keys, etc.), not in CLI.
|
|
61
|
+
|
|
62
|
+
## Gotchas
|
|
63
|
+
- **n8n must be running**: Distribution depends on n8n catching the Strapi webhook.
|
|
64
|
+
- **brand-config required**: The `brand-config` content type in Strapi must have platform IDs (IG page ID, FB page ID) for the target brand.
|
|
65
|
+
- **Scheduling**: Posts with future `scheduled_date` are queued — a 15-minute n8n cron picks them up. Posts with past/blank `scheduled_date` are distributed immediately on publish.
|
|
66
|
+
- **delivery_status flow**: pending → scheduled (if future date) → delivered/failed. Partial state means some platforms succeeded and others failed.
|
|
67
|
+
- **Meta Marketing API**: Requires `ads_management` + `ads_read` permissions on the Meta developer app access token.
|
|
68
|
+
|
|
69
|
+
## Status
|
|
70
|
+
Implementation status: Not yet implemented. Spec only. Uses existing `lib/cms/strapi-client.ts` for Strapi operations; distribution handled by n8n webhook.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rate-anomalies
|
|
3
|
+
description: Flag outlier $/unit rates across programs and months in ReturnPro financial data
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Detects anomalous per-unit rates ($/unit) across programs and months in ReturnPro's staged financial data. Compares each program-month's rate against its historical average and flags statistical outliers. This is a key data quality tool — rate anomalies often indicate data entry errors, misclassified programs, or upstream reporting issues.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **months** (optional): Comma-separated YYYY-MM strings to analyze. Default: last 6 months with data.
|
|
11
|
+
- **threshold** (optional): Z-score threshold for flagging anomalies. Default: `2.0` (2 standard deviations).
|
|
12
|
+
- **programs** (optional): Comma-separated program name substrings to filter. Default: all programs.
|
|
13
|
+
- **format** (optional): Output format — `table` (default) or `csv`.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
1. Call `lib/returnpro/anomalies.ts::detectRateAnomalies(options?)` to fetch and analyze data
|
|
17
|
+
2. Query `stg_financials_raw` for revenue and unit rows across the target months
|
|
18
|
+
3. Compute $/unit rate for each program-month combination
|
|
19
|
+
4. Calculate historical mean and standard deviation per program
|
|
20
|
+
5. Flag any program-month where the rate's z-score exceeds the threshold
|
|
21
|
+
6. Sort flagged anomalies by severity (highest z-score first)
|
|
22
|
+
7. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
23
|
+
|
|
24
|
+
## Output
|
|
25
|
+
Bloomberg-dense table with inline severity indicators:
|
|
26
|
+
|
|
27
|
+
| Program | Month | Rate | Avg Rate | Z-Score | Delta |
|
|
28
|
+
|---------|-------|------|----------|---------|-------|
|
|
29
|
+
| BRTON-WM | 2026-01 | $3.42/u | $2.10/u | 3.1 | ↑62.9% |
|
|
30
|
+
| FORTX-POOL | 2025-12 | $0.89/u | $1.55/u | -2.4 | ↓-42.6% |
|
|
31
|
+
|
|
32
|
+
Rows with z-score > 3.0 get `bg-red-500/5` row tint indicator. All numbers are `font-mono`, right-aligned.
|
|
33
|
+
|
|
34
|
+
## CLI Usage
|
|
35
|
+
```bash
|
|
36
|
+
# Default: last 6 months, z-score threshold 2.0
|
|
37
|
+
optimal rate-anomalies
|
|
38
|
+
|
|
39
|
+
# Specific months, stricter threshold
|
|
40
|
+
optimal rate-anomalies --months 2026-01,2025-12 --threshold 1.5
|
|
41
|
+
|
|
42
|
+
# Filter to specific programs
|
|
43
|
+
optimal rate-anomalies --programs BRTON,FORTX
|
|
44
|
+
|
|
45
|
+
# CSV export
|
|
46
|
+
optimal rate-anomalies --format csv > anomalies.csv
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Environment
|
|
50
|
+
Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
|
|
51
|
+
|
|
52
|
+
## Tables Touched
|
|
53
|
+
- `stg_financials_raw` — read revenue and unit data (CAST amount from TEXT)
|
|
54
|
+
- `dim_master_program` — resolve program names
|
|
55
|
+
|
|
56
|
+
## Gotchas
|
|
57
|
+
- **amount is TEXT**: Always CAST `stg_financials_raw.amount` before numeric operations.
|
|
58
|
+
- **Reference implementation**: The Bloomberg-dense rate anomaly explorer UI is at `components/analysis/rate-anomaly-explorer.tsx` in dashboard-returnpro (Feb 2026 redesign).
|
|
59
|
+
- **Sign conventions**: Revenue is positive, expenses are negative. Unit counts are always positive.
|
|
60
|
+
|
|
61
|
+
## Status
|
|
62
|
+
Implementation status: Not yet implemented. Spec only. Lib function `lib/returnpro/anomalies.ts` to be extracted from dashboard-returnpro's `/api/analytics/rate-anomalies` route.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scrape-ads
|
|
3
|
+
description: Scrape Meta Ad Library for competitor ad intelligence using headless Playwright browser
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Scrape the Facebook/Meta Ad Library to extract competitor ad data for analysis. Uses headless Chromium with anti-detection measures (disabled automation features, realistic user agent, viewport 1920x1080). Extracts ad metadata including Library ID, status, start date, ad copy, platforms, impressions, spend, and media type.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **companies** (required): Comma-separated list of company names, OR a path to a text file with one company per line
|
|
11
|
+
- **output** (optional): File path to save CSV results (default: stdout)
|
|
12
|
+
- **batch-size** (optional): Number of companies per batch (default: 6). Companies run sequentially within a batch with 4s delay between each.
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
1. **Parse companies** — accept CSV string or read from file (one company per line)
|
|
16
|
+
2. **Launch browser** — headless Chromium with anti-detection args
|
|
17
|
+
3. **Batch processing** — split companies into batches of `batch-size`, each batch gets a fresh browser context
|
|
18
|
+
4. **Per company**: navigate to Ad Library search URL, wait for load, scroll to load all ads (up to 15 scrolls with 2s delay each)
|
|
19
|
+
5. **Extract ads** — two-stage: first try DOM querySelectorAll for divs containing exactly one `Library ID: \d+`, then fallback to splitting full page text by Library ID boundaries
|
|
20
|
+
6. **Parse metadata** — regex extraction of Library ID, start date, status, page name, ad text, impressions, spend, media type, platforms
|
|
21
|
+
7. **Extract landing URLs** — scan DOM for `l.facebook.com` redirect links and associate with ad IDs
|
|
22
|
+
8. **Output CSV** — columns: company_searched, ad_id, page_name, ad_text, status, start_date, impressions, spend, media_type, platforms, landing_page_url, full_text_snippet
|
|
23
|
+
|
|
24
|
+
## Output
|
|
25
|
+
- CSV data (to stdout or file) with one row per ad
|
|
26
|
+
- Console progress logs during scraping
|
|
27
|
+
|
|
28
|
+
## CLI Usage
|
|
29
|
+
```bash
|
|
30
|
+
# Scrape specific companies (stdout)
|
|
31
|
+
optimal scrape-ads --companies "State Farm,Allstate,GEICO"
|
|
32
|
+
|
|
33
|
+
# Scrape from file, save to CSV
|
|
34
|
+
optimal scrape-ads --companies ~/projects/meta-ad-scraper/data/companies.txt --output ./ads.csv
|
|
35
|
+
|
|
36
|
+
# Custom batch size
|
|
37
|
+
optimal scrape-ads --companies "Company A,Company B" --batch-size 3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Environment
|
|
41
|
+
Requires: `playwright` npm package with Chromium browser installed (`npx playwright install chromium`)
|
|
42
|
+
|
|
43
|
+
## Gotchas
|
|
44
|
+
- **Browser install**: Playwright requires a one-time `npx playwright install chromium` to download the browser binary. The scraper will fail if this hasn't been run.
|
|
45
|
+
- **Rate limiting**: Facebook may rate-limit or block automated access. The 4s delay between companies and fresh contexts per batch help mitigate this.
|
|
46
|
+
- **Anti-detection**: Uses `--disable-blink-features=AutomationControlled` and a realistic user agent string.
|
|
47
|
+
- **DOM extraction**: The primary extraction strategy looks for div elements containing exactly one Library ID with text length between 50-5000 chars, deduplicated by Library ID. Falls back to text splitting if DOM strategy finds nothing.
|
|
48
|
+
- **Batch strategy**: Per memory, run in 3 parallel batches of 6 companies each for optimal throughput.
|
|
49
|
+
- **No actual execution in CI**: This scraper hits live Facebook servers. Do not run in automated pipelines without explicit intent.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stamp-transactions
|
|
3
|
+
description: Auto-categorize unclassified transactions using a 4-stage rule-based matching engine
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Queries all unclassified transactions (where `provider IS NULL` or `category_id IS NULL`) for a given user and runs them through a 4-stage matching algorithm to assign provider names and categories. Optionally runs in dry-run mode to preview results without writing.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **user-id** (required): Supabase user UUID whose transactions to stamp
|
|
11
|
+
- **dry-run** (optional): Preview matches without updating the database
|
|
12
|
+
|
|
13
|
+
## Matching Algorithm (4 stages)
|
|
14
|
+
| Stage | Name | Confidence | Method |
|
|
15
|
+
|-------|------|------------|--------|
|
|
16
|
+
| 1 | PATTERN | 100% | Regex patterns for transfers, Zelle, P2P, CC payments, payroll, ATM, fees |
|
|
17
|
+
| 2 | LEARNED | 80-99% | Description hash lookup in `learned_patterns` (weight determines confidence) |
|
|
18
|
+
| 3 | EXACT | 100% | Provider name (or variant) found as substring in description |
|
|
19
|
+
| 4 | FUZZY | 60-95% | Token overlap between description and provider names (threshold 0.6) |
|
|
20
|
+
| Fallback | CATEGORY_INFER | 50% | Map institution-specific category to standard category |
|
|
21
|
+
|
|
22
|
+
Transactions matching at >= 90% confidence are auto-confirmed. Below that, they remain `pending` for user review.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
1. Load matching rules from DB: `providers`, `learned_patterns`, `user_provider_overrides`
|
|
26
|
+
2. Fetch unclassified transactions for the user
|
|
27
|
+
3. Fetch `stamp_categories` and user `categories` for name-to-ID mapping
|
|
28
|
+
4. Run each transaction through the 4-stage pipeline
|
|
29
|
+
5. Update matched rows with `provider`, `provider_method`, `provider_confidence`, `category_id`
|
|
30
|
+
|
|
31
|
+
## Output
|
|
32
|
+
```
|
|
33
|
+
Matcher loaded: 342 providers, 89 learned patterns
|
|
34
|
+
Found 156 unclassified transactions
|
|
35
|
+
|
|
36
|
+
Stamped: 127 | Unmatched: 29 | Total: 156
|
|
37
|
+
By match type: PATTERN=18, LEARNED=34, EXACT=52, FUZZY=19, CATEGORY_INFER=4
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
In dry-run mode, no database writes occur — the output shows what would happen.
|
|
41
|
+
|
|
42
|
+
## CLI Usage
|
|
43
|
+
```bash
|
|
44
|
+
# Full stamp run
|
|
45
|
+
tsx bin/optimal.ts stamp-transactions --user-id <uuid>
|
|
46
|
+
|
|
47
|
+
# Preview only
|
|
48
|
+
tsx bin/optimal.ts stamp-transactions --user-id <uuid> --dry-run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Environment
|
|
52
|
+
Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
|
|
53
|
+
|
|
54
|
+
## Tables Read
|
|
55
|
+
- `providers` — global provider-to-category mappings + aliases
|
|
56
|
+
- `learned_patterns` — user-confirmed description patterns
|
|
57
|
+
- `user_provider_overrides` — per-user category overrides
|
|
58
|
+
- `stamp_categories` — standard category definitions
|
|
59
|
+
- `categories` — user-specific categories
|
|
60
|
+
|
|
61
|
+
## Tables Written
|
|
62
|
+
- `transactions` — update `provider`, `provider_method`, `provider_confidence`, `provider_inferred_at`, `category_id`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upload-income-statements
|
|
3
|
+
description: Load confirmed income statement CSVs into ReturnPro for accuracy auditing
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Uploads confirmed income statement CSVs (exported from NetSuite) into `confirmed_income_statements`. These serve as the source of truth for financial accuracy auditing — the audit-financials skill compares staged data against these confirmed records. Maintaining accurate confirmed data is essential because ReturnPro targets 100% accuracy between staged and confirmed financials.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
- **file** (required): Absolute path to the income statement CSV file on disk
|
|
11
|
+
- **month** (required): Target month as YYYY-MM (e.g., `2026-01`)
|
|
12
|
+
- **replace** (optional): If set, deletes existing confirmed rows for the target month before inserting. Default: false (append/upsert).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
1. Call `lib/returnpro/upload-income.ts::uploadIncomeStatements(file, month, options?)` to orchestrate the upload
|
|
16
|
+
2. Read the CSV file and parse rows (account_code, account_name, total_amount, period)
|
|
17
|
+
3. Validate account codes against `dim_account`
|
|
18
|
+
4. If `--replace` flag is set, delete existing `confirmed_income_statements` rows for the target month
|
|
19
|
+
5. Batch-insert rows into `confirmed_income_statements`
|
|
20
|
+
6. Run a quick accuracy check against `stg_financials_raw` for the uploaded month (same logic as audit-financials)
|
|
21
|
+
7. Report accuracy inline so Carlos immediately knows the data state
|
|
22
|
+
8. Log execution via `lib/kanban.ts::logSkillExecution()`
|
|
23
|
+
|
|
24
|
+
## Output
|
|
25
|
+
```
|
|
26
|
+
Parsed income statement CSV: 189 accounts for 2026-01
|
|
27
|
+
Inserted: 189 | Replaced: 0
|
|
28
|
+
Quick accuracy check (2026-01): 91.2% (83/91 staged accounts match)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## CLI Usage
|
|
32
|
+
```bash
|
|
33
|
+
# Upload income statement
|
|
34
|
+
optimal upload-income-statements --file ~/Downloads/returnpro-data/IS-Jan-2026.csv --month 2026-01
|
|
35
|
+
|
|
36
|
+
# Replace existing month data
|
|
37
|
+
optimal upload-income-statements --file ~/Downloads/returnpro-data/IS-Jan-2026.csv --month 2026-01 --replace
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Environment
|
|
41
|
+
Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
|
|
42
|
+
|
|
43
|
+
## Tables Touched
|
|
44
|
+
- `confirmed_income_statements` — insert/replace confirmed GL account rows
|
|
45
|
+
- `stg_financials_raw` — read-only for post-upload accuracy check
|
|
46
|
+
- `dim_account` — validate account codes
|
|
47
|
+
|
|
48
|
+
## Gotchas
|
|
49
|
+
- **Coverage gap**: Confirmed data has ~185-193 accounts/month vs staging's ~88-93. The delta is GL accounts not in Solution7 (expected, but tracked).
|
|
50
|
+
- **Always run audit after upload**: The skill automatically runs a quick accuracy check, but a full audit-financials run is recommended for detailed investigation.
|
|
51
|
+
- **Upload via Admin Console**: Can also be done via the ReturnPro Admin Console UI (Income Statement tab).
|
|
52
|
+
|
|
53
|
+
## Status
|
|
54
|
+
Implementation status: Not yet implemented. Spec only. Lib function `lib/returnpro/upload-income.ts` to be extracted from dashboard-returnpro's `/api/admin/confirmed-income-statements` route.
|