← FactIQCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to FactIQ
Snapshot Sep 30, 2026 · 23:07 UTC · version 0.35.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "factiq",
"description": "Answer economic and financial data questions with FactIQ data: US and international indicators, trade, markets, earnings calls, executive media, business news, and satellite signals. Satellite coverage includes rainfall, fires, air quality, NDVI, heat, soil moisture, nighttime lights, reservoir levels, shipping, ports, and chokepoints by country, state, or bounding box. Use for unemployment, inflation, GDP, wages, energy, trade flows, stocks, commodities, forex, earnings intelligence, drought and monsoon conditions, wildfires, economic charts and maps, terminal previews, and research reports. Discover series, query read-only SQL, compute, then return a sourced answer or local output. Use the bundled SQL generators for bilateral trade.\n",
"included_files": [],
"skill_md_contents": "---\nname: factiq\ndescription: >\n Answer economic and financial data questions with FactIQ data: US and\n international indicators, trade, markets, earnings calls, executive media,\n business news, and satellite signals. Satellite coverage includes rainfall,\n fires, air quality, NDVI, heat, soil moisture, nighttime lights, reservoir\n levels, shipping, ports, and chokepoints by country, state, or bounding box.\n Use for unemployment, inflation, GDP, wages, energy, trade flows, stocks,\n commodities, forex, earnings intelligence, drought and monsoon conditions,\n wildfires, economic charts and maps, terminal previews, and research reports.\n Discover series, query read-only SQL, compute, then return a sourced answer or\n local output. Use the bundled SQL generators for bilateral trade.\n---\n\n# FactIQ Data Tools\n\nYou are the analyst. FactIQ provides authenticated MCP tools for discovery and\nfetching: catalog, dataset and series search, read-only SQL, series lookup,\nmarket data, transcript and media search, satellite signals, style guides, and\nfeedback. There is no server-side agent. You decompose the question, fetch the\ndata, do the math, then answer or build a local output.\n\n**Publishing boundary:** FactIQ cannot host charts or reports publicly or\ncreate public share links. When asked for a FactIQ-hosted link, explain the\nlimitation and offer an inline chart or local artifact. Do not inspect or\ndirect the user to FactIQ's legacy authenticated web interface, use browser\nautomation, or probe HTTP endpoints to work around a missing tool. The legacy\nweb interface is not a supported product workflow. Normal OAuth connection is\nstill supported; website, support, privacy, and OAuth URLs do not imply a\npublishing capability. If the user rules out other publishers, do not use\nChatGPT Sites or another service. Never call `send_feedback` for this\nintentional capability boundary.\n\nThree output modes:\n\n- **Direct answer** — a plain-text sentence with no chart. Use\n when the question asks for a single current value or a simple scalar lookup\n where a chart would add nothing: \"what's the US unemployment rate right now?\",\n \"latest CPI print\", \"Apple's trailing P/E\". Still fetch the value with the MCP\n tools — only the *presentation* is a sentence. State the number with its\n period and source (e.g. \"US unemployment was 4.1% in May 2026, per BLS.\").\n The moment the question wants a trend, a history, a comparison across\n categories or entities, a breakdown, or explicitly asks for a chart or report,\n switch to one of the modes below.\n- **Quick chart** (`term_chart.py`) — one focused local ChartSpec plus an inline\n terminal preview. Default for a single trend or category comparison. Maps can\n use a ranked-table terminal fallback; see `references/output/chart-spec.md`.\n- **Detailed report** — a saved report JSON object with summary, sections,\n charts, methodology, and terminal previews. Use for broad analytical\n questions. Covered domains route through `references/report-patterns/README.md`.\n If scope is unclear, use `references/report-patterns/interview-step.md` first.\n\n**Data in, output out:**\n\n- All discovery and fetching go through the FactIQ **MCP tools**. Local scripts\n build and render the final files from the fetched results.\n- The local scripts never touch the API:\n\n ```bash\n python3 \"{plugin_root}/scripts/term_chart.py\" render ... # terminal ChartSpec preview\n python3 \"{plugin_root}/scripts/comext_sql.py\" ... # SQL generator: Eurostat Comext (EU) trade\n python3 \"{plugin_root}/scripts/trade_sql.py\" ... # SQL generator: US/China/India/Korea/Japan/Taiwan customs\n python3 \"{plugin_root}/scripts/hs_codes.py\" ... # HS commodity code <-> name, offline\n python3 \"{plugin_root}/scripts/series_math.py\" ... # YoY/YTD/share/index/merge on saved results\n ```\n\n Resolve `{plugin_root}` once, then reuse it. In Claude Code it is\n `${CLAUDE_PLUGIN_ROOT}`. In Codex, start from the absolute path supplied for\n this `SKILL.md`: the plugin root is two directories above the directory\n containing this file (`skills/factiq/../..`). Never resolve these scripts\n from the shell's current working directory or from a similarly named\n `scripts/` directory in the user's project. Keep the quotes around the\n absolute path so installations under a directory containing spaces still\n work.\n\n **For any bilateral-trade question, generate the SQL instead of writing it.**\n `comext_sql.py` and `trade_sql.py` encode each schema's series-ID grammar,\n partner-code system, units, and HS-level rules, so the query is correct by\n construction — run `--help` on either for the subcommands (total / products /\n trend). Label the HS codes a ranking returns with `hs_codes.py` (zero server\n calls), and use `series_math.py` for growth or shares when you have saved the\n fetched payload locally.\n\n## Setup\n\nOne connection covers everything: the FactIQ **MCP server** bundled with this\nplugin (`.mcp.json`), authorized over OAuth. On first use the coding agent runs\nFactIQ's browser-based **Connect** flow. If the FactIQ tools are missing or\nreturn an auth error, the connection isn't set up yet — tell the user to\nauthorize the MCP server:\n\n- **Claude Code**: run **`/mcp`**, pick **factiq**, and complete the sign-in.\n- **Codex**: run **`codex mcp login factiq`** and complete the sign-in.\n\nThe same FactIQ login works everywhere (email, Google, or passkey) and authorizes\nthe data and feedback tools.\n\n**Local development.** The bundled MCP URL is\n`https://api.factiq.com/mcp`. For a local backend, edit `.mcp.json` in your\ndevelopment checkout or configure a standalone `factiq` MCP server in Codex or\nClaude Code that points at the local URL.\n\n## Tools\n\nAll FactIQ tools are MCP tools provided by the `factiq` MCP server.\n\n### Data\n\n| Tool | Purpose |\n|---|---|\n| `get_data_catalog` (`schemas?`, `full?`) | Per-schema index + the shared table DDL. **Call once per session before anything else.** `full=true` returns the heavy per-dataset dump (rarely needed — use `describe_dataset`). Schemas listed under `schemas_without_data` have no rows — skip them. |\n| `search_datasets` (`query`, `schemas?`, `limit?`) | Keyword (not semantic) ranking of datasets across all schemas. **The first discovery step** — find the right `schema` + `dataset_code`. |\n| `describe_dataset` (`schema`, `dataset_code`) | Full metadata for one dataset: topic, methodology, release dates, base-change notice, dimensions, example series. Call after `search_datasets`. |\n| `search_series` (`schema`, `terms`, `limit?`, `include_compound?`) | Series-level title-substring search within one schema (`terms` is a list — prefer short stems). Includes `COMPOUND::` series. |\n| `run_sql` (`schema`, `sql`, `question?`, `explore?`, `auto_retry?`, `page?`) | Read-only SELECT against one schema. The power tool for joins, pivots, aggregation. `page` works on the `nasa_fires` schema only, where individual rows are the answer; everywhere else, aggregate. |\n| `get_series` (`schema`, `series_id`, `from_year?`, `to_year?`, `transform?`) | Fetch one series — timeseries, tabular, or `COMPOUND::` ids all work. `transform=\"yoy_pct\"` (percent change) or `\"yoy_diff\"` (difference, for rates) adds a column with the change versus the same period one year earlier, matched by calendar date; the cell is null where that period is absent. A `coverage_note` with `missing_periods` means the series skips a period — disclose it. SEC-backed results include `row_sources` keyed by `result_index`, with the supporting filing, accession/form/date, reported-vs-derived status, and a standardized `source_link`. For those series `schema=\"filings\"` and `schema=\"sec\"` return the same result. |\n| `get_market_data` (`asset`, `data_type?`, `frequency?`, `limit?`) | Provider-neutral quotes, daily/weekly/monthly price history, company and ETF profiles, symbol search, FX, and commodities. `data_type` is `price_history`, `quote`, `company_profile`, `etf_profile`, or `symbol_search`; `limit` is 1–5,000. |\n| `get_geo_data` (`dataset`, `region`, `start_date`, `end_date`, `aggregation?`, `resolution?`, `include_flares?`) | Satellite-derived signals: `fires_viirs` (crop burning/wildfires; every detection since 2012 is held in FactIQ's own database, so calls answer in under a second — `aggregation=\"seasons\"` compares the same calendar window in every year since 2012 in one call, `\"grid\"` maps the footprint at a cell size you pick with `resolution`, `\"points\"` returns exact detection coordinates), `no2_tropomi` / `so2_tropomi` / `co_tropomi` (industrial, coal/smelting, and combustion activity), `aerosol_index_tropomi` (smoke/dust/haze), `ndvi_s2` (crop condition) — these five also accept `aggregation=\"grid\"` for a cell-by-cell spatial snapshot — `precip_chirps` (0.05° gauge-calibrated rainfall within 50S-50N), `precip_imerg` (0.1° global rainfall), `temperature_power`, `soil_moisture_power` — aggregated over a country, state (`\"India/Punjab\"`), or bbox. `resolution` and `include_flares` apply to `fires_viirs` only; gas flares and other permanent industrial heat are excluded unless you ask for them. **Read `references/data/satellite.md` before first use** — it covers windows (50 intervals, 200 for fires; grid/points 92 days, 366 for fires), the `valid_obs_share` rule, and attribution. For fire questions this tool does not cover, query the `nasa_fires` SQL schema (`references/data/schemas.md`). |\n| `search_company_filings` (`company`, `query?`, `concept?`, `search_target?`, `report_type?`, `fiscal_year?`, `fiscal_period?`, `metric_class?`, `segment?`, `date_from?`, `date_to?`, `active_only?`, `format?`, `limit?`) | The central tool for company filings. Deterministic (no model) search over the structured facts and report metadata in one company's filed reports, for every company FactIQ covers: US SEC filers (10-K, 10-Q, 8-K, plus 20-F/40-F/6-K for foreign filers) and companies listed in Germany (annual, half-year, Q1, and Q3 reports, values in EUR — e.g. `company=\"BAS\"` for BASF SE). Use an exact ticker; a share-class sibling (GOOGL for GOOG) resolves to the same filer, a German company resolves by its German ticker, full name, or LEI, and an ambiguous name comes back with `possible_matches`. Start with `search_target=\"coverage\"` to see which report types, periods, and metric classes exist. Then set `concept` to one metric name (`\"revenue\"`, `\"net income\"`) to get that concept's values across periods, or use `search_target=\"metrics\"` to list stored concepts and `\"facts\"` for reported values. `query` is a lexical text search across concept names, source labels, aliases, and segment names; narrow with `metric_class` (`financial`, `ifrs`, `segment`, `geography`, `product`, `kpi`, `apm`, `guidance`), `segment`, `report_type` (`annual`, `quarterly`, `half_year`, `10-K`, `10-Q`), `fiscal_year` + `fiscal_period` (`2025`, `Q3`), or `date_from`/`date_to`. Every result is a tree: company → metric class → concept → series → period. With `format=\"json\"`, filing/fact nodes retain the report URL and add a standardized `source_link`; exact-ticker metrics/facts misses may fall back to standardized statements with no filing evidence. `format=\"pretty\"` returns a rendered text tree instead of JSON. When a company reports the same concept twice in one filing, the second copy is labeled \"Reported line 2\" — never add it to the first. Results stop at `limit` (max 50) with `truncated: true`; narrow the filters rather than paging. For joins or aggregations across companies over the same archive, use `run_sql` on the `filings` schema (open to every account; tables in `references/data/schemas.md`). |\n| `search_earnings_transcripts` (`query`, `search_target?`, `ticker?`, `company_name?`, `quarter_filter?`, `claim_family?`, `section?`, `detail?`, `limit?`) | Lexical (not semantic) retrieval over atomic, quote-anchored earnings-call rows — never a raw transcript dump. Tickers go in `ticker` and company names in `company_name`; pass one of the two, never both (both ignore case; a name in `ticker` is still read as a name, and the response says so). `company_filter` is the old name of `ticker` and still works. For a non-empty `query`, strict websearch matches rank above an automatically broadened loose partial-match OR-of-tokens tier, so lower-ranked rows may match only some terms; trigram fallback runs only when full-text search returns no rows. Inspect every row for support and retry concise company-native vocabulary (`\"capital expenditure\"`, `\"capex\"`, segment names) before concluding lexical silence. For one-call notes, first use `search_target=\"coverage\"`, choose its exact returned `latest_period`, then browse `claims` with `query=\"\"`, that ticker + `quarter_filter`, `detail=true`, and a deliberate limit; fetch `pressure_points` with the same ticker and quarter. The browse is capped, not a promise of a complete call. Claim and pressure rows include `transcript_id`, `source_block_index`, `qa_turn_id`, and `source_link`; `source_link.source_label` names the company or ticker, fiscal period, and earnings call transcript, never the ingestion vendor. Quote only `verbatim_quote`, preserving any `[…]` omission marker exactly; `canonical_statement` is normalized, and neither `analyst_hypothesized` nor `mgmt_declined_to_confirm` is a management assertion. For filed actuals use `search_company_filings`; for formal targets use `sec_guidance`. Full target/filter reference and workflows: `references/report-patterns/earnings-intelligence.md`. |\n| `search_media_appearances` (`query`, `search_target?`, `company_filter?`, `person?`, `sort?`, `appearance_type?`, `claim_family?`, `date_from?`, `date_to?`, `detail?`, `limit?`) | Deterministic, lexical retrieval over precomputed public-safe paraphrases of what executives said outside earnings calls; **no serving-time model** interprets or expands the query. Strict lexical FTS runs first, loose any-term FTS only when strict finds no candidates, and trigram fallback only when both FTS stages are empty. Prefer concise topical language and retry company-native synonyms before concluding silence. Targets are `search` (default claims + passages blend), `claims`, `passages`, `pressure_points`, `appearances`, and `coverage`. `sort=\"relevance\"` ranks lexical score before publication date; `sort=\"newest\"` ranks publication date before lexical score. `company_filter` accepts comma-separated primary tickers; `person` is a case-insensitive speaker-name substring; `appearance_type`, `claim_family`, inclusive `date_from`/`date_to`, `detail`, and `limit` provide further narrowing. Dates are the video's publication/upload date, not necessarily its recording/event date. `claim_family` makes blended search claims-only, is invalid with `passages`, and requires matching claims for catalog targets. Structured finding rows expose `result_kind`, `canonical_paraphrase`, speaker/topic/video metadata, relevance, and a timestamped YouTube URL; `appearances` returns video-level metadata, attribution, matching-claim count, URL, and relevance; `coverage` returns company-level structured corpus counts and date/channel inventory. `detail=true` adds normalized claim/attribution fields to finding rows, but never raw transcript text or evidence spans; claim-only fields remain null on passages and detail does not change catalog rows. Never put `canonical_paraphrase` in quotation marks or claim it is verbatim; follow the timestamped source when exact wording or tone matters. Empty-query behavior and the complete workflow are in `references/report-patterns/media-intelligence.md`. |\n| `search_news` (`query?`, `tickers?`, `topic?`, `sources?`, `start_date?`, `end_date?`, `sort?`, `limit?`) | Search FactIQ's curated business-news feed — public RSS headlines and summaries from Bloomberg, the Financial Times, and the Wall Street Journal, plus India-macro (Zerodha Daily Brief, ET HealthWorld) and global-health sources (WHO, ECDC, CDC, STAT News, KFF), aggregated and processed by FactIQ so each article carries the listed companies it names (`{symbol, exchange, country}`) and an `analysis` block: searchable keywords, a geography, and an `angle` — one sentence on why the story matters to an investor. Company stories get analysis too, not just macro ones; only content with no business read at all (sports, lifestyle, celebrity) comes back with `analysis: null`. Results are headline + short publisher summary + link out, never full articles. `query` is lexical full-text over headline+summary — start with short concrete stems (`\"obesity drug\"`, `\"rate cut\"`); if a multi-word query matches nothing in full, the tool automatically retries matching ANY of the words with rare words ranked first, flagged as `meta.query_mode: \"any_term\"`, so one query attempt is usually enough. `tickers` matches share classes and cross-listings automatically (GOOG also finds GOOGL-tagged articles, TSM its Taiwan listing) — pass whichever symbol you know; most macro stories name no listed company, so zero ticker matches is a normal answer. `topic` is one of markets / economics / companies / technology / politics / world / energy / health / india / opinion — combined with a `query` it is a ranking preference (matching sections rank first, but strong matches from other sections still return, since stories often run outside their obvious feed); without a query it filters to the topic's feeds. `sort` is `\"latest\"` (default) or `\"relevance\"` (needs a query); `limit` 1–50 (default 20). Coverage is recent news (most feeds start late 2025) — treat it as a current-events lens, not an archive. |\n| `get_style_guides` (`guides`) | FactIQ house-style guides (`\"chart\"`, `\"report\"`, `\"sql\"`, `\"earnings\"`, or `\"all\"`). Use these for current style and sourcing rules. Fetch `\"earnings\"` before writing from `search_earnings_transcripts`. |\n\nWhen an answer uses a quote or filing-backed figure, place that row's provided\n`source_link.source_url` immediately beside the claim as a Markdown link whose\ntext is `source_link.source_label`. Do not replace that evidence label with an\ningestion vendor, make a second tool call solely to find a citation, reconstruct\na URL, or substitute a related press release. `source_precision=\"document\"` is\nnot exact context. If `source_url` is null, state that the direct link is\nunavailable and use the returned locator/document ID only as provenance\nmetadata.\n\nEvery row-returning tool (`run_sql`, `get_series`, `search_company_filings`,\n`search_earnings_transcripts`, `search_media_appearances`) returns **at most 50\nrows**, but the remedy is tool-specific:\n\n- For `run_sql`, aggregate to the needed grain; for a long `get_series`\n result, use `from_year` / `to_year`. See **Context budget** below.\n- For earnings, narrow by ticker, exact fiscal quarter, target, family, and\n (for claims) section. Synthesize multiple bounded calls and disclose when a\n 50-row result may be incomplete. Never query the gated `transcripts` schema\n with `run_sql`, request a raw transcript, or assume pagination exists.\n- For media, narrow by ticker, person, dates, target, appearance type, and\n claim family as supported by the live tool contract; use bounded searches\n rather than SQL or an assumed pagination/full-transcript path.\n\nThere is no universal \"give me everything\" option, by design.\n\n#### Earnings target/filter quick reference\n\n| Target | Use and applicable arguments |\n|---|---|\n| `claims` | Lexical search or empty browse; company, exact quarter, family (primary or secondary), claims-only section, detail, limit |\n| `pressure_points` | Lexical search or empty browse; company, exact quarter, linked family, detail, limit. `section` is ignored because these rows are Q&A |\n| `disclosure_profile` | Direct lookup by the first `ticker` value, the first ticker resolved from `company_name`, or `query`; not text or quarter search; other filters, detail, and limit are ignored |\n| `coverage` | Company inventory and limit; query, quarter, family, section, and detail do not narrow it |\n\nCanonical call patterns:\n\n- **Latest-call note:** `coverage` for one ticker → read `latest_period` →\n empty-query `claims` with that ticker + exact `quarter_filter`, `detail=true`,\n deliberate limit → `pressure_points` with the same ticker + quarter.\n- **Cross-company theme:** check coverage, then run the same concise query and\n synonym sweep separately for each ticker + exact comparable quarter; inspect\n partial-term rows before merging them.\n- **Disclosure habits:** call `disclosure_profile` with one ticker; do not add a\n quarter or treat the ticker as a theme query.\n\nQuote only `verbatim_quote`; use `canonical_statement` unquoted. A `[…]`\ninside `verbatim_quote` marks omitted transcript sentences between\nnon-adjacent evidence spans — preserve it exactly when quoting, and never\npresent the text on either side of it as one continuous statement. Treat\n`analyst_hypothesized` as the analyst’s framing and\n`mgmt_declined_to_confirm` as a refusal. Keep spoken call claims, formal\n`sec_guidance` targets, and filed actuals as separate source classes. Put\nthe row's provided source URL beside every quote you use, with the provided\nsource label as link text; if it is null, say the link is unavailable instead\nof searching for or inventing one.\n\n#### Media target/filter quick reference\n\n| Target | Use and empty-query behavior |\n|---|---|\n| `search` | Default blend of high-signal claims and broad passage cards. Empty query browses recent high-signal claims only, without generic passages |\n| `claims` | Structured, decision-relevant executive claims. Empty query browses recent claims |\n| `passages` | Broader substantive topics not promoted to claims. Empty query browses recent passage cards |\n| `pressure_points` | Stored refusal / declined-to-confirm rows, not a complete interviewer-Q&A map. Empty query browses recent refusals |\n| `appearances` | Video-level title, channel, publication date, type, attribution, claim-count, URL, and relevance rows. Empty query browses the catalog |\n| `coverage` | Company-level structured corpus inventory: appearance/claim counts, date span, covered channels, and attribution status. Empty query returns the inventory |\n\nAll targets accept `company_filter`, `person`,\n`appearance_type`, `claim_family` where compatible, publication-date\n`date_from`/`date_to`, and `limit`; finding targets also support `detail`.\n`claim_family` suppresses passage cards in `search` and cannot be combined\nwith `passages`. Catalog rows are not expanded by `detail=true`.\n\nStart with `coverage` before absence claims. Use `search` plus\n`sort=\"relevance\"` for a theme sweep, then drill into `claims` and\n`passages`. Use explicit `sort=\"newest\"` plus date filters for a timeline.\nIf a bounded result reaches 50 rows, narrow by ticker, person, target,\nappearance type, claim family, or date window; never query the gated\n`transcripts` schema, assume pagination, or ask for a full transcript.\n\nMedia findings are sourced paraphrases. Attribute person, company/ticker when\navailable, publication date, title/channel, and the timestamped link.\n`canonical_paraphrase` must stay outside quotation marks. Verify the linked\nsource independently when exact wording or tone is material. For coverage,\ntheme sweeps, timelines, cross-company work, and media-vs-earnings comparison,\nread `references/report-patterns/media-intelligence.md` before searching.\n\n\n### Feedback\n\n| Tool | Purpose |\n|---|---|\n| `send_feedback` (`message`, `category?`) | Report a problem to the FactIQ team: `category` is `\"data_issue\"` (a value that contradicts the official source, wrong units/scale, duplicated or missing periods), `\"tool_error\"` (a tool that errors or returns malformed results), `\"missing_data\"` (advertised but empty, or coverage ends too early), or `\"other\"`. Returns an acknowledgment. |\n\nCall this when a tool result looks broken. Write one short, specific message with the concrete\nidentifiers (schema, `dataset_code` / `series_id`, the SQL you ran, expected\nvs. observed, the official source's value or URL if you have one). Don't\ninclude the user's personal details or your conversation. It's one-way — the\nteam reviews every report, but nothing comes back — so file it and continue\nwith the task; never block on it.\n\n### Terminal charts — `term_chart.py`\n\n`term_chart.py` prints local ANSI/ASCII previews from FactIQ chart objects. It\nnever calls FactIQ. Build the ChartSpec from fetched data, save it to JSON, and\nrender it:\n\n```bash\npython3 \"{plugin_root}/scripts/term_chart.py\" render --spec /tmp/factiq-chart.json --width 80 --charset ascii --color auto\n```\n\nFor a report, save the report object or a wrapper such as\n`{\"question\": \"...\", \"report\": {...}}` to JSON, then render its charts:\n\n```bash\npython3 \"{plugin_root}/scripts/term_chart.py\" report --report /tmp/factiq-report.json --width 80 --charset ascii --color auto\n```\n\nAfter `term_chart.py` renders, paste the preview verbatim into your reply inside\na triple-backtick code block and provide the saved JSON path.\n\nSupported terminal renderers:\n\n| Renderer | Use when |\n|---|---|\n| `bar` | Categorical comparisons and short ranked lists |\n| `line` | Time-series trends (one or more series) |\n| `table` | Fallback for unsupported chart types or dense data |\n\nUseful options:\n\n| Option | Purpose |\n|---|---|\n| `--type auto\\|bar\\|line\\|table` | Pick the terminal renderer; `auto` maps from `ChartSpec.type` |\n| `--width 80` / `--width auto` | Fixed width by default; `auto` reads the terminal size |\n| `--height N` | Line-chart plot height |\n| `--charset ascii\\|unicode-block` | Strict ASCII or denser Unicode block glyphs |\n| `--color auto\\|always\\|never` | ANSI color control; `auto` respects TTY, `NO_COLOR`, and `TERM=dumb` |\n| `--max-charts N` | Report previews only: cap the number of rendered charts; `0` means all |\n| `--out FILE` | Also save the rendered text |\n\nBecause agents often capture command output instead of streaming it directly to\nthe user's terminal, use `--charset ascii --color never` for previews you paste\ninto the final answer. Use ANSI color for real terminal stdout or saved `.ansi`\npreviews.\n\n## Orchestration workflow\n\n0. **Interview before major forks.** If the request is broad, vague, or about\n to become a high-commitment workflow — especially a detailed report or one\n that could follow multiple scopes — interview the user before fetching data\n or spawning research subagents. Read\n `references/report-patterns/interview-step.md` and ask only the few choices\n that would materially change the work: detail level, audience, user context\n or hypothesis, priority lens, required/excluded entities, and time window.\n Pass the answers into all downstream research and assembler prompts as hard\n context. Skip the interview for direct answers, narrow quick charts, or when\n the user already gave clear scope, audience, and detail level. If the user\n does not answer, proceed with the defaults in the interview guide.\n1. **Catalog first.** Call `get_data_catalog` once to get the compact\n per-schema index and the table DDL. It tells you what each schema covers,\n not every dataset. Skip schemas under `schemas_without_data`. (You rarely\n need `full=true`; use `describe_dataset` for detail on one dataset.)\n2. **Find datasets, then drill in.** Call `search_datasets` to rank datasets\n across all schemas by keyword — the primary discovery step. Survey every\n schema that could be relevant before committing: for India check both\n `mospi` and `rbi`; for the US check `bls`, `bea`, `census`; energy means\n `eia`. Once a dataset looks right, `describe_dataset` for its dimensions and\n example series, then find the exact series with `search_series` (substring —\n prefer short stems like `rare`, not `rare earth`) or exploration SQL\n (`run_sql` with `explore=true`) on the `series` and `dimensions` tables.\n For multi-source stories, actually fetch data from 2+ schemas.\n Satellite-derived series live in two schemas: `portwatch` (daily shipping —\n chokepoints, ports, country trade estimates) and `satellite` (nighttime\n lights by state, lake/reservoir water levels) — see\n `references/data/schemas.md` for routing and\n `references/data/satellite.md` for the on-demand geo tool. Fire detections\n live in a third schema, `nasa_fires`, which holds raw detections rather than\n series and is shaped unlike the others — read its section in\n `references/data/schemas.md` before writing SQL against it.\n\n Eurostat Comext is the exception: country schemas contain millions of\n series, so do not explore their `series` or `dimensions` tables by text or\n dimension value. Read the **Eurostat Comext country schemas** section in\n `references/data/sql-guide.md`; it uses the small product lookup table and\n exact indexed series IDs.\n\n The IMF replaces each forecast in place, but the `imf` schema also keeps the\n recent earlier releases of the World Economic Outlook, the Fiscal Monitor,\n the Regional Economic Outlooks and COFER. Use them for any question about how\n a forecast has been revised. The plain series id is always the newest\n release; an earlier one is the same id with the year and month appended\n (`WEO_IND.NGDPD.A_2025OCT`), lives in a dataset whose code ends in\n `_vintages`, and carries a `release` dimension. See **IMF past releases** in\n `references/data/schemas.md`.\n\n **Domain report patterns.** If the question is broad and analytical —\n policy, trade, revenue, investment analysis, \"what's driving X\" — read\n `references/report-patterns/README.md` **before fetching**. It teaches the\n dialectical method every report follows (thesis: the headline reading;\n antithesis: the strongest contradiction, fetched, not footnoted;\n synthesis: one claim that explains both) and routes covered domains\n (bilateral trade, bilateral economic policy, monetary policy,\n fiscal-policy revenue, business formation) to a playbook of that domain's\n canonical antitheses with ready SQL. For domains without a playbook, apply\n the method directly. Either way it changes what you fetch, not just how\n you write it up. The interview step does not replace this method: it sets\n the user's preferred scope and audience first, then the report-pattern\n method determines the thesis, antithesis, synthesis, and data work inside\n that scope.\n\n For report-mode questions covering multiple topics, companies, or data\n sources, consider decomposing the research into parallel subagents — see\n **Subagent orchestration** below.\n\n3. **Fetch in batches.** Once you know which series you need, issue the fetch\n calls together (multiple tool calls in one turn). Use `get_series` for 1–2\n known ids; `run_sql` with a CASE-WHEN pivot for 3+ series or joins. Keep\n results inside the 50-row cap — aggregate in SQL to the granularity a chart\n actually needs. For report tables, pick row granularity to fit the window\n (see the granularity note in `references/output/report-spec.md`).\n4. **Compute deterministically.** For YoY/YTD growth, shares, rebasing, or\n merging compatible results, save the fetched `columns` and `results` as a\n local FactIQ payload and use `series_math.py`. Never inspect Claude/Codex\n transcripts or session directories to recover tool results. For other\n metrics such as per-capita values or custom ratios, write a small local\n Python calculation on the fetched values. There is no server-side code\n interpreter in this loop.\n Year-over-year is the same period one year earlier, **matched by date**,\n never by row position: for one series use `get_series` with\n `transform=\"yoy_pct\"` (or `\"yoy_diff\"` for a rate); for several series or a\n merged table use `series_math.py yoy`; in SQL join on the calendar month\n (`date_trunc('month', prior.time) = date_trunc('month', cur.time) - interval '1 year'`\n — the stored day of the month varies by source, so never compare exact\n dates), never `LAG(value, 12)` (see the trap in\n `references/data/sql-guide.md`). When a tool result carries a\n `coverage_note`, the rows skip the periods in `missing_periods`: name them\n in the answer, do not interpolate, and label any aggregate that spans them\n as partial (\"Q4 2025 average of two months\").\n5. **Recent market data.** The DB lags for very recent market/price data — use\n `get_market_data` for current quotes, commodities, and FX. For what the\n news is saying about a company, sector, or economy right now, use\n `search_news` — each business article carries keywords, a geography, and a\n one-sentence investor angle, plus the tickers it names; chase a company\n story with `get_market_data` or `search_earnings_transcripts`, and a macro\n story with `search_series`/`run_sql`.\n6. **Satellite signals.** For crop burning, wildfires, air-quality-based\n activity (NO2/SO2/CO), smoke and dust plumes, crop condition (NDVI),\n monsoon rainfall, heatwaves, or agricultural drought — where satellite\n observation runs ahead of official statistics — use `get_geo_data`. Read\n `references/data/satellite.md` first: it covers the ten datasets, region\n syntax, the window budget (50 intervals, 200 for fires), the `grid` mode for\n mapping where a signal sits (fires, NDVI, air quality), the fires-only\n `points`, `seasons`, `resolution`, and `include_flares` controls, cloud-cover\n caveats, and required attribution. Fires are the one dataset FactIQ stores\n itself — every detection since 2012 — so a fourteen-year seasonal comparison\n is one call. Satellite data complements warehouse series; prefer curated\n series where both exist.\n7. **Answer or render.** Direct-answer mode: reply with one sentence\n that states the number, period, and source. Quick-chart mode: build a\n ChartSpec from wide-format data (see `references/output/chart-spec.md`;\n required keys are `title`, `type`, `xField`, `series`, `data`, and a y-axis\n label goes in `yAxisLabel`), save it to JSON, run `term_chart.py render`, and paste the preview into a fenced\n code block. Report mode: build and save a report object (see\n `references/output/report-spec.md`), run `term_chart.py report`, and return\n the findings, local JSON path, and terminal previews.\n\n## Subagent orchestration\n\nFor report-mode questions that span multiple distinct topics, companies, or data\nsources, decompose the work into parallel subagents. This does two things:\neach research thread gets a full, focused context instead of competing for\nattention in one serial pass, and the report-assembly step gets the spec\nloaded directly in its prompt so it never guesses at field names.\n\nBefore spawning subagents for a broad or underspecified request, run the\ninterview described in\n`references/report-patterns/interview-step.md` unless the user already gave\nclear scope, detail level, audience, and priority lens. Include the interview\nanswers in every research-agent prompt and in the report-assembler prompt so\nthe final artifact reflects the user's context instead of only the generic\nversion of the question.\n\n### The interview runs in the main context\n\nRun the interview yourself, in the main conversation — a background subagent\ncannot put questions to the user. Its job is to clarify the decision,\naudience, scope, output shape, and success criteria and produce a compact\nbrief before any data is fetched or chart schemas are chosen. Research\nsubagents run only after the brief and the relevant report pattern are known.\n\n**Do NOT use subagents** for quick-chart mode or single-topic questions — the\noverhead is not worth it. The decision point is right after step 2 of the\norchestration workflow: once you have done the catalog lookup and initial\ndataset discovery, you know whether the question decomposes into 2+ independent\nresearch threads. If it does, fan out.\n\n### Research subagents\n\nSpawn one Agent call per research thread. Each agent inherits the skill's\nFactIQ MCP tools, so it can discover, fetch, and compute on its own. Give each\nagent a tightly scoped prompt and tell it to return structured findings — not\nprose and not a final artifact.\n\nAgent prompt template (adapt the specifics per thread):\n\n```\nYou are a FactIQ research agent. Answer one sub-question and return structured\nfindings only. Do not assemble the final chart or report.\n\nSub-question: {sub_question}\n\nRelevant schemas/datasets (from the parent's catalog step): {hints}\n\nConstraints: every tool result is capped at 50 rows, so aggregate in SQL to\nthe grain the finding needs; compute derived metrics (YoY, ratios, indices)\nyourself from the fetched values. Year-over-year is the same period one year\nearlier matched by date (get_series with transform=\"yoy_pct\", series_math.py\nyoy, or a SQL join on date_trunc('month', time)), never a 12-row offset or an\nexact-date comparison. Report any\ncoverage_note / missing_periods from the tool results in your findings.\n\nReturn your findings as a structured block:\n\nFINDINGS:\n- sub_question: (echo it back)\n- series_used: [{schema, series_id, title}, ...]\n- sql_queries: [the exact SQL you ran, formatted multi-line]\n- data: [{columns: [...], rows: [...]}, ...] — the actual fetched/computed values\n- key_insights: [1-3 sentences stating what the data shows, with numbers]\n- chart_suggestion: {chart_type, title, x_column, y_columns, units}\n```\n\nLaunch the agents in parallel — multiple subagent calls in one turn, each\nwith its own research prompt and a short name such as\n`research-supply-chain`, `research-pricing`, `research-demand`.\n\nEach agent runs independently and returns its findings block. Wait for all of\nthem before proceeding to assembly.\n\n### Report assembler subagent\n\nAfter all research is complete, spawn a single report-assembler agent. Its\nprompt must contain two things: (1) the full content of `references/output/report-spec.md`,\nso the assembler has the spec without needing the plugin path, and\n(2) all the research findings from the previous step.\n\nBefore spawning the assembler, read `references/output/report-spec.md` yourself with\nthe Read tool. Then embed its entire content in the assembler's prompt.\n\nAgent prompt template:\n\n```\nYou are a FactIQ report assembler. Build a complete report object, save it, and\nrender terminal previews for its charts. Do not do data discovery or fetching;\nall data is provided below.\n\nUSER QUESTION: {original_question}\n\n=== REPORT SPEC (from references/output/report-spec.md) ===\n{paste the full content of references/output/report-spec.md here}\n=== END REPORT SPEC ===\n\n=== RESEARCH FINDINGS ===\n{paste all findings blocks from the research agents, labeled by thread}\n=== END RESEARCH FINDINGS ===\n\nInstructions:\n1. Design 2-5 sections. Each section makes one claim its chart(s) prove.\n2. Chart titles state the finding with numbers, not the topic.\n3. Narratives are plain text — no markdown formatting.\n4. Every chart must have columns, data (from the findings above), x_column,\n y_columns (for line/bar), sources, and lineage.\n5. Lineage code must be formatted multi-line SQL/Python with real newlines.\n series_refs must list every series the step used.\n6. Save the full report object to JSON and run:\n `python3 {plugin_root}/scripts/term_chart.py report --report <json-file> --charset ascii --color never`\n7. Return the JSON path and paste the terminal previews into the reply inside a\n triple-backtick code block.\n```\n\nLaunch the assembler as one subagent (name it `report-assembler`) with the\nspec-plus-findings prompt.\n\nThe assembler has the full spec in context, so it builds the report object,\nsaves the JSON, and returns the local path plus terminal previews.\n\n### Example decomposition\n\nQuestion: \"How is the US EV market evolving — supply chain, pricing, and demand?\"\n\nAfter step 2 (catalog + discovery), you identify three independent threads:\n\n| Thread | Sub-question | Schemas |\n|---|---|---|\n| Supply chain | What does US EV battery/component production look like? | `census`, `bea` |\n| Pricing | How have EV prices and average selling prices changed? | `bls` (CPI), market data |\n| Consumer demand | What are EV sales and registration trends? | `bts`, `bea`, market data |\n\nSpawn three research agents in parallel. When all return, spawn one assembler\nagent with the spec and all three findings blocks. The assembler builds a\n3-section report, saves it, renders terminal previews, and returns both.\n\n### When NOT to use subagents\n\n- Quick-chart mode (single metric, single chart).\n- Single-topic questions even in report mode (\"How has US unemployment evolved\n since 2020?\" — one thread, no decomposition needed).\n\nFor these cases, do the research and build the output in the main context.\n\n## Detailed reports\n\nA report is a structured local research output: a bulleted summary, sections\nthat pair narrative with charts, and methodology notes. Author every chart row\nand narrative claim from data fetched in this session. The JSON format and a\nworked example are in `references/output/report-spec.md`. For reliable assembly,\nload that full file into a dedicated report-assembler subagent.\n\nGround rules:\n\n- **2–5 sections, 1–2 charts each** is the normal size. The format allows up to 12\n sections, 16 charts). Each section should make one claim its charts prove.\n- **Chart titles state the finding** (\"Health care added 652k jobs in 2024 —\n triple tech's losses\"), not the topic (\"Jobs by sector\").\n- **Narratives are plain text.** Keep them short and direct.\n- **Cite sources and lineage.** Every chart must identify the datasets and the\n exact SQL or computation used. Format SQL and Python with real newlines. List\n every series used in `series_refs`.\n- **Do not pad.** If the data only supports one chart, build a quick chart\n instead of inflating a report.\n- **Broad analytical questions get the dialectic.** Follow the\n thesis → antithesis → synthesis method in\n `references/report-patterns/README.md`: sections that only restate the\n headline reading are an unfinished report. Covered domains (bilateral\n trade, bilateral economic policy, monetary policy, fiscal-policy revenue,\n business formation) must additionally meet the required coverage in the\n playbook the README routes to — do not reduce them to the easiest single\n chart.\n\nCheck the report object against `references/output/report-spec.md`, save it to\nJSON, and render it with `term_chart.py report`. Return the report findings, the\nlocal JSON path, and visible terminal previews.\n\n## Context budget — the 50-row cap\n\nEvery row-returning MCP tool (`run_sql`, `get_series`) returns **at most 50\nrows**, and there is no \"give me everything\" option — by design. The cap keeps\nresults context-sized, so you do **not** stage data to disk to protect your\ncontext; you take the tool result directly.\n\nThere is one exception. In the `nasa_fires` schema a row is a single satellite\nfire detection, which belongs to no series and carries no value to average, so\nthe rows themselves can be the answer. There `run_sql` accepts `page` and walks\nthe result 50 rows at a time. Give the query an `ORDER BY`, or the pages will\nnot line up. No other schema accepts `page`.\n\nWhen a result comes back `\"truncated\": true`, there is more data and your move\nis to **aggregate or compute it in SQL**, not to try to fetch the raw rows:\n\n- Roll a long daily/monthly series up with `GROUP BY date_trunc('month', time)`\n (or quarter/year) — a chart wants a few hundred points at most, and 50\n aggregated points usually says everything.\n- Return a SUM / AVG / rank / ratio instead of the underlying rows.\n- For one series, window it with `get_series(..., from_year=, to_year=)`, or\n make a few windowed calls and stitch them.\n\nWhatever you chart or report has to be the aggregated result you bring back —\nwhich is also all it needs.\n\n## Errors and limits\n\n- **MCP tool unavailable / auth error** — the FactIQ MCP is not connected. Tell\n the user to authorize it (Claude Code: `/mcp` → factiq; Codex:\n `codex mcp login factiq`).\n- **429** — either the 1 request/second rate limit or the monthly tool-call\n quota. The error states when it resets. Do not re-fetch data you already have.\n- **403** — that schema is admin-restricted for this account; drop it.\n- **SQL errors** come back in the tool result as an `error` (syntax errors,\n timeouts, bad column names). Revise the query and rerun.\n- **Zero rows** — your filter was too narrow. Broaden it yourself (see\n `references/data/sql-guide.md`). `auto_retry=true` opts into a server-side LLM\n reviser, but you can usually revise better and cheaper yourself.\n- **`coverage_note` on a result** — not an error: the rows skip the periods in\n `missing_periods` (a month the source never published, or one the query\n window cut off). Any period-over-period figure built with a fixed row\n offset (`LAG(value, 12)`, `shift(12)`, \"the row 12 back\") over those rows\n is wrong for the 12 rows after each gap (a forward offset: the 12 rows\n before it). Recompute by matching dates, name the missing periods in the\n answer, and label partial aggregates as partial.\n- **SQL timeout** — statements are capped at 30s. Filter on indexed columns\n (`series_id`, `dataset_code`) instead of scanning titles, and never\n pattern-match `series_id` on `data_points` — resolve ids from `series` first\n (see the pitfall in `references/data/sql-guide.md`). For `eu_comext_*`, do\n not retry a dimension scan; use `eu_comext_lookup.product_codes` and exact\n IDs as described in the Comext section of that guide.\n- **Anything that looks broken on FactIQ's side** — a value that contradicts\n the official source, wrong units, missing periods, an advertised dataset\n that returns nothing, a tool that keeps erroring — report it with\n `send_feedback` (see **Feedback** above), then work around it and continue.\n The FactIQ team reviews every report and fixes what it can.\n\n## References\n\n**`references/data/`** — the data layer:\n\n- `schemas.md` — what lives in each schema. The `get_data_catalog` tool is the\n live, authoritative version; `search_datasets` / `describe_dataset` drill\n into individual datasets on demand.\n- `sql-guide.md` — table structure, query idioms, pitfalls (frequency\n literals, national vs sub-national, pivots, tabular data).\n- `satellite.md` — the `get_geo_data` satellite tool: datasets and their\n economic reading, region syntax and coverage, window budgeting, the\n spatial `grid` mode, the fires-only `points` and `seasons` modes and the\n `resolution` / `include_flares` controls, cloud/quality caveats,\n attribution requirements.\n\n**`references/output/`** — local output formats:\n\n- `chart-spec.md` — ChartSpec format, chart-type selection, terminal rendering, and a worked example.\n- `report-spec.md` — report JSON format: sections, per-chart fields,\n sources, lineage, limits, and a worked example.\n\n**`references/report-patterns/`** — how to think about broad analytical\nquestions. Start at `report-patterns/interview-step.md` when the request is\nvague or high-commitment; it defines the interview that clarifies scope and\naudience before data work. Then read\n`report-patterns/README.md`: it teaches the dialectical method (thesis →\nantithesis → synthesis) that every report follows and routes covered domains\n(bilateral trade, bilateral economic policy, monetary policy, fiscal-policy\nrevenue, business formation, and any added later) to a playbook of that\ndomain's canonical antitheses with ready SQL. For uncovered domains —\ninvestment analysis, general macro — the README shows how to apply the method\ndirectly.\n"
}SHA-256: 4ab06e198502a14e12786893442bf2deff7c3f036889faabd48754fc19f9c113