{
  "openapi": "3.1.0",
  "info": {
    "title": "EU Trade Explorer",
    "description": "HTTP API for the EU Trade Explorer, serving Eurostat Comext (customs trade) and\nPRODCOM (industrial production) statistics for the Combined Nomenclature.\n\n**Request model.** Every analytical endpoint takes the same `DashboardQuery`\nJSON body (product code(s), reporter, partner set, period window, frequency).\nEndpoint-specific options (`entity_level`, `flow`, `partner`, detection\nthresholds, ...) are passed as query-string parameters.\n\n**Streaming responses.** The analytical endpoints under `/api/descriptive`,\n`/api/concentration`, `/api/volatility` and `/api/vulnerability` do not\nreturn a single JSON object. They stream newline-delimited JSON\n(`application/x-ndjson`): zero or more `{\"type\": \"progress\", \"message\": ...}`\nlines while Eurostat data loads, then exactly one terminal frame — either\n`{\"type\": \"result\", \"data\": ...}` or `{\"type\": \"error\", \"detail\": ..., \"status\": ...}`.\n\n**Access.** The Overview endpoint and everything under `/api/reference` and\n`/api/auth` are public; all other analytical endpoints require a signed-in\nsession cookie. Requests are rate-limited per client.\n",
    "version": "1.0.0"
  },
  "paths": {
    "/api/reference/countries": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Get Countries",
        "description": "Country reference data for the sidebar selectors.\n\nReturns the selectable reporters (EU, Euro area and the member states),\nthe named partner presets, the predefined reporter/partner groups, and a\ntable of per-language country-name translations so the frontend can\nrelabel everything synchronously when the user switches language without a\nsecond round-trip.",
        "operationId": "get_countries_api_reference_countries_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/api/reference/language": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Get Language",
        "description": "Detect the preferred UI language for a first-time visitor.\n\nResolution order: an explicit ``lang`` cookie wins; otherwise the\n``Accept-Language`` header is matched against the supported set; otherwise\nEnglish. The response reports both the chosen ``lang`` and the ``source``\nit came from ('cookie', 'accept-language' or 'default').",
        "operationId": "get_language_api_reference_language_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/api/reference/validate-code/{code}": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Validate Code",
        "description": "Validate and describe one or more Combined Nomenclature customs codes.\n\n**Single code** – behaves as before: normalises loose user input (spaces),\nresolves the full hierarchy of description levels for the code in *lang*,\nand returns ``{code, levels, product_name, has_subcodes}``, plus\n``interpreted_from`` when the input was reformatted.  The synthetic\n'TOTAL' aggregate short-circuits the nomenclature lookup.  Successful\nresponses are cacheable for a day.\n\n**Batch** – when the path segment contains commas the input is treated as a\ncomma-and-space-separated list of codes (e.g. ``73030010, 73030090``).\nThe input is validated against a strict regex before any processing\n(``^\\d{2,12}(\\s*,\\s*\\d{2,12})+$``) to reject non-digit characters.\nEach code is validated independently and the response is\n``{\"results\": [...]}`` where every item is either a normal success dict or\n``{\"code\": raw, \"error\": {\"status\": N, \"detail\": \"...\"}}``.  At most\n50 codes per request.\n\nError responses distinguish the cases the sidebar surfaces to the user:\n400 (too short / beyond 8-digit trade granularity), 404 (code or\ndescription not found, with the nearest valid parent), and 503 (the EU\nnomenclature SPARQL service is unavailable).",
        "operationId": "validate_code_api_reference_validate_code__code__get",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "en",
              "title": "Lang"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/reference/data-range": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Get Data Range",
        "description": "Return the period coverage available in the local Comext store for the\ngiven product code(s).\n\nUsed by the sidebar period picker so the picker reflects the actual\nrange of data on disk rather than offering unbounded date selection.\n\nResponse::\n\n    {\"data_min\": \"YYYY-MM\" | null, \"data_max\": \"YYYY-MM\" | null}\n\nBoth fields are ``null`` when nothing is cached yet for any of the\nrequested codes — the front-end then falls back to its broad\n“beginning-of-time” bounds.",
        "operationId": "get_data_range_api_reference_data_range_get",
        "parameters": [
          {
            "name": "products",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "title": "Products"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/reference/chapters": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Get Chapters",
        "description": "Return all top-level Combined Nomenclature chapters (2-digit codes).\n\nBootstraps the nomenclature browser's root level; the frontend then drills\ninto each chapter via ``/subtree/{code}``. The response is\n``{chapters: [{code, text}, ...]}`` in *lang*; results are cacheable for a\nday. Returns 503 when the nomenclature service is unavailable.",
        "operationId": "get_chapters_api_reference_chapters_get",
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "en",
              "title": "Lang"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/reference/subtree/{code}": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Get Subtree",
        "description": "Return the Combined Nomenclature sub-tree rooted at *code*.\n\nUsed to drill from a parent code into its children for the sub-product\ncomparison tabs. The response is the nested ``{code, text, children}`` tree\nin *lang*; results are cacheable for a day. Returns 400 for malformed codes\nand 503 when the nomenclature service is unavailable.",
        "operationId": "get_subtree_api_reference_subtree__code__get",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "en",
              "title": "Lang"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/reference/search": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Search Codes",
        "description": "Keyword/code search over a nomenclature for the sidebar typeahead.\n\nReturns a ranked list of ``{code, label, path}`` candidates.  Selecting one\nis handled client-side by the existing ``/validate-code`` endpoint.\n\nA dotted PRODCOM notation (``XX.XX`` or deeper) is auto-detected: instead\nof a keyword search, the PRODCOM→CN reverse mapping runs and the\ncorresponding CN codes are returned as the candidates.  Each carries a\n``via_prodcom`` provenance tag, and the response includes ``matched_prodcom``\n(always present for a well-formed PRODCOM code, even when no CN code maps,\nso the frontend can warn that there is no correspondence).",
        "operationId": "search_codes_api_reference_search_get",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100,
              "title": "Q"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "cn",
              "title": "Source"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "en",
              "title": "Lang"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/overview": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Overview",
        "description": "Headline figures for the Overview tab (public, no login required).\n\nStreams an NDJSON sequence of ``{\"type\": \"progress\"}`` status lines while\nthe data loads, followed by one ``{\"type\": \"result\", \"data\": ...}`` frame\n(or ``{\"type\": \"error\"}`` on failure). The result carries total import and\nexport quantities, values and weighted-average prices per period, the trade\nbalance, and a top-partner breakdown — everything the landing charts need\nfor the selected product and window.",
        "operationId": "get_overview_api_descriptive_overview_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/partner-detail": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Partner Detail",
        "description": "Per-partner import/export breakdown for the Partners tab.\n\nStreams NDJSON progress then a result frame. For each flow the result holds\nthe top-N trading partners ranked both by quantity and by value (so the\nfrontend can re-rank when the user flips the volume/value switch), each with\nits quantity, value and price time series, plus an optional 'Other' bucket.",
        "operationId": "get_partner_detail_api_descriptive_partner_detail_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/reporter-detail": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Reporter Detail",
        "description": "Per-EU-member-state import/export breakdown for the Reporters tab.\n\nThe reporter-side counterpart of ``/partner-detail``: streams NDJSON, then\nreturns each flow's top-N member states (by quantity and by value) with\ntheir quantity, value and price series. Aggregate reporters (EU, Euro area,\ngroups) are excluded from the ranking and the 'Other' bucket.",
        "operationId": "get_reporter_detail_api_descriptive_reporter_detail_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/map-data": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Map Data",
        "description": "Choropleth-ready aggregates for the map view.\n\nStreams NDJSON, then returns per-period totals (quantity, value, price) for\nboth imports and exports, keyed by GISCO country code on both the reporter\nand partner sides so the frontend can shade either map directly.",
        "operationId": "get_map_data_api_descriptive_map_data_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/top-entities": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Top Entities",
        "description": "Just the ordered list of top entities for a slice — no time series.\n\nA lightweight helper the frontend uses to populate entity pickers. Streams\nNDJSON, then returns the ranked entity names.",
        "operationId": "get_top_entities_api_descriptive_top_entities_post",
        "parameters": [
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to rank: 'partner' (trading partners) or 'reporter' (EU member states).",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to rank: 'partner' (trading partners) or 'reporter' (EU member states)."
          },
          {
            "name": "flow",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "Imports",
                "Exports"
              ],
              "type": "string",
              "description": "Trade flow to rank entities by.",
              "default": "Imports",
              "title": "Flow"
            },
            "description": "Trade flow to rank entities by."
          },
          {
            "name": "n",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 200,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Number of top entities to return. Defaults to the body's n_top when omitted.",
              "title": "N"
            },
            "description": "Number of top entities to return. Defaults to the body's n_top when omitted."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/production": {
      "post": {
        "tags": [
          "dashboard"
        ],
        "summary": "Get Production Series",
        "description": "EU27 and per-country PRODCOM production series — how each reporting\ncountry's output and production unit value evolve over time.\n\nProduction volumes carry no trade columns, so the per-country panel spans\nevery reporting country in DS-059358 (the EU member states plus EFTA,\ncandidate and other reporters), not only the EU-27.\n\nReturns four chart series per production basis: EU quantity, EU production\nunit value, quantity per country, and production unit value per country.\nCountry-level production is heavily confidentiality-suppressed, so\nsuppressed/absent values are returned as ``null`` rather than zero.  (For\nhow concentrated that production is across countries, see\n``/api/concentration/production``.)\n\nStreams NDJSON, then returns the production-series payload.  Use the\n``prodcom_code`` body field to pin a specific PRODCOM mapping.",
        "operationId": "get_production_series_api_descriptive_production_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/descriptive/product-compare": {
      "post": {
        "tags": [
          "products"
        ],
        "summary": "Get Product Compare",
        "description": "Sub-product comparison charts for the Cross-section view.\n\nFor a single parent code, expands into its CN children; for a multi-code\nrequest, compares the entered codes directly. Streams NDJSON, then returns\nper-subcode quantity, value and price series for both flows (top-N\nsub-products by volume), so each code is plotted as its own line rather\nthan being aggregated. A leaf code with no children returns ``leaf: true``.",
        "operationId": "get_product_compare_api_descriptive_product_compare_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/concentration/concentration": {
      "post": {
        "tags": [
          "concentration"
        ],
        "summary": "Get Concentration",
        "description": "Trade-concentration series using the Herfindahl-Hirschman Index (HHI).\n\nThe HHI measures how concentrated trade is across partners or EU member\nstates. For each period it sums the squared market shares of each entity,\nproducing a score from 0 (perfectly spread across many partners) to 10,000\n(a single partner holds 100% of the market). Higher means more dependent on\na handful of counterparties.\n\nStreams NDJSON, then returns per-flow HHI series plus the top-entity share\nbreakdown (top 8 by average value, the rest bundled as 'Others'). The\n``entity_level`` query param selects the question asked:\n\n- **partner** (default): how dependent is this reporter on a few trading\n  partners for its imports/exports?\n- **reporter**: for each of the top partners, is trade concentrated among a\n  few EU member states, or evenly distributed?",
        "operationId": "get_concentration_api_concentration_concentration_post",
        "parameters": [
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required.",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required."
          },
          {
            "name": "partner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Partner country name (English). Required when entity_level='reporter'.",
              "title": "Partner"
            },
            "description": "Partner country name (English). Required when entity_level='reporter'."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/concentration/concentration-compare": {
      "post": {
        "tags": [
          "concentration"
        ],
        "summary": "Get Concentration Compare",
        "description": "Partner-concentration (HHI) compared across sub-products.\n\nSame Herfindahl-Hirschman Index as ``/concentration`` (squared market\nshares per period, 0 = perfectly spread to 10,000 = a single partner holds\nthe whole market), but plotted as one HHI line per sub-product instead of\none per flow — so you can see which sub-products drive a parent code's\noverall concentration. Expands a single parent into its CN children (or\nuses the entered multi-code list), then returns per-subcode HHI series for\nvolume and value across both flows. Streams NDJSON.",
        "operationId": "get_concentration_compare_api_concentration_concentration_compare_post",
        "parameters": [
          {
            "name": "n_subcodes",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "description": "Maximum number of sub-products to plot as separate HHI lines.",
              "default": 8,
              "title": "N Subcodes"
            },
            "description": "Maximum number of sub-products to plot as separate HHI lines."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/concentration/concentration-map": {
      "post": {
        "tags": [
          "concentration"
        ],
        "summary": "Get Concentration Map",
        "description": "Cross-sectional trade-concentration (HHI) per country for one year — the\nchoropleth counterpart of ``/concentration``.\n\nTwo views, selected by the ``entity_level`` query param:\n\n- **partner** (default — reporter map): for every EU member state, the HHI\n  of its trade across partners.  How dependent is each European country on\n  a few trading partners for its imports/exports?\n- **reporter** (partner map): for every partner, the HHI of its trade\n  across EU member states.  How concentrated among a few member states is\n  trade with each non-EU partner?  Subject to the Rotterdam/Antwerp\n  entry-point distortion — imports are booked to the member state of\n  customs declaration, not of final consumption.\n\nStreams NDJSON, then returns per-flow, per-indicator (quantity/value) HHI\ndictionaries keyed by country, for the latest year present.  Override the\nyear with the ``year`` query param.",
        "operationId": "get_concentration_map_api_concentration_concentration_map_post",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 2100,
                  "minimum": 1988
                },
                {
                  "type": "null"
                }
              ],
              "description": "Override the year for the cross-sectional HHI snapshot. Defaults to the latest year with data.",
              "title": "Year"
            },
            "description": "Override the year for the cross-sectional HHI snapshot. Defaults to the latest year with data."
          },
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required.",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required."
          },
          {
            "name": "partner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Partner country name (English). Required when entity_level='reporter'.",
              "title": "Partner"
            },
            "description": "Partner country name (English). Required when entity_level='reporter'."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/concentration/specialisation": {
      "post": {
        "tags": [
          "concentration"
        ],
        "summary": "Get Specialisation",
        "description": "Intra-EU revealed symmetric comparative advantage (RSCA) per member\nstate — a Balassa index benchmarked against the EU instead of the world.\n\nFor each member state: is its share of intra-EU exports of the selected\nproduct larger than its share of intra-EU exports overall?  RSCA runs from\n−1 (under-specialised) through 0 (EU average) to +1 (strongly specialised),\nusing the Comext ``TOTAL`` aggregate as the all-products denominator.\nValue-based and intra-EU only.\n\n.. note::\n    Intra-EU flows are distorted by the Rotterdam/Antwerp quasi-transit\n    effect; treat single-market positioning with caution.\n\nStreams NDJSON, then returns per-reporter RSCA for the latest *full* year\npresent (override with the ``year`` query param). A year only counts as\nfull once the store holds all 12 months of the ``TOTAL`` aggregate for\nit — otherwise the most recent complete year is used instead, since Comext\ntypically doesn't settle a year's figures until well into the next one.",
        "operationId": "get_specialisation_api_concentration_specialisation_post",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 2100,
                  "minimum": 1988
                },
                {
                  "type": "null"
                }
              ],
              "description": "Override the year for the RSCA snapshot. Defaults to the latest full year with data.",
              "title": "Year"
            },
            "description": "Override the year for the RSCA snapshot. Defaults to the latest full year with data."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/concentration/production": {
      "post": {
        "tags": [
          "concentration"
        ],
        "summary": "Get Production Concentration",
        "description": "How concentrated production of a product is across the reporting\ncountries (PRODCOM).\n\nProduction volumes carry no trade columns, so this spans every reporting\ncountry in DS-059358 (the EU member states plus EFTA, candidate and other\nreporters), not only the EU-27 — it measures concentration across all of\nthem rather than within the EU.\n\nReturns the Herfindahl-Hirschman Index of PRODVAL across countries over\ntime (a single dominant producer pushes it toward 10,000) plus each\ncountry's production share.  Country-level production is heavily\nconfidentiality-suppressed, so suppressed/absent values are returned as\n``null`` rather than zero.  (For the underlying per-country output and unit-\nvalue series, see ``/api/descriptive/production``.)\n\nStreams NDJSON, then returns the production-concentration payload.  Use the\n``prodcom_code`` body field to pin a specific PRODCOM mapping.",
        "operationId": "get_production_concentration_api_concentration_production_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/volatility/volatility": {
      "post": {
        "tags": [
          "volatility"
        ],
        "summary": "Get Volatility",
        "description": "Coefficient-of-variation volatility for the Volatility tab.\n\nEach cell/bar is the coefficient of variation (CV = standard deviation\ndivided by the mean) for a single partner x product pair over the selected\nperiod. Inactive periods (zero quantity) are excluded so they do not distort\nthe result. A higher CV means the flow swings more relative to its typical\nlevel.\n\nStreams NDJSON, then returns per-entity CV bars (with drill-down series)\nand, unless ``include_heatmap=false``, an entity x sub-product CV heatmap.",
        "operationId": "get_volatility_api_volatility_volatility_post",
        "parameters": [
          {
            "name": "n_top_entities",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "description": "Number of top entities (partners or reporters) to include in the volatility bar chart.",
              "default": 15,
              "title": "N Top Entities"
            },
            "description": "Number of top entities (partners or reporters) to include in the volatility bar chart."
          },
          {
            "name": "n_top_products",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "description": "Maximum number of sub-products to include in the entity × product heatmap.",
              "default": 10,
              "title": "N Top Products"
            },
            "description": "Maximum number of sub-products to include in the entity × product heatmap."
          },
          {
            "name": "include_heatmap",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Whether to compute the entity × sub-product CV heatmap. Set to false to receive only the per-entity bar chart (faster).",
              "default": true,
              "title": "Include Heatmap"
            },
            "description": "Whether to compute the entity × sub-product CV heatmap. Set to false to receive only the per-entity bar chart (faster)."
          },
          {
            "name": "combos",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Comma-separated flow × indicator keys to compute in one request: imp-qty, imp-price, exp-qty, exp-price. When supplied the response is a dict keyed by combo; when absent the 'flow' and 'indicator' params select a single grid.",
              "title": "Combos"
            },
            "description": "Comma-separated flow × indicator keys to compute in one request: imp-qty, imp-price, exp-qty, exp-price. When supplied the response is a dict keyed by combo; when absent the 'flow' and 'indicator' params select a single grid."
          },
          {
            "name": "flow",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "Imports",
                "Exports"
              ],
              "type": "string",
              "description": "Trade flow for single-grid mode (ignored when 'combos' is set).",
              "default": "Imports",
              "title": "Flow"
            },
            "description": "Trade flow for single-grid mode (ignored when 'combos' is set)."
          },
          {
            "name": "indicator",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "quantity",
                "price"
              ],
              "type": "string",
              "description": "Measure for single-grid mode (ignored when 'combos' is set).",
              "default": "quantity",
              "title": "Indicator"
            },
            "description": "Measure for single-grid mode (ignored when 'combos' is set)."
          },
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required.",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required."
          },
          {
            "name": "partner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Partner country name (English). Required when entity_level='reporter'.",
              "title": "Partner"
            },
            "description": "Partner country name (English). Required when entity_level='reporter'."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/volatility/pattern-shift": {
      "post": {
        "tags": [
          "volatility"
        ],
        "summary": "Get Pattern Shift",
        "description": "Before/after trade-pattern shift around a split point (Pattern Shifts tab).\n\nCuts the window at ``midpoint`` (from the body) and, for each entity,\ncompares average quantity and price before vs. after. Streams NDJSON, then\nreturns scatter points (quantity-change % vs. price-change %) for partners\nand reporters across both flows, plus quadrant labels that read the sign of\neach change: top-right = demand-driven growth (up volume, up price),\ntop-left = supply constraint / monopoly risk (down volume, up price),\nbottom-right = dumping / oversupply risk (up volume, down price), and\nbottom-left = market contraction (down volume, down price).\n\nIntended workflow: note a country's usual volatility in the Volatility tab,\ncheck its detailed charts for significant volume or price variations, use\nthe Sudden volume changes / Rapid price changes tabs to pinpoint the dates\nwhere shocks are most pronounced, then pass one of those dates as\n``midpoint`` here.",
        "operationId": "get_pattern_shift_api_volatility_pattern_shift_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/volatility/supply-shocks": {
      "post": {
        "tags": [
          "volatility"
        ],
        "summary": "Get Volatility Supply Shocks",
        "description": "Detect abnormal collapses in trade volumes (Sudden volume changes tab).\n\nFor each partner/product pair with enough sustained activity, the algorithm\nflags periods where volume drops well below the pair's historical average.\nA shock is flagged when the average quantity during the disruption drops far\nenough below the normal volume that the standard-deviation gap exceeds a\nthreshold (2 sigma by default): below 2 sigma is an ordinary fluctuation,\n2-3 sigma is unusual, above 3 sigma is highly abnormal.\n\nStreams NDJSON, then returns ranked shock events per flow — each with its\ntimeline phases (baseline, decline, disruption, recovery, ...), magnitude,\nabnormality score and the underlying series.\n\nDetection is tunable via query params:\n\n- ``min_abnormality`` — how statistically unusual the crash must be, in\n  sigma (default 2.0).\n- ``relative_threshold`` — minimum drop relative to peak to count as a\n  collapse (default 0.10).\n- ``min_active_years`` — consecutive years of regular trade required before\n  a collapse can be flagged (default 2).\n- ``cumulative_share`` — only the top trading partners that together account\n  for this share of total trade are analysed (default 0.80).\n- ``max_tolerated_gap`` — consecutive quiet periods allowed within an\n  otherwise stable trading relationship (default 2).\n\nPlus ``entity_level`` ('partner' | 'reporter'; the latter requires\n``partner``). To make the function detect more shocks, set a low minimum\ndeviation, encompass a large cumulative share of total trade, extend\ntolerated gaps to the maximum, reduce the number of stable years to the\nminimum, or treat any small drop (even as little as 5%) as a shock marker.",
        "operationId": "get_volatility_supply_shocks_api_volatility_supply_shocks_post",
        "parameters": [
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required.",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required."
          },
          {
            "name": "partner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Partner country name (English). Required when entity_level='reporter'.",
              "title": "Partner"
            },
            "description": "Partner country name (English). Required when entity_level='reporter'."
          },
          {
            "name": "min_abnormality",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 10.0,
              "minimum": 0.1,
              "description": "Minimum statistical abnormality of the volume crash, in sigma. Lower values flag more (weaker) shocks; higher values flag only extreme events. Default 2.0.",
              "default": 2.0,
              "title": "Min Abnormality"
            },
            "description": "Minimum statistical abnormality of the volume crash, in sigma. Lower values flag more (weaker) shocks; higher values flag only extreme events. Default 2.0."
          },
          {
            "name": "relative_threshold",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 0.95,
              "minimum": 0.01,
              "description": "Minimum drop relative to peak volume to count as a collapse (e.g. 0.10 = 10% drop). Default 0.10.",
              "default": 0.1,
              "title": "Relative Threshold"
            },
            "description": "Minimum drop relative to peak volume to count as a collapse (e.g. 0.10 = 10% drop). Default 0.10."
          },
          {
            "name": "min_active_years",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 5,
              "minimum": 1,
              "description": "Consecutive years of regular trade required before a collapse can be flagged. Default 2.",
              "default": 2,
              "title": "Min Active Years"
            },
            "description": "Consecutive years of regular trade required before a collapse can be flagged. Default 2."
          },
          {
            "name": "cumulative_share",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 1.0,
              "minimum": 0.3,
              "description": "Only the top trading partners that together account for this share of total trade are analysed (e.g. 0.80 = top 80%). Default 0.80.",
              "default": 0.8,
              "title": "Cumulative Share"
            },
            "description": "Only the top trading partners that together account for this share of total trade are analysed (e.g. 0.80 = top 80%). Default 0.80."
          },
          {
            "name": "max_tolerated_gap",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 6,
              "minimum": 1,
              "description": "Consecutive quiet periods allowed within an otherwise stable trading relationship. Default 2.",
              "default": 2,
              "title": "Max Tolerated Gap"
            },
            "description": "Consecutive quiet periods allowed within an otherwise stable trading relationship. Default 2."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/volatility/price-shocks": {
      "post": {
        "tags": [
          "volatility"
        ],
        "summary": "Get Volatility Price Shocks",
        "description": "Detect sudden price regime shifts (Rapid price changes tab).\n\nDetects sudden price variations while accounting for the usual volatility of\nthose prices within each flow. The algorithm compares each period's price\nagainst a retrospective reference window, then flags regime changes whose\namplitude exceeds a configurable multiple of the coefficient of variation —\nso naturally volatile flows aren't over-flagged.\n\nStreams NDJSON, then returns ranked shock events per flow with their\ntimeline phases (price spike/drop, volatile trade, return to baseline, ...),\nmagnitude and series.\n\nDetection is tunable via query params:\n\n- ``min_abnormality`` — how statistically unusual the price shift must be\n  compared to baseline volatility (default 2.0).\n- ``lookback_years`` — years before the shock used to establish the\n  coefficient of variation (default 2).\n- ``cumulative_share`` — only the top trading partners that together account\n  for this share of total trade are analysed (default 0.80).\n- ``max_shocks_per_pair`` — number of non-overlapping shocks kept per\n  trading partner (default 1).\n\nPlus ``entity_level`` ('partner' | 'reporter'; the latter requires\n``partner``). To surface more (weaker) shocks, lower ``min_abnormality``,\nwiden ``cumulative_share`` toward the whole market, and raise\n``max_shocks_per_pair``.",
        "operationId": "get_volatility_price_shocks_api_volatility_price_shocks_post",
        "parameters": [
          {
            "name": "entity_level",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "partner",
                "reporter"
              ],
              "type": "string",
              "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required.",
              "default": "partner",
              "title": "Entity Level"
            },
            "description": "Dimension to analyse. 'partner' — how concentrated/volatile is the reporter's trade across its trading partners? 'reporter' — how concentrated/volatile is a given partner's trade across EU member states? When 'reporter', the 'partner' query param is required."
          },
          {
            "name": "partner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Partner country name (English). Required when entity_level='reporter'.",
              "title": "Partner"
            },
            "description": "Partner country name (English). Required when entity_level='reporter'."
          },
          {
            "name": "min_abnormality",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 10.0,
              "minimum": 0.5,
              "description": "Minimum statistical unusualness of the price shift compared to baseline volatility (coefficient-of-variation multiples). Lower values surface more (weaker) shocks. Default 2.0.",
              "default": 2.0,
              "title": "Min Abnormality"
            },
            "description": "Minimum statistical unusualness of the price shift compared to baseline volatility (coefficient-of-variation multiples). Lower values surface more (weaker) shocks. Default 2.0."
          },
          {
            "name": "lookback_years",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 5,
              "minimum": 1,
              "description": "Years before the shock used to establish the coefficient-of-variation baseline. Default 2.",
              "default": 2,
              "title": "Lookback Years"
            },
            "description": "Years before the shock used to establish the coefficient-of-variation baseline. Default 2."
          },
          {
            "name": "cumulative_share",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 1.0,
              "minimum": 0.3,
              "description": "Only the top trading partners that together account for this share of total trade are analysed. Default 0.80.",
              "default": 0.8,
              "title": "Cumulative Share"
            },
            "description": "Only the top trading partners that together account for this share of total trade are analysed. Default 0.80."
          },
          {
            "name": "max_shocks_per_pair",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 5,
              "minimum": 1,
              "description": "Number of non-overlapping shocks kept per trading partner. Raise to surface multiple shock events per pair. Default 1.",
              "default": 1,
              "title": "Max Shocks Per Pair"
            },
            "description": "Number of non-overlapping shocks kept per trading partner. Raise to surface multiple shock events per pair. Default 1."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/vulnerability/net-import-reliance": {
      "post": {
        "tags": [
          "vulnerability"
        ],
        "summary": "Get Net Import Reliance",
        "description": "Net Import Reliance (NIR): net imports as a share of EU consumption.\n\nNIR = (Imports - Exports) / Apparent consumption, where apparent\nconsumption = Production + Imports - Exports. The European Commission uses\nit to gauge an industrial sector's import dependence; e.g. 40% means 40% of\nEU consumption is met by imports, a negative value means the EU is a net\nexporter. Combines PRODCOM production data with Comext trade data, so it is\nannual and CN codes are mapped to PRODCOM (with confidentiality fallbacks).\n\nStreams NDJSON, then returns the NIR % series, its supply/disposition\ncomponents, sibling-category and per-code comparisons, the resolved PRODCOM\ncodes, and availability notes. Use the ``prodcom_code`` body field to pin a\nspecific mapping.\n\nPRODCOM is the intra-EU production-statistics database; it has its own\nnomenclature, separate from customs codes, with only partial correspondence\ntables. The mapping from a CN code therefore resolves to one of several\ncases, reported in the response's ``self`` block — a ``notes`` array\n(human-readable messages) and, when no series can be produced, an\n``unavailability_type`` code:\n\n- direct single match — the CN code maps to exactly one PRODCOM code.\n- confidentiality fallback — it maps to a code Eurostat suppresses at that\n  level of detail, so the figures shown come from the nearest available\n  higher level.\n- multi-match — it maps to several PRODCOM codes whose statistics are\n  aggregated; the picker lets the user drill into one (pin it with\n  ``prodcom_code``).\n\n``unavailability_type`` (else ``None``) takes one of:\n\n- ``'no_mapping'`` — the CN code has no correspondence in PRODCOM, so no\n  production statistics exist for it.\n- ``'selection_required'`` — several PRODCOM codes map to the CN code and\n  some are suppressed; the caller must choose a specific one.\n- ``'suppressed'`` — it does map to PRODCOM code(s), but for confidentiality\n  reasons Eurostat publishes nothing for them nor their immediate parents.\n- ``'no_data'`` — it maps to PRODCOM code(s) but Eurostat returns no data\n  yet (they may be too recent).\n- ``'not_applicable'`` — the product is the 'TOTAL' meta-aggregate, which\n  has no PRODCOM equivalent and cannot be used with this indicator.\n\nThe ``self`` block (single mode) and each ``children`` entry carry the full\n``data[measure][basis]`` matrix (value/quantity × total/own/sub); the\nfrontend selects which variant to plot.",
        "operationId": "get_net_import_reliance_api_vulnerability_net_import_reliance_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/vulnerability/trade-intensity": {
      "post": {
        "tags": [
          "vulnerability"
        ],
        "summary": "Get Trade Intensity",
        "description": "Trade Intensity (TI): how exposed an EU sector is to international trade.\n\nTI = (Imports + Exports) / (Imports + Production). A value near 1 marks a\nsector highly exposed to trade; near 0, a predominantly domestic one. The\nCommission uses this CEEAG methodology to assess carbon-leakage / State-aid\neligibility. Like NIR it joins PRODCOM production with Comext trade data, is\nannual, and resolves CN codes to PRODCOM.\n\nStreams NDJSON, then returns the TI series, its components, sibling-category\nand per-code comparisons, the resolved PRODCOM codes and availability notes.\nUse the ``prodcom_code`` body field to pin a specific mapping.\n\nPRODCOM (the intra-EU production-statistics database) has its own\nnomenclature, separate from customs codes, with only partial correspondence\ntables — so, exactly as for Net Import Reliance, the CN-to-PRODCOM mapping\nresolves to a direct single match, a confidentiality fallback to the nearest\navailable higher level, or a multi-match whose codes are aggregated (drill\ninto one with ``prodcom_code``). These are described in the response's\n``self`` block via a ``notes`` array and, when no series can be produced, an\n``unavailability_type`` code:\n\n- ``'no_mapping'`` — the CN code has no correspondence in PRODCOM, so no\n  production statistics exist for it.\n- ``'selection_required'`` — several PRODCOM codes map to the CN code and\n  some are suppressed; the caller must choose a specific one.\n- ``'suppressed'`` — it does map to PRODCOM code(s), but for confidentiality\n  reasons Eurostat publishes nothing for them nor their immediate parents.\n- ``'no_data'`` — it maps to PRODCOM code(s) but Eurostat returns no data\n  yet (they may be too recent).\n- ``'not_applicable'`` — the product is the 'TOTAL' meta-aggregate, which\n  has no PRODCOM equivalent and cannot be used with this indicator.\n\nOtherwise ``unavailability_type`` is ``None``. The ``self`` block and each\n``children`` entry carry the full ``data[measure][basis]`` matrix\n(value/quantity × total/own/sub); the frontend selects which to plot.",
        "operationId": "get_trade_intensity_api_vulnerability_trade_intensity_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/vulnerability/export-propensity": {
      "post": {
        "tags": [
          "vulnerability"
        ],
        "summary": "Get Export Propensity",
        "description": "Export Propensity: the share of EU production that is exported.\n\nExport propensity = Exports / Production. Unlike Net Import Reliance and\nTrade Intensity it isolates outward orientation rather than mixing import\nand export flows, and is not clamped — values above 100% are legitimate\n(re-exports, stock drawdown, or production/export scope differences). Like\nthe other industrial-exposure indicators it joins PRODCOM production with\nComext trade data, is annual, and resolves CN codes to PRODCOM.\n\nStreams NDJSON, then returns the export-propensity matrix, a same-level\nsibling comparison (single mode) or per-code comparison (multi mode), the\nresolved PRODCOM codes and availability notes. Use the ``prodcom_code`` body\nfield to pin a specific mapping.\n\nThe CN-to-PRODCOM mapping resolves exactly as for Net Import Reliance and\nTrade Intensity; the response's ``self`` block reports a ``notes`` array and\nan ``unavailability_type`` (``'no_mapping'``, ``'selection_required'``,\n``'suppressed'``, ``'no_data'`` or ``'not_applicable'``; else ``None``).\n\nThe ``self`` block and each ``children`` entry carry the full\n``data[measure][basis]`` matrix (value/quantity × total/own/sub); the\nfrontend selects which variant to plot.",
        "operationId": "get_export_propensity_api_vulnerability_export_propensity_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/autonomy/subcontracting": {
      "post": {
        "tags": [
          "autonomy"
        ],
        "summary": "Get Subcontracting Intensity",
        "description": "EU27 sub-contracting intensity: the share of sold production that is\ntoll (sub-contracted) manufacturing rather than own-account output.\n\nStreams NDJSON, then returns the intensity (%) across the value/quantity\nmeasures, its sub-contracted / own-account / total production components,\nper-period data-quality status, the resolved PRODCOM codes and availability\nnotes.  The own/sub split exists only from 2021 onward; earlier years carry\nno component and resolve to a ``not_available`` availability.",
        "operationId": "get_subcontracting_intensity_api_autonomy_subcontracting_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/autonomy/subcontracting-members": {
      "post": {
        "tags": [
          "autonomy"
        ],
        "summary": "Get Subcontracting Members",
        "description": "Per-country sub-contracting intensity (PRODCOM).\n\nThe per-country counterpart of ``/subcontracting``: for each reporting\ncountry, the share of its sold production that is toll manufacturing — which\ncountries act as processing hubs versus autonomous producers.  The own/sub\nsplit carries no trade columns, so this spans every reporting country in\nDS-059358 (the EU member states plus EFTA, candidate and other reporters),\nnot only the EU-27; reporters that don't report the split simply don't\nappear.  Country cells are heavily confidentiality-suppressed, so suppressed\n/ absent values surface as ``null`` rather than zero.\n\nStreams NDJSON, then returns the per-country intensity (%) panel and its\nsub-contracted-production components, across the value/quantity measures.",
        "operationId": "get_subcontracting_members_api_autonomy_subcontracting_members_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardQuery"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Get Stats",
        "operationId": "get_stats_api_stats_get",
        "parameters": [
          {
            "name": "password",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "title": "Password"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/register": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Register",
        "description": "Start account creation and send an email confirmation link.\n\nAlways returns ``{ok: true}`` regardless of whether the address is new,\nalready taken, or invalid — to avoid disclosing which emails are registered.\nAn unverified existing account quietly gets a fresh confirmation link.",
        "operationId": "auth_register_api_auth_register_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/verify": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Verify",
        "description": "Consume an email-confirmation token and redirect to the app.\n\nMarks the account verified and redirects to ``/?verified=1`` on success or\n``/?verified=0`` for an invalid/expired token. Hit by the user clicking the\nlink in the confirmation email.",
        "operationId": "auth_verify_api_auth_verify_get",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "title": "Token"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Login",
        "description": "Authenticate and open a session.\n\nOn success sets the ``td_session`` HttpOnly cookie and returns the user's\nemail and recently checked codes. Returns 401 for bad credentials and 403\nif the email has not been confirmed yet.",
        "operationId": "auth_login_api_auth_login_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Logout",
        "description": "Revoke the current session and clear the session cookie.",
        "operationId": "auth_logout_api_auth_logout_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/api/auth/me": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Me",
        "description": "Return the signed-in user's email and recent codes, or 401 if anonymous.",
        "operationId": "auth_me_api_auth_me_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/api/auth/resend-verification": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Resend",
        "description": "Resend the confirmation email for an unverified account.\n\nAlways returns ``{ok: true}`` to avoid disclosing account existence.",
        "operationId": "auth_resend_api_auth_resend_verification_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/request-password-reset": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Request Reset",
        "description": "Email a password-reset link if the address has an account.\n\nAlways returns ``{ok: true}`` to avoid disclosing account existence.",
        "operationId": "auth_request_reset_api_auth_request_password_reset_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/reset-password": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Reset Password",
        "description": "Set a new password using a valid reset token.\n\nConsumes the token, updates the password, marks the email verified (control\nof the inbox is proven), and revokes all existing sessions. Returns 400 for\nan invalid/expired token or a too-short password.",
        "operationId": "auth_reset_password_api_auth_reset_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/set-lang": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Set Lang",
        "description": "Persist the signed-in user's preferred UI language.",
        "operationId": "auth_set_lang_api_auth_set_lang_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetLangReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/delete-account": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Delete Account",
        "description": "Permanently delete the signed-in user's account and all related data.\n\nRequires the current password as confirmation. On success every session,\nemail token and the account row are removed, the session cookie is cleared,\nand the user is fully forgotten. Returns 401 if anonymous or if the password\ndoes not match.",
        "operationId": "auth_delete_account_api_auth_delete_account_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteAccountReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/recent-codes": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Auth Add Recent Code",
        "description": "Record a checked CN code on the user's history.\n\nReturns the updated recent-codes list.",
        "operationId": "auth_add_recent_code_api_auth_recent_codes_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecentCodeReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "DashboardQuery": {
        "properties": {
          "product": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            ],
            "title": "Product",
            "description": "Combined Nomenclature customs code(s), 2–8 digits, or the literal 'TOTAL' meta-aggregate (all products combined). Pass a list to aggregate several codes: their volumes are summed and prices volume-weighted across every tab — except the Cross-section views, which keep each code separate.",
            "examples": [
              "3801",
              [
                "0901",
                "0902"
              ],
              "TOTAL"
            ]
          },
          "reporter": {
            "type": "string",
            "title": "Reporter",
            "description": "Declaring entity: an EU member state, an EU candidate country, Northern Ireland ('XI'), or one of the three aggregate presets ('European Union', 'Euro area', 'EU candidate countries'), or a predefined reporter group name.",
            "default": "European Union"
          },
          "reporter_members": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reporter Members",
            "description": "Explicit member countries for a custom reporter group. Each entry must be an EU member state, EU candidate country, or Northern Ireland. When more than one member is supplied the frame is aggregated into a synthetic group named after 'reporter'."
          },
          "partner_set": {
            "type": "string",
            "title": "Partner Set",
            "description": "Named partner preset, e.g. 'non-EU countries', 'EU countries', 'all countries', or a predefined partner group.",
            "default": "non-EU countries"
          },
          "partners": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Partners",
            "description": "Explicit partner-country list that overrides 'partner_set' when present."
          },
          "period_start": {
            "type": "string",
            "title": "Period Start",
            "description": "Inclusive start of the analysis window, ISO month 'YYYY-MM'.",
            "default": "2015-01"
          },
          "period_end": {
            "type": "string",
            "title": "Period End",
            "description": "Inclusive end of the analysis window, ISO month 'YYYY-MM'. Defaults to roughly three months ago — the latest period Eurostat typically has fully reported."
          },
          "frequency": {
            "type": "string",
            "title": "Frequency",
            "description": "Resampling frequency for the series: 'month', 'quarter' or 'year'.",
            "default": "quarter"
          },
          "n_top": {
            "type": "integer",
            "title": "N Top",
            "description": "Number of top entities (by volume/value) to break out individually in charts.",
            "default": 7
          },
          "include_other": {
            "type": "boolean",
            "title": "Include Other",
            "description": "Bundle entities outside the top-N into a single 'Other' bucket.",
            "default": true
          },
          "remove_outliers": {
            "type": "boolean",
            "title": "Remove Outliers",
            "description": "Drop extreme unit-price outliers before computing weighted-average prices.",
            "default": true
          },
          "midpoint": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Midpoint",
            "description": "Split point 'YYYY-MM' for the Pattern Shifts tab: the window is cut in two here and the before/after change per entity is reported. Defaults to the middle of the period when omitted."
          },
          "lang": {
            "type": "string",
            "title": "Lang",
            "description": "Language for translated labels in the response (en, de, fr, es, it, ro).",
            "default": "en"
          },
          "prodcom_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Prodcom Code",
            "description": "Specific PRODCOM dataset code to pin for the Net Import Reliance and Trade Intensity tabs, when a single CN code maps to several. Ignored elsewhere."
          }
        },
        "type": "object",
        "required": [
          "product"
        ],
        "title": "DashboardQuery",
        "description": "Shared request body for every analytical endpoint.\n\nOne product (or a list of products to aggregate), one reporter (or group of\nreporters), a partner set, a period window and a frequency fully describe a\nslice of Eurostat Comext/PRODCOM data. Individual endpoints read extra,\nendpoint-specific options from the query string (e.g. ``entity_level``,\n``flow``, ``partner``) rather than from this body."
      },
      "DeleteAccountReq": {
        "properties": {
          "password": {
            "type": "string",
            "title": "Password"
          }
        },
        "type": "object",
        "required": [
          "password"
        ],
        "title": "DeleteAccountReq"
      },
      "EmailReq": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "lang": {
            "type": "string",
            "title": "Lang",
            "default": "en"
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "EmailReq"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "LoginReq": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "title": "Password"
          },
          "lang": {
            "type": "string",
            "title": "Lang",
            "default": "en"
          }
        },
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "title": "LoginReq"
      },
      "RecentCodeReq": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "name": {
            "type": "string",
            "title": "Name",
            "default": ""
          }
        },
        "type": "object",
        "required": [
          "code"
        ],
        "title": "RecentCodeReq"
      },
      "RegisterReq": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "title": "Password"
          },
          "lang": {
            "type": "string",
            "title": "Lang",
            "default": "en"
          }
        },
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "title": "RegisterReq"
      },
      "ResetReq": {
        "properties": {
          "token": {
            "type": "string",
            "title": "Token"
          },
          "password": {
            "type": "string",
            "title": "Password"
          }
        },
        "type": "object",
        "required": [
          "token",
          "password"
        ],
        "title": "ResetReq"
      },
      "SetLangReq": {
        "properties": {
          "lang": {
            "type": "string",
            "title": "Lang"
          }
        },
        "type": "object",
        "required": [
          "lang"
        ],
        "title": "SetLangReq"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      }
    }
  },
  "tags": [
    {
      "name": "reference",
      "description": "Public reference data: country lists, UI language detection, customs-code validation/search and nomenclature trees."
    },
    {
      "name": "dashboard",
      "description": "Descriptive statistics for the main dashboard: overview totals, partner/reporter breakdowns, the map view, entity tables and member-state PRODCOM production series."
    },
    {
      "name": "products",
      "description": "Cross-section views comparing sub-products or several codes side by side."
    },
    {
      "name": "concentration",
      "description": "Concentration analysis via the Herfindahl-Hirschman Index (HHI): trade concentration across partners/member states (overall, per sub-product and on the map), intra-EU specialisation, and PRODCOM production concentration across member states."
    },
    {
      "name": "volatility",
      "description": "Volatility and shock detection: coefficient-of-variation grids, sudden volume collapses, rapid price shifts and pattern shifts."
    },
    {
      "name": "vulnerability",
      "description": "Industrial-exposure indicators that join PRODCOM production with Comext trade data: Net Import Reliance, Trade Intensity and Export Propensity."
    },
    {
      "name": "autonomy",
      "description": "Industrial-autonomy indicators built on the PRODCOM sub-contracting / own-account split: how much of EU (and member-state) sold production is toll manufacturing for others."
    },
    {
      "name": "admin",
      "description": "Password-protected usage statistics."
    },
    {
      "name": "auth",
      "description": "Account lifecycle: register, email verification, login/logout, password reset and per-user preferences."
    }
  ]
}