vortexa-claude-skills 1.0.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/CHANGELOG.md +28 -0
- package/VERSION +1 -0
- package/bin/.gitkeep +0 -0
- package/bin/setup.js +302 -0
- package/commands/vortexa/_check-setup.md +9 -0
- package/commands/vortexa/_skill-template.md +100 -0
- package/commands/vortexa/breakdown.md +294 -0
- package/commands/vortexa/cargo-flows.md +247 -0
- package/commands/vortexa/compare.md +315 -0
- package/commands/vortexa/custom.md +214 -0
- package/commands/vortexa/explain.md +124 -0
- package/commands/vortexa/init.md +133 -0
- package/commands/vortexa/oow.md +189 -0
- package/commands/vortexa/seasonal.md +185 -0
- package/commands/vortexa/voyages.md +285 -0
- package/context/.gitkeep +0 -0
- package/context/cargo-movements.md +738 -0
- package/context/date-units.md +188 -0
- package/context/endpoint-template.md +176 -0
- package/context/entity-resolution.md +217 -0
- package/context/guardrails.md +161 -0
- package/context/reference-endpoints.md +651 -0
- package/context/voyages.md +636 -0
- package/lib/__init__.py +4 -0
- package/lib/aliases.json +52 -0
- package/lib/api.py +20 -0
- package/lib/entities.py +254 -0
- package/lib/inventory.py +140 -0
- package/lib/movements.py +242 -0
- package/lib/requirements.txt +6 -0
- package/lib/seasonal.py +200 -0
- package/lib/timeseries.py +271 -0
- package/lib/utils.py +120 -0
- package/lib/vessels.py +192 -0
- package/lib/visualization.py +164 -0
- package/lib/voyages.py +236 -0
- package/package.json +28 -0
- package/templates/.env.template +3 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vortexa:init
|
|
3
|
+
description: Set up Vortexa analytics environment in the current project
|
|
4
|
+
argument-hint: ""
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Edit
|
|
8
|
+
- Write
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
---
|
|
13
|
+
<objective>
|
|
14
|
+
Initialize the Vortexa analytics environment in the current project directory. Verifies the global install at ~/.claude/vortexa/, creates a local .env for API key management, sets up CLAUDE.md with context @-imports, verifies .gitignore protection, and validates the Python environment.
|
|
15
|
+
</objective>
|
|
16
|
+
|
|
17
|
+
<process>
|
|
18
|
+
|
|
19
|
+
## Step 1: Verify Global Install
|
|
20
|
+
|
|
21
|
+
Check that `~/.claude/vortexa/` exists and contains expected files:
|
|
22
|
+
- `~/.claude/vortexa/VERSION` must exist
|
|
23
|
+
- `~/.claude/vortexa/context/guardrails.md` must exist
|
|
24
|
+
- `~/.claude/vortexa/lib/__init__.py` must exist
|
|
25
|
+
- `~/.claude/commands/vortexa/cargo-flows.md` must exist
|
|
26
|
+
|
|
27
|
+
If any are missing, tell the user:
|
|
28
|
+
"Vortexa skills package is not installed or is incomplete. Run: npx @vortexa/claude-skills setup"
|
|
29
|
+
Stop execution.
|
|
30
|
+
|
|
31
|
+
If all present, read VERSION and display: "Vortexa skills v{version} detected."
|
|
32
|
+
|
|
33
|
+
## Step 2: Create/Verify .env File
|
|
34
|
+
|
|
35
|
+
Read the template from the global install: `~/.claude/vortexa/templates/.env.template`
|
|
36
|
+
|
|
37
|
+
**If .env does NOT exist in the current directory:**
|
|
38
|
+
- Create .env from the template content
|
|
39
|
+
- Tell user: "Created .env file. Please add your Vortexa API key: edit .env and replace 'your_key_here' with your actual key."
|
|
40
|
+
|
|
41
|
+
**If .env EXISTS:**
|
|
42
|
+
- Read it and check if `VORTEXA_API_KEY` is present
|
|
43
|
+
- If present and not `your_key_here`: "Vortexa API key detected in .env -- you're all set."
|
|
44
|
+
- If present but still `your_key_here`: "Found .env but API key is still the placeholder. Please edit .env and add your real API key."
|
|
45
|
+
- If VORTEXA_API_KEY line is missing: Append `VORTEXA_API_KEY=your_key_here` to the .env and tell user to fill it in.
|
|
46
|
+
|
|
47
|
+
## Step 3: Verify .gitignore Protection
|
|
48
|
+
|
|
49
|
+
Check if `.gitignore` exists in the project root.
|
|
50
|
+
|
|
51
|
+
**If .gitignore does NOT exist:**
|
|
52
|
+
- Create it with `.env` and `.env.*` entries
|
|
53
|
+
- Tell user: "Created .gitignore to protect your API key from being committed."
|
|
54
|
+
|
|
55
|
+
**If .gitignore EXISTS:**
|
|
56
|
+
- Check if it contains `.env`
|
|
57
|
+
- If YES: Good, no action needed
|
|
58
|
+
- If NO: Append `.env` and `.env.*` to .gitignore
|
|
59
|
+
- Tell user: "Updated .gitignore to protect your .env file."
|
|
60
|
+
|
|
61
|
+
## Step 4: Create/Update Project CLAUDE.md
|
|
62
|
+
|
|
63
|
+
**If no CLAUDE.md exists in the current directory:**
|
|
64
|
+
Create a minimal one:
|
|
65
|
+
```markdown
|
|
66
|
+
# Project
|
|
67
|
+
|
|
68
|
+
## Vortexa Analytics Context
|
|
69
|
+
@~/.claude/vortexa/context/guardrails.md
|
|
70
|
+
@~/.claude/vortexa/context/entity-resolution.md
|
|
71
|
+
@~/.claude/vortexa/context/date-units.md
|
|
72
|
+
```
|
|
73
|
+
Tell user: "Created CLAUDE.md with Vortexa context imports."
|
|
74
|
+
|
|
75
|
+
**If CLAUDE.md exists but has no Vortexa imports:**
|
|
76
|
+
Check if the file contains `vortexa/context/guardrails.md`. If not, append:
|
|
77
|
+
```markdown
|
|
78
|
+
|
|
79
|
+
## Vortexa Analytics Context
|
|
80
|
+
@~/.claude/vortexa/context/guardrails.md
|
|
81
|
+
@~/.claude/vortexa/context/entity-resolution.md
|
|
82
|
+
@~/.claude/vortexa/context/date-units.md
|
|
83
|
+
```
|
|
84
|
+
Tell user: "Added Vortexa context imports to existing CLAUDE.md."
|
|
85
|
+
|
|
86
|
+
**If CLAUDE.md already has Vortexa imports:**
|
|
87
|
+
Tell user: "CLAUDE.md already has Vortexa context configured."
|
|
88
|
+
|
|
89
|
+
## Step 5: Check Python Environment
|
|
90
|
+
|
|
91
|
+
Run each check and collect results:
|
|
92
|
+
|
|
93
|
+
1. `python3 --version` (fall back to `python --version`): Require 3.10+
|
|
94
|
+
2. `python3 -c "import vortexasdk; print(vortexasdk.__version__)"`: Check vortexasdk installed
|
|
95
|
+
3. `python3 -c "import pandas; print(pandas.__version__)"`: Check pandas installed
|
|
96
|
+
4. `python3 -c "import plotly; print(plotly.__version__)"`: Check plotly installed
|
|
97
|
+
5. `python3 -c "from dotenv import load_dotenv; print('ok')"`: Check python-dotenv installed
|
|
98
|
+
6. `python3 -c "import numpy; print(numpy.__version__)"`: Check numpy installed
|
|
99
|
+
7. `python3 -c "import dateutil; print(dateutil.__version__)"`: Check python-dateutil installed
|
|
100
|
+
|
|
101
|
+
For any missing package, collect it. At the end, if anything is missing, print:
|
|
102
|
+
```
|
|
103
|
+
Missing Python dependencies:
|
|
104
|
+
pip install vortexasdk pandas plotly python-dotenv numpy python-dateutil
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If all present, print versions found. Do NOT block setup if anything is missing -- warn only.
|
|
108
|
+
|
|
109
|
+
## Step 6: Verify Context Accessibility
|
|
110
|
+
|
|
111
|
+
Read `~/.claude/vortexa/context/guardrails.md` (first 5 lines) to confirm file access works from the current directory.
|
|
112
|
+
|
|
113
|
+
If read fails: "Warning: Cannot read context files from ~/.claude/vortexa/. Check file permissions."
|
|
114
|
+
If read succeeds: "Context files accessible."
|
|
115
|
+
|
|
116
|
+
## Step 7: Print Summary
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Vortexa Analytics Environment Ready!
|
|
120
|
+
|
|
121
|
+
Package: v{version} (installed at ~/.claude/vortexa/)
|
|
122
|
+
API Key: {status}
|
|
123
|
+
Python: {version}
|
|
124
|
+
Dependencies: {all ok / missing list}
|
|
125
|
+
Context: {accessible / warning}
|
|
126
|
+
CLAUDE.md: {created / updated / already configured}
|
|
127
|
+
|
|
128
|
+
You're ready to go! Try:
|
|
129
|
+
/vortexa:cargo-flows crude exports from Saudi Arabia last month
|
|
130
|
+
/vortexa:explain what is the difference between loading_end and loading_state?
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
</process>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vortexa:oow
|
|
3
|
+
description: "Run oil-on-water breakdown by region using the CM-Authoritative methodology"
|
|
4
|
+
argument-hint: "crude on water by shipping region, last 3 months"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Edit
|
|
8
|
+
- Write
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<objective>
|
|
15
|
+
Build an oil-on-water breakdown by region using the CM-Authoritative methodology: CargoMovements for authoritative OOW periods and quantities, VoyagesSearchEnriched for real-time vessel location enrichment. Uses `cargo_on_water_ts()` from lib/movements.py for data and `oow_area_chart()` from lib/visualization.py for stacked area visualization.
|
|
16
|
+
</objective>
|
|
17
|
+
|
|
18
|
+
<execution_context>
|
|
19
|
+
## Pre-loaded Context (via CLAUDE.md @imports)
|
|
20
|
+
The following are automatically available -- do NOT re-read them:
|
|
21
|
+
- context/guardrails.md -- NEVER/ALWAYS rules for all Vortexa API calls
|
|
22
|
+
- context/entity-resolution.md -- How to resolve entity names to hex IDs
|
|
23
|
+
- context/date-units.md -- Date parsing rules and unit defaults
|
|
24
|
+
|
|
25
|
+
## Required Context (Read on demand)
|
|
26
|
+
Read before processing the user's query:
|
|
27
|
+
- context/cargo-movements.md -- CargoMovements parameters, activity filters, column naming
|
|
28
|
+
- context/voyages.md -- VoyagesSearchEnriched parameters, event structure, location details
|
|
29
|
+
</execution_context>
|
|
30
|
+
|
|
31
|
+
## Setup Check
|
|
32
|
+
@commands/vortexa/_check-setup.md
|
|
33
|
+
|
|
34
|
+
<process>
|
|
35
|
+
|
|
36
|
+
## CM-Authoritative OOW -- MANDATORY
|
|
37
|
+
|
|
38
|
+
The ONLY correct method for location-based OOW analysis is `cargo_on_water_ts()` from lib/movements.py.
|
|
39
|
+
|
|
40
|
+
DO NOT use CTS `cargo_on_water_state` for this skill. CTS shows where cargo was LOADED FROM and GOING TO (origin/destination), NOT where it currently IS on the water.
|
|
41
|
+
|
|
42
|
+
The CM-Authoritative pattern:
|
|
43
|
+
1. CargoMovements with `cargo_on_water_state` for authoritative OOW periods and quantities
|
|
44
|
+
2. VoyagesSearchEnriched for real-time vessel location (shipping_region from events)
|
|
45
|
+
3. Join on cargo_movement_id (16-char truncated) to enrich CM cargoes with voyage location
|
|
46
|
+
|
|
47
|
+
If the generated code imports CargoTimeSeries for the OOW skill, it is WRONG.
|
|
48
|
+
|
|
49
|
+
## Step 1: Read Endpoint Context
|
|
50
|
+
|
|
51
|
+
Read `context/cargo-movements.md` and `context/voyages.md` (both are needed -- CM for quantities, VSE for location).
|
|
52
|
+
|
|
53
|
+
## Step 2: Parse the Query
|
|
54
|
+
|
|
55
|
+
Analyze $ARGUMENTS and extract:
|
|
56
|
+
|
|
57
|
+
- **Product**: typically crude, but any product group works
|
|
58
|
+
- **Location layer**: `shipping_region_v2` (default), `region`, or `country`. Controls how granular the location breakdown is.
|
|
59
|
+
- **Time range**: Apply date-units.md convention. Note: longer ranges = longer API queries (CM + VSE both queried).
|
|
60
|
+
- **Unit**: commodity defaults (oil=b for OOW since it's absolute volume, not rate)
|
|
61
|
+
- **Top-N**: how many regions to show in chart (default 8)
|
|
62
|
+
- **Frequency**: for chart display resampling -- daily (default), weekly, monthly
|
|
63
|
+
- **Exclude intra-country**: optional, default false for OOW
|
|
64
|
+
|
|
65
|
+
## Step 3: Check for Missing Parameters
|
|
66
|
+
|
|
67
|
+
Required (ask if missing):
|
|
68
|
+
- **Product** -- MUST be specified
|
|
69
|
+
- **Time range** -- MUST be specified
|
|
70
|
+
|
|
71
|
+
Optional with smart defaults (mention in confirmation):
|
|
72
|
+
- **Location layer**: default `shipping_region_v2`
|
|
73
|
+
- **Unit**: default `b` (absolute barrels for OOW volumes)
|
|
74
|
+
- **Top-N**: default 8
|
|
75
|
+
- **Frequency**: default `daily`
|
|
76
|
+
- **Exclude intra-country**: default false
|
|
77
|
+
|
|
78
|
+
Ask one parameter at a time. Do NOT bulk-ask.
|
|
79
|
+
|
|
80
|
+
## Step 4: Resolve Entity IDs
|
|
81
|
+
|
|
82
|
+
Resolve product ID using `lib/entities.py`:
|
|
83
|
+
1. Check `lib/aliases.json` for common shorthands
|
|
84
|
+
2. Call `resolve_product(term, layer)` with the correct layer
|
|
85
|
+
3. If multiple matches: present top 3 candidates. Ask user to pick. NEVER auto-correct.
|
|
86
|
+
4. If zero matches: tell user the term wasn't found.
|
|
87
|
+
|
|
88
|
+
No geography resolution needed -- OOW covers global flows; the location layer is applied during the CM+VSE join.
|
|
89
|
+
|
|
90
|
+
Use `EntityCache()` from `lib/entities.py` for the session.
|
|
91
|
+
|
|
92
|
+
## Step 5: Confirm Before Executing (MANDATORY)
|
|
93
|
+
|
|
94
|
+
Present the complete parameter set:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Query: [restate the user's question]
|
|
98
|
+
Analysis: Oil-on-water by region (CM-Authoritative)
|
|
99
|
+
Data: CargoMovements (OOW periods) + VoyagesSearchEnriched (location)
|
|
100
|
+
Product: [name (layer)]
|
|
101
|
+
Time: [start datetime] -> [end datetime]
|
|
102
|
+
Location level: [shipping_region_v2 / region / country]
|
|
103
|
+
Unit: [unit] (absolute volume)
|
|
104
|
+
Chart: Stacked area, Top [N] regions + Other
|
|
105
|
+
Display frequency: [daily/weekly/monthly]
|
|
106
|
+
|
|
107
|
+
Note: This query calls both CM and VSE endpoints -- may take 1-3 minutes for large date ranges.
|
|
108
|
+
|
|
109
|
+
Confirm or adjust?
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
NEVER execute without confirmation. Wait for user response.
|
|
113
|
+
|
|
114
|
+
## Step 6: Generate & Execute Code Artifact
|
|
115
|
+
|
|
116
|
+
Generate a self-contained .py file using the data -> viz -> save pipeline:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
"""Vortexa OOW: {description}
|
|
120
|
+
Generated by /vortexa:oow
|
|
121
|
+
Date: {date}
|
|
122
|
+
"""
|
|
123
|
+
from datetime import datetime
|
|
124
|
+
import os
|
|
125
|
+
import sys; sys.path.insert(0, "lib")
|
|
126
|
+
from entities import resolve_product, EntityCache
|
|
127
|
+
from movements import cargo_on_water_ts
|
|
128
|
+
from visualization import oow_area_chart
|
|
129
|
+
|
|
130
|
+
# Entity Resolution
|
|
131
|
+
cache = EntityCache()
|
|
132
|
+
{product_resolution_code}
|
|
133
|
+
|
|
134
|
+
# OOW by Region (CM-Authoritative)
|
|
135
|
+
ts_wide = cargo_on_water_ts(
|
|
136
|
+
time_min=datetime({time_min}),
|
|
137
|
+
time_max=datetime({time_max}),
|
|
138
|
+
product={product_id},
|
|
139
|
+
unit="{unit}",
|
|
140
|
+
location_layer="{location_layer}",
|
|
141
|
+
exclude_intra_country={exclude_intra},
|
|
142
|
+
verbose=True,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
print(ts_wide)
|
|
146
|
+
|
|
147
|
+
# Chart
|
|
148
|
+
fig = oow_area_chart(ts_wide, freq="{freq}", top_n={top_n}, title="{title}")
|
|
149
|
+
|
|
150
|
+
os.makedirs("output", exist_ok=True)
|
|
151
|
+
filepath = f"output/{slug}_oow_{datetime.now().strftime('%Y-%m-%d')}.html"
|
|
152
|
+
fig.write_html(filepath, auto_open=True)
|
|
153
|
+
print(f"Chart saved: {filepath}")
|
|
154
|
+
|
|
155
|
+
# Export (uncomment to save)
|
|
156
|
+
# ts_wide.to_csv(f"output/{slug}_oow_{datetime.now().strftime('%Y-%m-%d')}.csv")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
After generating, ask: "Save as new file, or add to an existing notebook/script?" Default: new file.
|
|
160
|
+
Run the code to get results.
|
|
161
|
+
|
|
162
|
+
## Step 7: Present Results
|
|
163
|
+
|
|
164
|
+
- Show the wide DataFrame (Top-N regions + Other + Total by date)
|
|
165
|
+
- Row cap by display frequency: daily=30, weekly=52, monthly=24
|
|
166
|
+
- If capped: "Showing first N of M rows"
|
|
167
|
+
- One-line methodology footnote using CONFIRMED parameters:
|
|
168
|
+
```
|
|
169
|
+
Methodology: CM-Authoritative OOW | CargoMovements (cargo_on_water_state) + VoyagesSearchEnriched (location) | {product} | {unit} | {location_layer} | Top {N}
|
|
170
|
+
```
|
|
171
|
+
Use human-readable names for entity filters (not hex IDs).
|
|
172
|
+
- File path to chart HTML
|
|
173
|
+
- Offer: "Export to CSV?" and "Want a summary?"
|
|
174
|
+
|
|
175
|
+
## Error Handling
|
|
176
|
+
|
|
177
|
+
Report immediately in plain English, no auto-retry:
|
|
178
|
+
|
|
179
|
+
- **401 Unauthorized**: "API key is invalid or expired. Run /vortexa:init to check your setup."
|
|
180
|
+
- **500 Server Error**: "Vortexa API returned a server error. Try again in a few minutes."
|
|
181
|
+
- **Timeout**: "Query timed out. Try narrowing the date range or reducing filters."
|
|
182
|
+
- **Empty results / No cargo movements found**: "No cargo movements found for this query. Try broadening the product filter or date range."
|
|
183
|
+
- **Entity not found**: "The term '{term}' returned no matches. Check spelling or try a broader term."
|
|
184
|
+
- **Multiple entity matches**: "{n} matches found for '{term}': [list]. Which did you mean?"
|
|
185
|
+
- **Long query warning**: Already included in confirmation step (1-3 min for large date ranges).
|
|
186
|
+
|
|
187
|
+
Never auto-retry. Never self-correct. Report the error and let the user decide.
|
|
188
|
+
|
|
189
|
+
</process>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vortexa:seasonal
|
|
3
|
+
description: "Run multi-year seasonal pattern analysis for cargo flows with min/max/avg bands and forward-looking current year"
|
|
4
|
+
argument-hint: "crude exports from Middle East, 2020-2026, monthly"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Edit
|
|
8
|
+
- Write
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<objective>
|
|
15
|
+
Run multi-year seasonal pattern analysis using CargoTimeSeries, producing a seasonal overlay chart with historical min/max range band, average line, last year, and forward-looking current year segment. Uses `complete_seasonal_flows()` and `seasonal_charts()` from lib/seasonal.py for data, and `seasonal_chart()` from lib/visualization.py for Plotly output.
|
|
16
|
+
</objective>
|
|
17
|
+
|
|
18
|
+
<execution_context>
|
|
19
|
+
## Pre-loaded Context (via CLAUDE.md @imports)
|
|
20
|
+
The following are automatically available -- do NOT re-read them:
|
|
21
|
+
- context/guardrails.md -- NEVER/ALWAYS rules for all Vortexa API calls
|
|
22
|
+
- context/entity-resolution.md -- How to resolve entity names to hex IDs
|
|
23
|
+
- context/date-units.md -- Date parsing rules and unit defaults
|
|
24
|
+
|
|
25
|
+
## Required Context (Read on demand)
|
|
26
|
+
Read before processing the user's query:
|
|
27
|
+
- context/cargo-movements.md -- CTS parameters, activity filter rules
|
|
28
|
+
</execution_context>
|
|
29
|
+
|
|
30
|
+
## Setup Check
|
|
31
|
+
@commands/vortexa/_check-setup.md
|
|
32
|
+
|
|
33
|
+
<process>
|
|
34
|
+
|
|
35
|
+
## CRITICAL PITFALLS
|
|
36
|
+
|
|
37
|
+
- `complete_seasonal_flows()` handles the 4-year API block limit automatically via `get_search_blocks()`. Do NOT manually split date ranges.
|
|
38
|
+
- `seasonal_charts()` filters out Feb 29 automatically. The viz function handles "Current year" column filtering (non-empty values only).
|
|
39
|
+
- Minimum 3 years for meaningful min/max/avg bands. Warn if user requests < 3 years: "Seasonal analysis with fewer than 3 years produces unreliable min/max bands. Consider extending the range."
|
|
40
|
+
|
|
41
|
+
## Step 1: Read Endpoint Context
|
|
42
|
+
|
|
43
|
+
Read `context/cargo-movements.md` for CTS parameters and activity filter rules.
|
|
44
|
+
|
|
45
|
+
## Step 2: Parse the Query
|
|
46
|
+
|
|
47
|
+
Analyze $ARGUMENTS and extract:
|
|
48
|
+
|
|
49
|
+
- **Product**: commodity (crude, LNG, clean products, etc.)
|
|
50
|
+
- **Geography**: origin and/or destination
|
|
51
|
+
- **Activity**: movement stage
|
|
52
|
+
- exports / shipped out / departures = `loading_end`
|
|
53
|
+
- imports / arrivals / received = `unloading_start`
|
|
54
|
+
- on the water / afloat / in transit = `cargo_on_water_state`
|
|
55
|
+
- floating storage = `storing_state`
|
|
56
|
+
- **Year range**: start year to current year. "2020-2026" or "last 5 years" = start year 5 years ago. MINIMUM 3 years for meaningful seasonal pattern.
|
|
57
|
+
- **Unit**: commodity defaults (oil=bpd, LNG=t, LPG=t)
|
|
58
|
+
- **Frequency**: `day` or `month` -- these are the two supported by `seasonal_charts()`. Default to `month` if not specified. Do NOT ask -- just default and mention in confirmation.
|
|
59
|
+
- **Chart type**: multi-year overlay line chart (default). User can request "area chart" override.
|
|
60
|
+
- **Moving average**: optional, if user says "7-day MA" or "30-day smoothing"
|
|
61
|
+
|
|
62
|
+
## Step 3: Check for Missing Parameters (ASK before confirming)
|
|
63
|
+
|
|
64
|
+
Required (ask if missing):
|
|
65
|
+
- **Product** -- MUST be specified
|
|
66
|
+
- **Activity** -- MUST be specified. If direction ambiguous, ask: "exports from origin or imports to destination?"
|
|
67
|
+
- **Year range** -- MUST be specified. If user says just "seasonal", ask: "What year range should I analyze? e.g., 2020-2026"
|
|
68
|
+
|
|
69
|
+
Optional but commonly specified:
|
|
70
|
+
- **Geography** -- origin and/or destination (optional)
|
|
71
|
+
|
|
72
|
+
Ask one parameter at a time. Do NOT bulk-ask.
|
|
73
|
+
|
|
74
|
+
## Step 4: Resolve Entity IDs
|
|
75
|
+
|
|
76
|
+
For each entity mentioned (origin, destination, product):
|
|
77
|
+
1. Check `lib/aliases.json` for common shorthands (ME, AG, USG, ARA, etc.)
|
|
78
|
+
2. Call `lib/entities.py` resolve functions with the correct entity type and layer:
|
|
79
|
+
- `resolve_geography(term, layer)` for origins/destinations
|
|
80
|
+
- `resolve_product(term, layer)` for products
|
|
81
|
+
- `resolve(term, entity_type, layer, cache)` for the unified resolver with caching
|
|
82
|
+
3. If multiple matches: present top 3 candidates. Ask user to pick. NEVER auto-correct.
|
|
83
|
+
4. If zero matches: tell user the term wasn't found, suggest checking spelling or trying a broader term.
|
|
84
|
+
|
|
85
|
+
Use `EntityCache()` from `lib/entities.py` for the session to avoid re-resolving the same entities.
|
|
86
|
+
|
|
87
|
+
## Step 5: Confirm Before Executing (MANDATORY)
|
|
88
|
+
|
|
89
|
+
Present the complete parameter set:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Query: [restate the user's question]
|
|
93
|
+
Analysis: Seasonal pattern
|
|
94
|
+
Endpoint: CargoTimeSeries (via complete_seasonal_flows)
|
|
95
|
+
Activity: [filter_activity value] ([user's term])
|
|
96
|
+
Origin: [name (layer)] or "not specified"
|
|
97
|
+
Destination: [name (layer)] or "not specified"
|
|
98
|
+
Product: [name (layer)]
|
|
99
|
+
Year range: [start_year] to [current_year] ([N] years)
|
|
100
|
+
Frequency: [day/month]
|
|
101
|
+
Unit: [unit] ([reason])
|
|
102
|
+
Chart: Multi-year overlay with min/max band, average, last year, current year
|
|
103
|
+
Intra: [exclude_intra_country for exports/imports, all otherwise]
|
|
104
|
+
|
|
105
|
+
Confirm or adjust?
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
NEVER execute without confirmation. Wait for user response.
|
|
109
|
+
|
|
110
|
+
## Step 6: Generate & Execute Code Artifact
|
|
111
|
+
|
|
112
|
+
Generate a self-contained .py file using the data -> viz -> save pipeline:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
"""Vortexa Seasonal: {description}
|
|
116
|
+
Generated by /vortexa:seasonal
|
|
117
|
+
Date: {date}
|
|
118
|
+
"""
|
|
119
|
+
from datetime import datetime
|
|
120
|
+
import os
|
|
121
|
+
import sys; sys.path.insert(0, "lib")
|
|
122
|
+
from entities import resolve_geography, resolve_product, EntityCache
|
|
123
|
+
from seasonal import complete_seasonal_flows
|
|
124
|
+
from visualization import seasonal_chart
|
|
125
|
+
|
|
126
|
+
# Entity Resolution
|
|
127
|
+
cache = EntityCache()
|
|
128
|
+
{entity_resolution_code}
|
|
129
|
+
|
|
130
|
+
# Seasonal Analysis
|
|
131
|
+
seasonal_df = complete_seasonal_flows(
|
|
132
|
+
start_y={start_year}, start_m=1, start_d=1,
|
|
133
|
+
product_ids=[{product_id}], product_ids_excl=[],
|
|
134
|
+
freq="{frequency}",
|
|
135
|
+
unit="{unit}",
|
|
136
|
+
activity="{activity}",
|
|
137
|
+
origin_ids=[{origin_ids}], origin_ids_excl=[],
|
|
138
|
+
destination_ids=[{destination_ids}], destination_ids_excl=[],
|
|
139
|
+
ma_period={ma_period_or_None},
|
|
140
|
+
exclude_intra={True_or_False},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
print(seasonal_df)
|
|
144
|
+
|
|
145
|
+
# Chart
|
|
146
|
+
fig = seasonal_chart(seasonal_df, title="{title}", y_label="{unit_label}")
|
|
147
|
+
|
|
148
|
+
os.makedirs("output", exist_ok=True)
|
|
149
|
+
filepath = f"output/{slug}_seasonal_{datetime.now().strftime('%Y-%m-%d')}.html"
|
|
150
|
+
fig.write_html(filepath, auto_open=True)
|
|
151
|
+
print(f"Chart saved: {filepath}")
|
|
152
|
+
|
|
153
|
+
# Export (uncomment to save)
|
|
154
|
+
# seasonal_df.to_csv(f"output/{slug}_seasonal_{datetime.now().strftime('%Y-%m-%d')}.csv", index=False)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
After generating, ask: "Save as new file, or add to an existing notebook/script?" Default: new file.
|
|
158
|
+
Run the code to get results.
|
|
159
|
+
|
|
160
|
+
## Step 7: Present Results
|
|
161
|
+
|
|
162
|
+
- Show full seasonal DataFrame (12-365 rows depending on freq)
|
|
163
|
+
- One-line methodology footnote using CONFIRMED parameters:
|
|
164
|
+
```
|
|
165
|
+
Methodology: CargoTimeSeries (seasonal) | {activity} | {key filters} | {freq} | {unit} | {start_year}-{current_year}
|
|
166
|
+
```
|
|
167
|
+
Use human-readable names for entity filters (not hex IDs).
|
|
168
|
+
- File path to chart HTML
|
|
169
|
+
- Offer: "Export to CSV?" and "Want a summary of these results?"
|
|
170
|
+
|
|
171
|
+
## Error Handling
|
|
172
|
+
|
|
173
|
+
Report immediately in plain English, no auto-retry:
|
|
174
|
+
|
|
175
|
+
- **401 Unauthorized**: "API key is invalid or expired. Run /vortexa:init to check your setup."
|
|
176
|
+
- **500 Server Error**: "Vortexa API returned a server error. Try again in a few minutes."
|
|
177
|
+
- **Timeout**: "Query timed out. Try narrowing the date range or reducing filters."
|
|
178
|
+
- **Empty results**: "No data found for this query. Check that the entity names, date range, and filters are correct."
|
|
179
|
+
- **Entity not found**: "The term '{term}' returned no matches. Check spelling or try a broader term."
|
|
180
|
+
- **Multiple entity matches**: "{n} matches found for '{term}': [list]. Which did you mean?"
|
|
181
|
+
- **< 3 years**: "Seasonal analysis with fewer than 3 years produces unreliable min/max bands. Consider extending the range."
|
|
182
|
+
|
|
183
|
+
Never auto-retry. Never self-correct. Report the error and let the user decide.
|
|
184
|
+
|
|
185
|
+
</process>
|