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.
@@ -0,0 +1,188 @@
1
+ # Date Ranges & Units
2
+
3
+ > Date parsing conventions and unit selection rules for all Vortexa queries.
4
+ > Loaded automatically for every /vortexa: skill.
5
+
6
+ ---
7
+
8
+ ## Date Parsing Convention
9
+
10
+ Two distinct patterns: **calendar periods** (named references to fixed ranges) and **trailing periods** (rolling windows from today).
11
+
12
+ **Rule:** "last" + named period = calendar. "last" + number + period = trailing.
13
+
14
+ ### Calendar Periods (Named References)
15
+
16
+ | User Says | Start | End |
17
+ |---|---|---|
18
+ | "last year" | Jan 1 of previous year, 00:00:00 | Dec 31 of previous year, 23:59:59 |
19
+ | "last month" | 1st of previous month, 00:00:00 | Last day of previous month, 23:59:59 |
20
+ | "last quarter" | 1st of previous quarter, 00:00:00 | Last day of previous quarter, 23:59:59 |
21
+ | "Q3 2025" | Jul 1 2025, 00:00:00 | Sep 30 2025, 23:59:59 |
22
+ | "Q1 2026" | Jan 1 2026, 00:00:00 | Mar 31 2026, 23:59:59 |
23
+ | "2024" | Jan 1 2024, 00:00:00 | Dec 31 2024, 23:59:59 |
24
+ | "2025" | Jan 1 2025, 00:00:00 | Dec 31 2025, 23:59:59 |
25
+ | "YTD", "year to date" | Jan 1 of current year, 00:00:00 | Current timestamp |
26
+ | "this month" | 1st of current month, 00:00:00 | Current timestamp |
27
+ | "this quarter" | 1st of current quarter, 00:00:00 | Current timestamp |
28
+
29
+ ### Quarter Reference
30
+
31
+ | Quarter | Start | End |
32
+ |---|---|---|
33
+ | Q1 | Jan 1 | Mar 31 |
34
+ | Q2 | Apr 1 | Jun 30 |
35
+ | Q3 | Jul 1 | Sep 30 |
36
+ | Q4 | Oct 1 | Dec 31 |
37
+
38
+ ### Trailing Periods (Number + Unit)
39
+
40
+ | User Says | Start | End |
41
+ |---|---|---|
42
+ | "last 6 months" | Today minus 6 months, 00:00:00 | Current timestamp |
43
+ | "last 30 days" | Today minus 30 days, 00:00:00 | Current timestamp |
44
+ | "last 12 months" | Today minus 12 months, 00:00:00 | Current timestamp |
45
+ | "last 2 weeks" | Today minus 14 days, 00:00:00 | Current timestamp |
46
+ | "rolling 3 months" | Today minus 90 days, 00:00:00 | Current timestamp |
47
+
48
+ ### CRITICAL DATE RULES
49
+
50
+ **NEVER** leave end dates at midnight (00:00:00) -- data from the last day will be excluded entirely. This is the most common silent data loss bug.
51
+
52
+ **ALWAYS** use `datetime(year, month, day, 23, 59, 59)` for historical end dates.
53
+
54
+ **ALWAYS** specify `filter_time_min` and `filter_time_max` explicitly -- never rely on defaults.
55
+
56
+ ### Code Examples
57
+
58
+ ```python
59
+ from datetime import datetime
60
+
61
+ # "last year" (calendar) -- assuming current year is 2026
62
+ time_min = datetime(2025, 1, 1) # Jan 1, 00:00:00
63
+ time_max = datetime(2025, 12, 31, 23, 59, 59) # Dec 31, 23:59:59
64
+
65
+ # "January 2025"
66
+ time_min = datetime(2025, 1, 1)
67
+ time_max = datetime(2025, 1, 31, 23, 59, 59)
68
+
69
+ # "Q3 2025"
70
+ time_min = datetime(2025, 7, 1)
71
+ time_max = datetime(2025, 9, 30, 23, 59, 59)
72
+
73
+ # "last 6 months" (trailing) -- rolling from today
74
+ from dateutil.relativedelta import relativedelta
75
+ time_min = datetime.now() - relativedelta(months=6)
76
+ time_max = datetime.now()
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Frequency Selection
82
+
83
+ Frequency determines the time bucket size for timeseries queries.
84
+
85
+ ### Frequency Options
86
+
87
+ | User Says | API Value (CM/CTS) | API Value (Voyages) | Description |
88
+ |---|---|---|---|
89
+ | "daily" | `day` | `day` | One data point per day |
90
+ | "weekly" | `week` | `week` | One data point per week |
91
+ | "DOE week" | `doe_week` | `doe_week` | US EIA/DOE week convention |
92
+ | "monthly" | `month` | `month` | One data point per month |
93
+ | "quarterly" | `quarter` | `quarter` | One data point per quarter |
94
+ | "yearly", "annually" | `year` | `year` | One data point per year |
95
+
96
+ ### Frequency Guidance by Date Range
97
+
98
+ | Date Range Length | Suggested Frequency | Rationale |
99
+ |---|---|---|
100
+ | < 1 month | `day` | Daily granularity is useful for short windows |
101
+ | 1-3 months | `day` or `week` | Weekly smooths noise; daily for detail |
102
+ | 3-12 months | `week` or `month` | Monthly is cleaner for medium ranges |
103
+ | 1-3 years | `month` | Monthly avoids excessive data points |
104
+ | > 3 years | `month` or `quarter` | Quarterly for very long ranges |
105
+
106
+ ### Frequency Rules
107
+
108
+ **ALWAYS** ask the user what frequency they want -- unless they already specified it in the query.
109
+
110
+ If the user says "monthly crude exports" -- "monthly" IS the frequency. No need to ask.
111
+
112
+ If the user says "crude exports from Saudi Arabia last year" -- frequency is NOT specified. Ask: "What frequency would you like? daily / weekly / monthly?"
113
+
114
+ The guidance table above is for suggesting defaults, not auto-selecting. Present it as a recommendation.
115
+
116
+ For CargoTimeSeries, the parameter is `timeseries_frequency`.
117
+ For VoyagesTimeseries, the parameter is `breakdown_frequency`.
118
+
119
+ ---
120
+
121
+ ## Unit Defaults by Commodity
122
+
123
+ ### Default Unit Table
124
+
125
+ | Product Group | Default Unit | API Value | Reason |
126
+ |---|---|---|---|
127
+ | Crude & Condensates | barrels per day | `bpd` | Industry standard for oil flow rates |
128
+ | Clean Petroleum Products | barrels per day | `bpd` | Industry standard for oil flow rates |
129
+ | Dirty Petroleum Products | barrels per day | `bpd` | Industry standard for oil flow rates |
130
+ | LNG | tonnes | `t` | Industry standard for LNG |
131
+ | LPG | tonnes | `t` | Industry standard for LPG |
132
+ | Counting queries | count | `c` | Number of cargoes |
133
+
134
+ ### All Available Units
135
+
136
+ | Unit | API Value (CTS) | API Value (CM search) | Description |
137
+ |---|---|---|---|
138
+ | barrels | `b` | `b` | Absolute oil volume |
139
+ | barrels per day | `bpd` | -- | Oil flow rate (most common for trends) |
140
+ | tonnes | `t` | `t` | Absolute weight (LNG, LPG, or oil) |
141
+ | tonnes per day | `tpd` | -- | Weight flow rate |
142
+ | cubic metres | `cbm` | `cbm` | LNG volume |
143
+ | count | `c` | -- | Number of cargo movements |
144
+ | count per day | `cpd` | -- | Cargo movement rate |
145
+ | million per day | `mpd` | -- | Millions per day |
146
+ | barrels per annum | `bpa` | -- | Annual oil rate |
147
+ | tonnes per annum | `tpa` | -- | Annual weight rate |
148
+ | million per annum | `mpa` | -- | Millions per annum |
149
+
150
+ **Note:** CargoMovements search uses `cm_unit` (only `b`, `t`, `cbm`). CargoTimeSeries uses `timeseries_unit` (full list above). VoyagesSearchEnriched uses `unit` (only `b`, `t`, `cbm`).
151
+
152
+ ### Unit Rules
153
+
154
+ **NEVER** guess units if the product could use multiple -- ASK the user. Example: LNG can use tonnes (`t`) or cubic metres (`cbm`).
155
+
156
+ **ALWAYS** mention the unit choice and its rationale in the confirmation step, so the user can override.
157
+
158
+ **ALWAYS** match the unit to the commodity:
159
+ - Oil queries: `bpd` for trends, `b` for absolute volumes
160
+ - LNG queries: `t` (default), or `cbm` if user specifies volume
161
+ - LPG queries: `t`
162
+ - Counting: `c` or `cpd`
163
+
164
+ **ALWAYS** use rate units (`bpd`, `tpd`) for timeseries trends. Use absolute units (`b`, `t`) only for snapshot/total queries.
165
+
166
+ ---
167
+
168
+ ## Confirmation Step: Date, Unit & Frequency
169
+
170
+ Every data query confirmation must include:
171
+
172
+ ```
173
+ Time: Jan 1 2025, 00:00:00 -> Dec 31 2025, 23:59:59 (calendar year 2025)
174
+ Unit: bpd (barrels per day -- default for oil)
175
+ Frequency: monthly
176
+ ```
177
+
178
+ If the user did not specify frequency, the confirmation should say:
179
+
180
+ ```
181
+ Frequency: Please specify -- daily / weekly / monthly?
182
+ ```
183
+
184
+ If the unit choice is non-obvious, explain why:
185
+
186
+ ```
187
+ Unit: tonnes (default for LNG -- would you prefer cubic metres?)
188
+ ```
@@ -0,0 +1,176 @@
1
+ # Adding a New Vortexa Endpoint
2
+
3
+ > Developer guide for extending the skills package with new API endpoints.
4
+ > Each new endpoint requires exactly 3 files that compose shared infrastructure.
5
+
6
+ ---
7
+
8
+ ## The 3-File Pattern
9
+
10
+ | File | Location | Purpose |
11
+ |------|----------|---------|
12
+ | Context doc | `context/<endpoint>.md` | API reference: parameters, filters, columns, code examples |
13
+ | Python module | `lib/<module>.py` | SDK wrapper functions: query, parse, transform |
14
+ | Skill file | `commands/vortexa/<skill>.md` | Slash command: NL parsing, confirmation, code generation |
15
+
16
+ Everything else -- entity resolution, date parsing, guardrails, visualization, setup checks -- is shared infrastructure already built.
17
+
18
+ ---
19
+
20
+ ## Shared Infrastructure (Already Built)
21
+
22
+ These are composed by all endpoints -- no duplication needed:
23
+
24
+ | Component | Path | What It Provides |
25
+ |-----------|------|-----------------|
26
+ | Guardrails | `context/guardrails.md` | NEVER/ALWAYS rules, activity filter table, confirmation template |
27
+ | Entity resolution | `context/entity-resolution.md` | Name-to-ID workflow, disambiguation protocol |
28
+ | Date & units | `context/date-units.md` | Date parsing, unit defaults, frequency guidance |
29
+ | Entity resolver | `lib/entities.py` | `resolve_geography()`, `resolve_product()`, `EntityCache` |
30
+ | API auth | `lib/api.py` | `load_api_key()`, environment validation |
31
+ | Utilities | `lib/utils.py` | `_to_dt()`, `_cols()`, `top_n_with_other()` |
32
+ | Visualization | `lib/visualization.py` | Vortexa theme, chart functions |
33
+ | Setup check | `commands/vortexa/_check-setup.md` | Pre-execution environment validation |
34
+ | Skill template | `commands/vortexa/_skill-template.md` | YAML frontmatter and process structure reference |
35
+
36
+ ---
37
+
38
+ ## Step-by-Step: Adding an Endpoint
39
+
40
+ ### Step 1: Context Doc (`context/<endpoint>.md`)
41
+
42
+ Create the API reference document. Follow the structure of `context/cargo-movements.md`:
43
+
44
+ 1. **Header**: Endpoint name, one-line description, SDK class name
45
+ 2. **When to Use**: Decision table -- when this endpoint vs others, with signal words
46
+ 3. **Parameters**: Full parameter table with name, type, default, description
47
+ 4. **Filter Options**: All filter parameters with valid values
48
+ 5. **Response Fields**: Column/field names, types, descriptions
49
+ 6. **Code Examples**: Working SDK code snippets for common queries
50
+ 7. **Cross-References**: Links to guardrails.md, entity-resolution.md, date-units.md
51
+
52
+ Target: 400-700 lines. Every parameter documented. Include negative guidance (NEVER do X).
53
+
54
+ ### Step 2: Python Module (`lib/<module>.py`)
55
+
56
+ Create SDK wrapper functions. Follow the structure of `lib/movements.py`:
57
+
58
+ 1. **Lazy imports**: `from vortexasdk import X` inside functions, not at module level
59
+ 2. **Internal imports**: `from lib.entities import resolve_geography, resolve_product`
60
+ 3. **Function signature**: Accept human-readable parameters, return DataFrames
61
+ 4. **Column renaming**: Apply `_cols()` from lib/utils.py for human-readable output
62
+ 5. **Error handling**: Catch SDK exceptions, return helpful messages
63
+
64
+ Minimum functions:
65
+ - `<endpoint>_timeseries(...)` -- time-series query with filters
66
+ - `<endpoint>_search(...)` -- individual record search
67
+ - Column rename map as module-level dict
68
+
69
+ ### Step 3: Skill File (`commands/vortexa/<skill>.md`)
70
+
71
+ Create the slash command. Follow the structure of `commands/vortexa/cargo-flows.md`:
72
+
73
+ 1. **YAML frontmatter**: name, description, argument-hint, allowed-tools
74
+ 2. **Objective**: What this skill does (1-3 sentences)
75
+ 3. **Execution context**: Pre-loaded vs on-demand context docs
76
+ 4. **Setup check**: Include `@commands/vortexa/_check-setup.md`
77
+ 5. **Process steps**:
78
+ - Read endpoint context doc
79
+ - Parse user query (extract entities, dates, filters)
80
+ - Check for missing parameters (ask if needed)
81
+ - Resolve entity IDs
82
+ - Confirm before executing (MANDATORY)
83
+ - Generate and execute Python code artifact
84
+ - Present results with methodology footnote
85
+ - Offer CSV export and summary
86
+
87
+ Target: 180-250 lines.
88
+
89
+ ---
90
+
91
+ ## Worked Example: Freight Outlook (Hypothetical)
92
+
93
+ To add Freight Outlook as a new endpoint:
94
+
95
+ ### File 1: `context/freight-outlook.md`
96
+
97
+ Document all FFA routes (TC1-TC17, TD1-TD25), pricing parameters, rate types (flat rate, worldscale, $/tonne), time-series options, and settlement conventions. Include a decision table for when to use Freight Outlook vs CargoTimeSeries for cost analysis. Cross-reference guardrails.md for confirmation template and date-units.md for time range parsing.
98
+
99
+ ### File 2: `lib/freight.py`
100
+
101
+ ```python
102
+ """Freight outlook queries and rate helpers."""
103
+
104
+ import pandas as pd
105
+
106
+ # column rename map
107
+ freight_cols = {
108
+ "key": "route",
109
+ "value": "rate",
110
+ "count": "fixtures",
111
+ }
112
+
113
+ def freight_timeseries(route, time_min, time_max, rate_type="flat_rate", frequency="day"):
114
+ """Query freight rate timeseries for a given route."""
115
+ from vortexasdk import FreightPricing
116
+ from lib.utils import _to_dt, _cols
117
+
118
+ result = FreightPricing().search(
119
+ route=route,
120
+ filter_time_min=_to_dt(time_min),
121
+ filter_time_max=_to_dt(time_max),
122
+ rate_type=rate_type,
123
+ breakdown_frequency=frequency,
124
+ ).to_df()
125
+
126
+ return _cols(result, freight_cols)
127
+
128
+ def freight_search(route, time_min, time_max, size=500):
129
+ """Search individual freight fixtures for a route."""
130
+ from vortexasdk import FreightPricing
131
+ from lib.utils import _to_dt, _cols
132
+
133
+ result = FreightPricing().search(
134
+ route=route,
135
+ filter_time_min=_to_dt(time_min),
136
+ filter_time_max=_to_dt(time_max),
137
+ size=size,
138
+ ).to_df()
139
+
140
+ return _cols(result, freight_cols)
141
+ ```
142
+
143
+ ### File 3: `commands/vortexa/freight.md`
144
+
145
+ Register as `/vortexa:freight`. Parse route names from natural language ("TD3C", "AG to Japan VLCC"), resolve to Vortexa route IDs, confirm rate type and time range, generate Python code artifact using `lib/freight.py`, present rate chart with Vortexa theme.
146
+
147
+ ### Shared infrastructure reused automatically:
148
+
149
+ - Entity resolution for vessel classes and geographies
150
+ - Date parsing for time ranges
151
+ - Vortexa theme for rate charts
152
+ - Setup check for environment validation
153
+ - Guardrails confirmation template
154
+
155
+ ---
156
+
157
+ ## Checklist for New Endpoint
158
+
159
+ Before submitting, verify all items:
160
+
161
+ - [ ] Context doc covers all parameters and response fields
162
+ - [ ] Context doc includes NEVER/ALWAYS negative guidance
163
+ - [ ] Context doc has "When to Use" decision table with signal words
164
+ - [ ] Context doc cross-references guardrails.md, entity-resolution.md, date-units.md
165
+ - [ ] Python module uses lazy imports (no top-level vortexasdk)
166
+ - [ ] Python module uses `_cols()` for column renaming
167
+ - [ ] Python module has both timeseries and search functions
168
+ - [ ] Python module has module-level column rename dict
169
+ - [ ] Skill file includes setup check via `@commands/vortexa/_check-setup.md`
170
+ - [ ] Skill file has mandatory confirmation step before execution
171
+ - [ ] Skill file includes methodology footnote in results
172
+ - [ ] Skill file offers CSV export after presenting results
173
+ - [ ] All entity resolution goes through `lib/entities.py`
174
+ - [ ] Generated code uses `sys.path.insert(0, "lib")` pattern
175
+ - [ ] npm `files` allowlist includes the new files' directories (already covered if using `context/`, `lib/`, `commands/`)
176
+ - [ ] CLAUDE.md "Available Skills" section updated with the new skill
@@ -0,0 +1,217 @@
1
+ # Entity Resolution
2
+
3
+ > Step-by-step workflow for resolving human-readable names to Vortexa 64-char hex IDs.
4
+ > Loaded automatically for every /vortexa: skill.
5
+
6
+ ---
7
+
8
+ ## Resolution Workflow
9
+
10
+ Every Vortexa API filter requires hex IDs, not names. Follow this process for every entity:
11
+
12
+ 1. **Identify entity type** -- geography, product, vessel, or corporate
13
+ 2. **Call the correct SDK search method** with appropriate filters (especially `filter_layer` for geographies and products)
14
+ 3. **Handle results:**
15
+ - 1 match = use it
16
+ - Multiple matches = disambiguate (present to user)
17
+ - 0 matches = suggest alternatives (broaden search, check spelling, try partial name)
18
+
19
+ ---
20
+
21
+ ## Entity Types & SDK Methods
22
+
23
+ | Entity Type | SDK Class | Search Method | Key Filter | Size Default |
24
+ |---|---|---|---|---|
25
+ | Geography | `Geographies` | `.search(term=..., filter_layer=...)` | `filter_layer` (critical) | 1 -- set higher |
26
+ | Product | `Products` | `.search(term=..., filter_layer=...)` | `filter_layer` | 1 -- set higher |
27
+ | Vessel | `Vessels` | `.search(term=..., vessel_classes=...)` | `vessel_classes` | 1 -- set higher |
28
+ | Corporation | `Corporations` | `.search(term=...)` | (none in SDK) | 1 -- set higher |
29
+
30
+ **ALWAYS** set `size` to at least 10 for resolution searches. Default `size=1` may return the wrong match.
31
+
32
+ ---
33
+
34
+ ## Geography Resolution
35
+
36
+ Geographies have a deep hierarchy. The `filter_layer` parameter is mandatory for accurate resolution.
37
+
38
+ ### Hierarchy (widest to most specific)
39
+
40
+ ```
41
+ wider_shipping_region > shipping_region_v2 > region > country > port > terminal
42
+ ```
43
+
44
+ ### Resolution Table
45
+
46
+ | User Says | filter_layer | Example Vortexa Name | Notes |
47
+ |---|---|---|---|
48
+ | "China", "US", "Saudi Arabia" | `country` | China, United States, Saudi Arabia | Country names |
49
+ | "Rotterdam", "Fujairah", "Ras Tanura" | `port` | Rotterdam [NL], Fujairah, Ras Tanura | Port names |
50
+ | "Middle East", "Asia", "Europe" | `region` | Middle East/North Africa, Asia, Europe | Broad regions |
51
+ | "AG", "Arabian Gulf", "Persian Gulf" | `shipping_region_v2` | Arabian/Persian Gulf | Shipping regions |
52
+ | "USG", "US Gulf Coast" | `shipping_region_v2` | US Gulf Coast | Shipping regions |
53
+ | "WAF", "West Africa" | `shipping_region_v2` | West Africa | Shipping regions |
54
+ | "ARA" | `port` (multiple) | Amsterdam, Rotterdam, Antwerp | Resolve each port separately |
55
+ | "PADD I", "East Coast" | `shipping_region_v2` | US Atlantic Coast | US regional |
56
+ | "Suez Canal", "Panama Canal" | `waypoint` | Suez Canal, Panama Canal | Chokepoints |
57
+
58
+ ### Code Pattern
59
+
60
+ ```python
61
+ from vortexasdk import Geographies
62
+
63
+ # ALWAYS specify filter_layer
64
+ result = Geographies().search(
65
+ term="Saudi Arabia",
66
+ filter_layer="country",
67
+ exact_term_match=True
68
+ ).to_df()
69
+
70
+ saudi_id = result.iloc[0]["id"] # 64-char hex
71
+ ```
72
+
73
+ **NEVER** search geographies without `filter_layer`. "China" without a layer returns ports, regions, and the country.
74
+
75
+ ---
76
+
77
+ ## Product Resolution
78
+
79
+ Products have a 4-level hierarchy. Match the `filter_layer` to the user's specificity.
80
+
81
+ ### Hierarchy
82
+
83
+ ```
84
+ group > group_product > category > grade
85
+ ```
86
+
87
+ ### Resolution Table
88
+
89
+ | User Says | filter_layer | Vortexa Name | Notes |
90
+ |---|---|---|---|
91
+ | "crude", "crude oil" | `group` | Crude & Condensates | Broadest oil group |
92
+ | "clean products", "CPP" | `group` | Clean Petroleum Products | |
93
+ | "dirty products", "DPP" | `group` | Dirty Petroleum Products | |
94
+ | "LNG" | `group` | LNG | |
95
+ | "LPG" | `group` | LPG | |
96
+ | "fuel oil" | `group_product` | Fuel Oil | Under Dirty Products |
97
+ | "naphtha" | `group_product` | Naphtha | Under Clean Products |
98
+ | "gasoline" | `group_product` | Gasoline / Mogas | Under Clean Products |
99
+ | "gasoil", "diesel" | `group_product` | Gasoil/Diesel | Under Clean Products |
100
+ | "condensate" | `group_product` | Condensate | Under Crude & Condensates |
101
+ | "Arab Light", "Bonny Light" | `grade` | Arab Light, Bonny Light | Most specific |
102
+
103
+ ### Code Pattern
104
+
105
+ ```python
106
+ from vortexasdk import Products
107
+
108
+ result = Products().search(
109
+ term="crude",
110
+ filter_layer="group"
111
+ ).to_df()
112
+
113
+ # Verify the match -- "crude" may return multiple layers
114
+ crude_id = result[result["name"] == "Crude & Condensates"].iloc[0]["id"]
115
+ ```
116
+
117
+ **ALWAYS** verify the `layer` and `name` columns in results. Fuzzy matching may return grades when you want the group.
118
+
119
+ ---
120
+
121
+ ## Vessel Resolution
122
+
123
+ Vessels are searched by name, IMO number, or class. No `filter_layer` applies.
124
+
125
+ | Search By | Method | Notes |
126
+ |---|---|---|
127
+ | Vessel name | `Vessels().search(term="vessel name")` | Fuzzy match; may return multiple |
128
+ | IMO number | `Vessels().search(ids=[9779836])` | Pass IMO as integer, not string |
129
+ | Vessel class | `Vessels().search(vessel_classes="oil_vlcc")` | Returns all vessels of that class |
130
+ | Name + class | `Vessels().search(term="ocean", vessel_classes="oil_vlcc")` | Narrows by both |
131
+
132
+ ### Code Pattern
133
+
134
+ ```python
135
+ from vortexasdk import Vessels
136
+
137
+ # By name
138
+ result = Vessels().search(term="Olympic Pride").to_df()
139
+
140
+ # By IMO (MUST be integer)
141
+ result = Vessels().search(ids=[9779836]).to_df(columns="all")
142
+ ```
143
+
144
+ **NEVER** pass IMO numbers as strings -- the API requires integers in the `ids` or `term` array.
145
+
146
+ For vessel class filtering in data queries (CM/Voyages), use `filter_vessel_classes=["oil_vlcc"]` directly -- no ID resolution needed.
147
+
148
+ ---
149
+
150
+ ## Corporate Resolution
151
+
152
+ Corporations (shipowners, charterers, traders) are searched by name only. The SDK does not support filtering by entity type.
153
+
154
+ | Search By | Method | Notes |
155
+ |---|---|---|
156
+ | Company name | `Corporations().search(term="Shell")` | Fuzzy match |
157
+ | Exact name | `Corporations().search(term="CHEVRON", exact_term_match=True)` | Exact match |
158
+
159
+ ### Code Pattern
160
+
161
+ ```python
162
+ from vortexasdk import Corporations
163
+
164
+ result = Corporations().search(term="BAHRI", exact_term_match=True).to_df()
165
+ corp_id = result.iloc[0]["id"]
166
+ ```
167
+
168
+ **Limitation:** The SDK `Corporations` class does not expose `corporateEntityType` filtering. To filter by role (effective_controller, charterer, etc.), use the REST API directly:
169
+
170
+ ```python
171
+ import requests
172
+
173
+ payload = {
174
+ "term": "CHEVRON",
175
+ "corporateEntityType": ["effective_controller"],
176
+ "size": 10
177
+ }
178
+ resp = requests.post(
179
+ "https://api.vortexa.com/v5/reference/charterers",
180
+ headers={"x-api-key": VORTEXA_API_KEY},
181
+ json=payload
182
+ )
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Disambiguation Protocol
188
+
189
+ When a search returns multiple matches:
190
+
191
+ 1. **Present top 5 matches** to the user with: name, type/layer, and first 8 chars of ID
192
+ 2. **Ask the user to select** the correct entity
193
+ 3. **NEVER guess** -- ambiguous entities cause silently wrong results
194
+
195
+ ### Example Disambiguation
196
+
197
+ ```
198
+ Multiple matches found for "Gulf":
199
+
200
+ 1. Arabian/Persian Gulf (shipping_region_v2) - ID: a3c1e9f2...
201
+ 2. US Gulf Coast (shipping_region_v2) - ID: b7d4f8a1...
202
+ 3. Gulf of Mexico (region) - ID: c2e5a7b3...
203
+ 4. Gulf of Oman (shipping_region_v2) - ID: d8f1c4e6...
204
+
205
+ Which did you mean?
206
+ ```
207
+
208
+ ---
209
+
210
+ ## ID Format
211
+
212
+ | Format | Length | Used In | Example |
213
+ |---|---|---|---|
214
+ | Full hex ID | 64 characters | All API filter parameters | `a1b2c3d4e5f6...` (64 chars) |
215
+ | Truncated ID | 16 characters | `cargo_movement_id` in joins, voyage links | `a1b2c3d4e5f6a7b8` |
216
+
217
+ **ALWAYS** use full 64-character IDs for API calls. Truncated 16-char IDs appear in cross-endpoint joins (e.g., matching cargo movements to voyages) but are not valid for filter parameters.