From 08825bf81775544609dceb8dc0fc69e295609547 Mon Sep 17 00:00:00 2001 From: gulimabr Date: Wed, 28 Jan 2026 15:34:58 -0300 Subject: [PATCH] initial commit --- .gitignore | 17 + .vscode/tasks.json | 13 + README.md | 28 + backend/Dockerfile | 18 + backend/README.md | 13 + backend/app/__init__.py | 0 backend/app/main.py | 488 ++++ backend/pyproject.toml | 24 + data/iso/iso-14244-tax.json | 4290 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 24 + frontend/Dockerfile | 15 + frontend/index.html | 12 + frontend/package.json | 25 + frontend/postcss.config.cjs | 6 + frontend/src/App.tsx | 204 ++ frontend/src/index.css | 11 + frontend/src/main.tsx | 10 + frontend/tailwind.config.js | 8 + frontend/tsconfig.json | 17 + frontend/tsconfig.node.json | 9 + frontend/vite.config.ts | 10 + package.json | 6 + pnpm-workspace.yaml | 3 + 23 files changed, 5251 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 README.md create mode 100644 backend/Dockerfile create mode 100644 backend/README.md create mode 100644 backend/app/__init__.py create mode 100644 backend/app/main.py create mode 100644 backend/pyproject.toml create mode 100644 data/iso/iso-14244-tax.json create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.cjs create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 package.json create mode 100644 pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..721889c --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Node +node_modules +dist +pnpm-debug.log* + +# Python +__pycache__/ +*.pyc +.venv/ +.pytest_cache/ + +# Environment +.env +.env.local + +# OS +.DS_Store diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ddd76fb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "noop", + "type": "shell", + "command": "echo", + "args": [ + "noop" + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c29c3a4 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# TermSearch + +Monorepo with a FastAPI backend and React + Tailwind frontend. + +## Development + +- Backend runs on http://localhost:8000 +- Frontend runs on http://localhost:5173 + +### Docker + +Build and start both services: + +- `docker compose up --build` + +### Local (without Docker) + +Backend: + +- `cd backend` +- `poetry install` +- `poetry run uvicorn app.main:app --reload` + +Frontend: + +- `cd frontend` +- `pnpm install` +- `pnpm dev` diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..9e6647f --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.12-slim + +WORKDIR /app + +ENV POETRY_VERSION=1.8.3 + +RUN pip install --no-cache-dir "poetry==$POETRY_VERSION" + +COPY pyproject.toml poetry.lock* /app/ + +RUN poetry config virtualenvs.create false \ + && poetry install --no-interaction --no-ansi + +COPY . /app + +EXPOSE 8000 + +CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..dd81b7f --- /dev/null +++ b/backend/README.md @@ -0,0 +1,13 @@ +# TermSearch Backend + +FastAPI service that will provide definitions from oil & gas glossary sources. + +## Development + +- `poetry install` +- `poetry run uvicorn app.main:app --reload` + +## Endpoints + +- `GET /api/health` +- `GET /api/definitions?term=...` diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..84acc7b --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,488 @@ +from __future__ import annotations + +import asyncio +import json +import logging +import os +import time +from functools import lru_cache +from pathlib import Path +from typing import List, Optional +from urllib.parse import quote_plus + +import httpx +import structlog +from asgi_correlation_id import CorrelationIdMiddleware +from asgi_correlation_id.context import correlation_id +from fastapi import FastAPI, Query, Response +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from selectolax.parser import HTMLParser +from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential + + +class Definition(BaseModel): + source: str + title: str + url: str + definition: str + + +class TaxonomyMatch(BaseModel): + category: str + class_name: str + class_code: str + type_description: Optional[str] = None + type_code: Optional[str] = None + annex: Optional[str] = None + full_name: str + + +class DefinitionResponse(BaseModel): + term: str + results: List[Definition] + request_id: Optional[str] = None + taxonomy: List[TaxonomyMatch] = Field(default_factory=list) + + +app = FastAPI(title="TermSearch API", version="0.1.0") + +logging.basicConfig(format="%(message)s", level=logging.INFO) +structlog.configure( + processors=[ + structlog.processors.TimeStamper(fmt="iso"), + structlog.processors.add_log_level, + structlog.processors.EventRenamer("event"), + structlog.processors.JSONRenderer(), + ] +) +logger = structlog.get_logger("termsearch") + +app.add_middleware( + CorrelationIdMiddleware, + header_name="X-Request-ID", +) + +frontend_origin = os.getenv("FRONTEND_ORIGIN", "http://localhost:5173") +allowed_origins = [origin.strip() for origin in frontend_origin.split(",") if origin.strip()] + +app.add_middleware( + CORSMiddleware, + allow_origins=allowed_origins, + allow_credentials=True, + allow_methods=["*"] , + allow_headers=["*"], +) + + +def normalize_text(text: str) -> str: + return " ".join(text.lower().split()) + + +@lru_cache(maxsize=1) +def load_taxonomy() -> dict: + root_dir = Path(__file__).resolve().parents[1] + tax_path = root_dir / "data" / "iso" / "iso-14244-tax.json" + if not tax_path.exists(): + tax_path = Path("/data/iso/iso-14244-tax.json") + with tax_path.open("r", encoding="utf-8") as handle: + return json.load(handle) + + +def find_taxonomy_matches(term: str) -> List[TaxonomyMatch]: + normalized_term = normalize_text(term) + term_tokens = normalized_term.split() + if not term_tokens: + return [] + + data = load_taxonomy() + taxonomy = data.get("taxonomy", {}) if isinstance(data, dict) else {} + categories = taxonomy.get("categories", {}) if isinstance(taxonomy, dict) else {} + matches: List[TaxonomyMatch] = [] + + for category_name, category in categories.items(): + classes = category.get("classes", {}) if isinstance(category, dict) else {} + for class_code, class_info in classes.items(): + class_name = class_info.get("name", "") + annex = class_info.get("annex") + types = class_info.get("types", {}) if isinstance(class_info, dict) else {} + + for type_code, type_info in types.items(): + type_description = type_info.get("description", "") + full_name = f"{type_description} {class_name}".strip() + full_name_normalized = normalize_text(full_name) + + if all(token in full_name_normalized for token in term_tokens): + matches.append( + TaxonomyMatch( + category=category_name, + class_name=class_name, + class_code=class_code, + type_description=type_description, + type_code=type_code, + annex=annex, + full_name=full_name, + ) + ) + + class_name_normalized = normalize_text(class_name) + if class_name and all(token in class_name_normalized for token in term_tokens): + matches.append( + TaxonomyMatch( + category=category_name, + class_name=class_name, + class_code=class_code, + annex=annex, + full_name=class_name, + ) + ) + + return matches + + +@retry( + retry=retry_if_exception_type((httpx.RequestError, httpx.TimeoutException)), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=0.5, min=0.5, max=4), +) +async def fetch_text(client: httpx.AsyncClient, url: str) -> str: + response = await client.get(url) + response.raise_for_status() + return response.text + + +@retry( + retry=retry_if_exception_type((httpx.RequestError, httpx.TimeoutException)), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=0.5, min=0.5, max=4), +) +async def fetch_json( + client: httpx.AsyncClient, + url: str, + data: dict[str, str], +) -> dict: + response = await client.post( + url, + data=data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + response.raise_for_status() + return response.json() + + +@retry( + retry=retry_if_exception_type((httpx.RequestError, httpx.TimeoutException)), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=0.5, min=0.5, max=4), +) +async def fetch_json_get(client: httpx.AsyncClient, url: str) -> dict: + response = await client.get(url) + response.raise_for_status() + return response.json() + + +async def scrape_dicionario_first(term: str) -> Optional[Definition]: + search_url = f"https://dicionariopetroleoegas.com.br/?s={quote_plus(term)}" + timeout = httpx.Timeout(10.0, connect=5.0) + start_time = time.perf_counter() + + try: + async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: + search_html = await fetch_text(client, search_url) + search_parser = HTMLParser(search_html) + first_link = search_parser.css_first("div.definitionlist a") + + if not first_link: + logger.info( + "no_results", + source="dicionariopetroleoegas", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + detail_url = first_link.attributes.get("href") + if not detail_url: + logger.warning( + "missing_detail_url", + source="dicionariopetroleoegas", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + detail_html = await fetch_text(client, detail_url) + detail_parser = HTMLParser(detail_html) + article_node = detail_parser.css_first("div.maincontent article") + if not article_node: + logger.warning( + "missing_article", + source="dicionariopetroleoegas", + term=term, + url=detail_url, + request_id=correlation_id.get(), + ) + return None + + definition_text = " ".join(article_node.text().split()) + elapsed_ms = (time.perf_counter() - start_time) * 1000 + + logger.info( + "scrape_success", + source="dicionariopetroleoegas", + term=term, + url=detail_url, + elapsed_ms=round(elapsed_ms, 2), + request_id=correlation_id.get(), + ) + + title = first_link.attributes.get("title") or term + + return Definition( + source="dicionariopetroleoegas", + title=title, + url=detail_url, + definition=definition_text, + ) + except httpx.HTTPStatusError as exc: + logger.warning( + "http_status_error", + source="dicionariopetroleoegas", + term=term, + url=str(exc.request.url), + status_code=exc.response.status_code, + request_id=correlation_id.get(), + ) + except httpx.RequestError as exc: + logger.warning( + "network_error", + source="dicionariopetroleoegas", + term=term, + url=str(exc.request.url) if exc.request else search_url, + error=str(exc), + request_id=correlation_id.get(), + ) + + return None + + +async def scrape_slb_first(term: str) -> Optional[Definition]: + search_url = "https://glossary.slb.com/coveo/rest/search/v2?siteName=OilfieldGlossary" + payload = { + "q": term, + "aq": "(@z95xpath==28F6D9B16B684F7C9BE6937026AB0B6B)", + "searchHub": "OilfieldGlossarySearchPage", + "locale": "en", + "pipeline": "SLBCom", + "numberOfResults": "12", + } + timeout = httpx.Timeout(10.0, connect=5.0) + start_time = time.perf_counter() + + try: + async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: + search_json = await fetch_json(client, search_url, payload) + results = search_json.get("results", []) if isinstance(search_json, dict) else [] + if not results: + logger.info( + "no_results", + source="slb-glossary", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + first_result = results[0] + detail_url = first_result.get("printableUri") or first_result.get("clickUri") + if not detail_url: + logger.warning( + "missing_detail_url", + source="slb-glossary", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + detail_html = await fetch_text(client, detail_url) + detail_parser = HTMLParser(detail_html) + content_node = detail_parser.css_first("div.content-two-col__text") + if not content_node: + logger.warning( + "missing_article", + source="slb-glossary", + term=term, + url=detail_url, + request_id=correlation_id.get(), + ) + return None + + definition_text = " ".join(content_node.text().split()) + elapsed_ms = (time.perf_counter() - start_time) * 1000 + + logger.info( + "scrape_success", + source="slb-glossary", + term=term, + url=detail_url, + elapsed_ms=round(elapsed_ms, 2), + request_id=correlation_id.get(), + ) + + raw = first_result.get("raw", {}) if isinstance(first_result, dict) else {} + title = raw.get("mainz32xtitle") or term + + return Definition( + source="slb-glossary", + title=title, + url=detail_url, + definition=definition_text, + ) + except httpx.HTTPStatusError as exc: + logger.warning( + "http_status_error", + source="slb-glossary", + term=term, + url=str(exc.request.url), + status_code=exc.response.status_code, + request_id=correlation_id.get(), + ) + except httpx.RequestError as exc: + logger.warning( + "network_error", + source="slb-glossary", + term=term, + url=str(exc.request.url) if exc.request else search_url, + error=str(exc), + request_id=correlation_id.get(), + ) + + return None + + +async def scrape_merriam_first(term: str) -> Optional[Definition]: + search_url = ( + "https://www.merriam-webster.com/lapi/v1/mwol-search/autocomplete" + f"?search={quote_plus(term)}" + ) + timeout = httpx.Timeout(10.0, connect=5.0) + start_time = time.perf_counter() + + try: + async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: + search_json = await fetch_json_get(client, search_url) + docs = search_json.get("docs", []) if isinstance(search_json, dict) else [] + if not docs: + logger.info( + "no_results", + source="merriam-webster", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + first_doc = docs[0] if isinstance(docs[0], dict) else {} + slug = first_doc.get("slug") + title = first_doc.get("word") or term + if not slug: + logger.warning( + "missing_detail_url", + source="merriam-webster", + term=term, + url=search_url, + request_id=correlation_id.get(), + ) + return None + + detail_url = f"https://www.merriam-webster.com{slug}" + detail_html = await fetch_text(client, detail_url) + detail_parser = HTMLParser(detail_html) + + content_node = detail_parser.css_first("span.dtText") + if not content_node: + logger.warning( + "missing_article", + source="merriam-webster", + term=term, + url=detail_url, + request_id=correlation_id.get(), + ) + return None + + definition_text = " ".join(content_node.text().split()) + definition_text = definition_text.lstrip(":").strip() + elapsed_ms = (time.perf_counter() - start_time) * 1000 + + logger.info( + "scrape_success", + source="merriam-webster", + term=term, + url=detail_url, + elapsed_ms=round(elapsed_ms, 2), + request_id=correlation_id.get(), + ) + + return Definition( + source="merriam-webster", + title=title, + url=detail_url, + definition=definition_text, + ) + except httpx.HTTPStatusError as exc: + logger.warning( + "http_status_error", + source="merriam-webster", + term=term, + url=str(exc.request.url), + status_code=exc.response.status_code, + request_id=correlation_id.get(), + ) + except httpx.RequestError as exc: + logger.warning( + "network_error", + source="merriam-webster", + term=term, + url=str(exc.request.url) if exc.request else search_url, + error=str(exc), + request_id=correlation_id.get(), + ) + + return None + + +@app.get("/api/health") +def health() -> dict: + return {"status": "ok"} + + +@app.get("/api/definitions", response_model=DefinitionResponse) +async def get_definitions( + response: Response, + term: str = Query(min_length=1), +) -> DefinitionResponse: + request_id = correlation_id.get() + if request_id: + response.headers["X-Request-ID"] = request_id + + results = [ + result + for result in await asyncio.gather( + scrape_dicionario_first(term), + scrape_slb_first(term), + scrape_merriam_first(term), + ) + if result + ] + + taxonomy = find_taxonomy_matches(term) + + return DefinitionResponse( + term=term, + results=results, + request_id=request_id, + taxonomy=taxonomy, + ) diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..5bbad3b --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "termsearch-backend" +version = "0.1.0" +description = "FastAPI backend for TermSearch" +authors = ["Your Name "] +readme = "README.md" +packages = [{ include = "app" }] + +[tool.poetry.dependencies] +python = "^3.12" +fastapi = "^0.111.0" +uvicorn = { extras = ["standard"], version = "^0.30.0" } +httpx = "^0.27.0" +selectolax = "^0.3.21" +tenacity = "^8.3.0" +structlog = "^24.4.0" +asgi-correlation-id = "^4.3.1" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.5.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/data/iso/iso-14244-tax.json b/data/iso/iso-14244-tax.json new file mode 100644 index 0000000..bd9c8f8 --- /dev/null +++ b/data/iso/iso-14244-tax.json @@ -0,0 +1,4290 @@ +{ + "taxonomy": { + "categories": { + "Rotating": { + "name": "Rotating", + "classes": { + "BL": { + "name": "Blowers and fans", + "code": "BL", + "annex": null, + "types": {} + }, + "CF": { + "name": "Centrifuges", + "code": "CF", + "annex": null, + "types": {} + }, + "CE": { + "name": "Combustion engines", + "code": "CE", + "annex": "A.2.2.1", + "types": { + "DE": { + "description": "Diesel engine", + "code": "DE" + }, + "GE": { + "description": "Otto (gas) engine", + "code": "GE" + } + } + }, + "CO": { + "name": "Compressors", + "code": "CO", + "annex": "A.2.2.2", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "SC": { + "description": "Screw", + "code": "SC" + }, + "AX": { + "description": "Axial", + "code": "AX" + } + } + }, + "EG": { + "name": "Electric generators", + "code": "EG", + "annex": "A.2.2.3", + "types": { + "TD": { + "description": "Gas-turbine driven", + "code": "TD" + }, + "SD": { + "description": "Steam-turbine driven", + "code": "SD" + }, + "TE": { + "description": "Turboexpander", + "code": "TE" + }, + "MD": { + "description": "Engine driven, e.g. diesel engine, gas engine", + "code": "MD" + } + } + }, + "EM": { + "name": "Electric motors", + "code": "EM", + "annex": "A.2.2.4", + "types": { + "AC": { + "description": "Alternating current", + "code": "AC" + }, + "DC": { + "description": "Direct current", + "code": "DC" + } + } + }, + "GT": { + "name": "Gas turbines", + "code": "GT", + "annex": "A.2.2.5", + "types": { + "IN": { + "description": "Industrial", + "code": "IN" + }, + "AD": { + "description": "Aero-derivative", + "code": "AD" + }, + "HD": { + "description": "Heavy duty", + "code": "HD" + } + } + }, + "LE": { + "name": "Liquid expanders", + "code": "LE", + "annex": null, + "types": {} + }, + "MI": { + "name": "Mixers", + "code": "MI", + "annex": null, + "types": {} + }, + "PU": { + "name": "Pumps", + "code": "PU", + "annex": "A.2.2.6", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + } + } + }, + "ST": { + "name": "Steam turbines", + "code": "ST", + "annex": "A.2.2.7", + "types": { + "MS": { + "description": "Multi-stage", + "code": "MS" + }, + "SS": { + "description": "Single-stage", + "code": "SS" + } + } + }, + "TE": { + "name": "Turboexpanders", + "code": "TE", + "annex": "A.2.2.8", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "AX": { + "description": "Axial", + "code": "AX" + } + } + } + } + }, + "Mechanical": { + "name": "Mechanical", + "classes": { + "CV": { + "name": "Conveyors and elevators", + "code": "CV", + "annex": null, + "types": {} + }, + "CR": { + "name": "Cranes", + "code": "CR", + "annex": "A.2.3.1", + "types": { + "HO": { + "description": "Electro-hydraulic operated", + "code": "HO" + }, + "DO": { + "description": "Diesel hydraulic operated", + "code": "DO" + } + } + }, + "FS": { + "name": "Filters and strainers", + "code": "FS", + "annex": null, + "types": {} + }, + "HE": { + "name": "Heat exchangers", + "code": "HE", + "annex": "A.2.3.2", + "types": { + "ST": { + "description": "Shell and tube", + "code": "ST" + }, + "P": { + "description": "Plate", + "code": "P" + }, + "PF": { + "description": "Plate fin", + "code": "PF" + }, + "DP": { + "description": "Double pipe", + "code": "DP" + }, + "BY": { + "description": "Bayonet", + "code": "BY" + }, + "PC": { + "description": "Printed circuit", + "code": "PC" + }, + "AC": { + "description": "Air-cooled", + "code": "AC" + }, + "S": { + "description": "Spiral", + "code": "S" + }, + "SW": { + "description": "Spiral-wound", + "code": "SW" + } + } + }, + "HB": { + "name": "Heaters and boilers", + "code": "HB", + "annex": "A.2.3.3", + "types": { + "DF": { + "description": "Direct-fired heater", + "code": "DF" + }, + "EH": { + "description": "Electric heater", + "code": "EH" + }, + "IF": { + "description": "Indirect HC-fired heater", + "code": "IF" + }, + "HT": { + "description": "Heater treater", + "code": "HT" + }, + "NF": { + "description": "Non-HC-fired boiler", + "code": "NF" + }, + "EB": { + "description": "Electric boiler", + "code": "EB" + }, + "FB": { + "description": "HC-fired boiler", + "code": "FB" + } + } + }, + "LA": { + "name": "Loading arms", + "code": "LA", + "annex": null, + "types": {} + }, + "PL": { + "name": "Onshore pipelines", + "code": "PL", + "annex": null, + "types": {} + }, + "PI": { + "name": "Piping", + "code": "PI", + "annex": "A.2.3.5", + "types": { + "CA": { + "description": "Carbon steels", + "code": "CA" + }, + "ST": { + "description": "Stainless steels", + "code": "ST" + }, + "LO": { + "description": "High-strength low-alloy steels", + "code": "LO" + }, + "TI": { + "description": "Titanium", + "code": "TI" + }, + "PO": { + "description": "Polymers including fibre-reinforced", + "code": "PO" + } + } + }, + "VE": { + "name": "Pressure vessels", + "code": "VE", + "annex": "A.2.3.4", + "types": { + "SP": { + "description": "Stripper", + "code": "SP" + }, + "SE": { + "description": "Separator", + "code": "SE" + }, + "CA": { + "description": "Coalescer", + "code": "CA" + }, + "FD": { + "description": "Flash drum", + "code": "FD" + }, + "SB": { + "description": "Scrubber", + "code": "SB" + }, + "CO": { + "description": "Contactor", + "code": "CO" + }, + "SD": { + "description": "Surge drum", + "code": "SD" + }, + "CY": { + "description": "Cyclone", + "code": "CY" + }, + "HY": { + "description": "Hydrocyclone", + "code": "HY" + }, + "SC": { + "description": "Slug catcher", + "code": "SC" + }, + "AD": { + "description": "Adsorber", + "code": "AD" + }, + "DR": { + "description": "Dryer", + "code": "DR" + }, + "PT": { + "description": "Pig trap", + "code": "PT" + }, + "DC": { + "description": "Distillation column", + "code": "DC" + }, + "SA": { + "description": "Saturator", + "code": "SA" + }, + "RE": { + "description": "Reactor", + "code": "RE" + }, + "DA": { + "description": "De-aerator", + "code": "DA" + } + } + }, + "SI": { + "name": "Silos", + "code": "SI", + "annex": null, + "types": {} + }, + "SE": { + "name": "Steam ejectors", + "code": "SE", + "annex": null, + "types": {} + }, + "TA": { + "name": "Storage Tanks", + "code": "TA", + "annex": "A.2.3.9", + "types": { + "FR": { + "description": "Fixed-Roof", + "code": "FR" + }, + "LR": { + "description": "Lifting Roof", + "code": "LR" + }, + "DP": { + "description": "Diaphragm", + "code": "DP" + }, + "EF": { + "description": "External Floating Roof", + "code": "EF" + }, + "RL": { + "description": "Roofless", + "code": "RL" + }, + "IF": { + "description": "Fixed Roof with Internal Floating Roof", + "code": "IF" + } + } + }, + "SW": { + "name": "Swivels", + "code": "SW", + "annex": "A.2.3.8", + "types": { + "AX": { + "description": "Axial", + "code": "AX" + }, + "TO": { + "description": "Toroidal", + "code": "TO" + }, + "ES": { + "description": "Electric/signal", + "code": "ES" + } + } + }, + "TU": { + "name": "Turrets", + "code": "TU", + "annex": "A.2.3.7", + "types": { + "DT": { + "description": "Disconnectable turrets", + "code": "DT" + }, + "PT": { + "description": "Permanent turrets", + "code": "PT" + } + } + }, + "WI": { + "name": "Winches", + "code": "WI", + "annex": "A.2.3.6", + "types": { + "EW": { + "description": "Electric winch", + "code": "EW" + }, + "HW": { + "description": "Hydraulic winch", + "code": "HW" + } + } + } + } + }, + "Electrical": { + "name": "Electrical", + "classes": { + "FC": { + "name": "Frequency converters", + "code": "FC", + "annex": "A.2.4.4", + "types": { + "LV": { + "description": "Low voltage", + "code": "LV" + }, + "HV": { + "description": "High voltage", + "code": "HV" + } + } + }, + "PC": { + "name": "Power cables and terminations", + "code": "PC", + "annex": null, + "types": {} + }, + "PT": { + "name": "Power transformers", + "code": "PT", + "annex": "A.2.4.2", + "types": { + "OT": { + "description": "Oil immersed", + "code": "OT" + }, + "DT": { + "description": "Dry", + "code": "DT" + } + } + }, + "SG": { + "name": "Switchgears", + "code": "SG", + "annex": "A.2.4.3", + "types": { + "LV": { + "description": "Low voltage", + "code": "LV" + }, + "OV": { + "description": "Oil and vacuum insulated", + "code": "OV" + }, + "HA": { + "description": "High voltage air insulated", + "code": "HA" + }, + "HG": { + "description": "High voltage gas insulated", + "code": "HG" + } + } + }, + "UP": { + "name": "Uninterruptible power supply", + "code": "UP", + "annex": "A.2.4.1", + "types": { + "UB": { + "description": "Dual UPS with standby bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "UB" + }, + "UD": { + "description": "Dual UPS without bypass Rectifier supplied from emergency power", + "code": "UD" + }, + "US": { + "description": "Single UPS with bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "US" + }, + "UT": { + "description": "Single UPS without bypass Rectifier supplied from emergency power", + "code": "UT" + } + } + } + } + }, + "Safety and control": { + "name": "Safety and control", + "classes": { + "CL": { + "name": "Control logic units", + "code": "CL", + "annex": "A.2.5.3", + "types": { + "LC": { + "description": "Programmable logic controller (PLC)", + "code": "LC" + }, + "PC": { + "description": "Computer", + "code": "PC" + }, + "DC": { + "description": "Distributed control unit", + "code": "DC" + }, + "RL": { + "description": "Relay", + "code": "RL" + }, + "SS": { + "description": "Solid state", + "code": "SS" + }, + "SL": { + "description": "Single-loop controller", + "code": "SL" + }, + "PA": { + "description": "Programmable automation controller (PAC)", + "code": "PA" + } + } + }, + "EC": { + "name": "Emergency communication equipment", + "code": "EC", + "annex": null, + "types": {} + }, + "ER": { + "name": "Escape, evacuation and rescue ", + "code": "ER", + "annex": null, + "types": {} + }, + "FG": { + "name": "Fire and gas detectors", + "code": "FG", + "annex": "A.2.5.1", + "types": { + "BS": { + "description": "Smoke/Combustion", + "code": "BS" + }, + "BH": { + "description": "Heat", + "code": "BH" + }, + "BF": { + "description": "Flame", + "code": "BF" + }, + "BM": { + "description": "Manual pushbutton", + "code": "BM" + }, + "BA": { + "description": "Others (Fire)", + "code": "BA" + }, + "AB": { + "description": "Hydrocarbon", + "code": "AB" + }, + "AS": { + "description": "Toxic gases", + "code": "AS" + }, + "AO": { + "description": "Others (Gas)", + "code": "AO" + } + } + }, + "FF": { + "name": "Fire-fighting equipment", + "code": "FF", + "annex": null, + "types": {} + }, + "FI": { + "name": "Flare ignition", + "code": "FI", + "annex": null, + "types": {} + }, + "IG": { + "name": "Inert-gas equipment", + "code": "IG", + "annex": null, + "types": {} + }, + "IP": { + "name": "Input devices", + "code": "IP", + "annex": "A.2.5.2", + "types": { + "PS": { + "description": "Pressure", + "code": "PS" + }, + "LS": { + "description": "Level", + "code": "LS" + }, + "TS": { + "description": "Temperature", + "code": "TS" + }, + "FS": { + "description": "Flow", + "code": "FS" + }, + "SP": { + "description": "Speed", + "code": "SP" + }, + "VI": { + "description": "Vibration", + "code": "VI" + }, + "DI": { + "description": "Displacement", + "code": "DI" + }, + "AN": { + "description": "Analyser", + "code": "AN" + }, + "WE": { + "description": "Weight", + "code": "WE" + }, + "CO": { + "description": "Corrosion", + "code": "CO" + }, + "LP": { + "description": "Limit switch", + "code": "LP" + }, + "PB": { + "description": "On/off (pushbutton)", + "code": "PB" + }, + "OT": { + "description": "Others", + "code": "OT" + } + } + }, + "LB": { + "name": "Lifeboats", + "code": "LB", + "annex": "A.2.5.6", + "types": { + "FF": { + "description": "Free fall", + "code": "FF" + }, + "DL": { + "description": "Davit launched", + "code": "DL" + } + } + }, + "NO": { + "name": "Nozzles", + "code": "NO", + "annex": "A.2.5.5", + "types": { + "DN": { + "description": "Deluge", + "code": "DN" + }, + "SR": { + "description": "Sprinkler", + "code": "SR" + }, + "WM": { + "description": "Water mist", + "code": "WM" + }, + "GA": { + "description": "Gaseous", + "code": "GA" + } + } + }, + "TC": { + "name": "Telecommunications", + "code": "TC", + "annex": null, + "types": {} + }, + "VA": { + "name": "Valves", + "code": "VA", + "annex": "A.2.5.4", + "types": { + "BA": { + "description": "Ball", + "code": "BA" + }, + "GA": { + "description": "Gate", + "code": "GA" + }, + "GL": { + "description": "Globe", + "code": "GL" + }, + "BP": { + "description": "Butterfly", + "code": "BP" + }, + "PG": { + "description": "Plug", + "code": "PG" + }, + "NE": { + "description": "Needle", + "code": "NE" + }, + "CH": { + "description": "Check", + "code": "CH" + }, + "DI": { + "description": "Disc", + "code": "DI" + }, + "FL": { + "description": "Flapper", + "code": "FL" + }, + "MO": { + "description": "Multiple orifice", + "code": "MO" + }, + "WA": { + "description": "Three-way", + "code": "WA" + }, + "SC": { + "description": "PSV-conventional", + "code": "SC" + }, + "SB": { + "description": "PSV-conventional with bellow", + "code": "SB" + }, + "SP": { + "description": "PSV-pilot operated", + "code": "SP" + }, + "SV": { + "description": "PSV-vacuum relief", + "code": "SV" + }, + "PC": { + "description": "Plug and cage", + "code": "PC" + }, + "ES": { + "description": "External sleeve", + "code": "ES" + }, + "AF": { + "description": "Axial flow", + "code": "AF" + }, + "PI": { + "description": "Pinch", + "code": "PI" + }, + "OH": { + "description": "Others", + "code": "OH" + } + } + } + } + }, + "Subsea": { + "name": "Subsea", + "classes": { + "DT": { + "name": "Dry tree risers", + "code": "DT", + "annex": null, + "types": {} + }, + "PR": { + "name": "Risers", + "code": "PR", + "annex": "A.2.6.3", + "types": { + "RI": { + "description": "Rigid", + "code": "RI" + }, + "FL": { + "description": "Flexible", + "code": "FL" + } + } + }, + "SC": { + "name": "Subsea compressors", + "code": "SC", + "annex": null, + "types": {} + }, + "SD": { + "name": "Subsea diving equipment", + "code": "SD", + "annex": null, + "types": {} + }, + "EP": { + "name": "Subsea electrical power", + "code": "EP", + "annex": "A.2.6.5", + "types": { + "SU": { + "description": "Single consumer without subsea step-down", + "code": "SU" + }, + "SD": { + "description": "Single consumer with subsea step-down", + "code": "SD" + }, + "MC": { + "description": "Multiple consumer", + "code": "MC" + } + } + }, + "FL": { + "name": "Subsea flowlines", + "code": "FL", + "annex": null, + "types": {} + }, + "SH": { + "name": "Subsea heat exchangers", + "code": "SH", + "annex": null, + "types": {} + }, + "CI": { + "name": "Subsea intervention", + "code": "CI", + "annex": null, + "types": {} + }, + "MA": { + "name": "Subsea manifolds", + "code": "MA", + "annex": null, + "types": {} + }, + "SL": { + "name": "Subsea pipelines", + "code": "SL", + "annex": "A.2.6.7", + "types": { + "FL": { + "description": "Flexible", + "code": "FL" + }, + "RI": { + "description": "Rigid", + "code": "RI" + } + } + }, + "CA": { + "name": "Submarine power cables", + "code": "CA", + "annex": null, + "types": {} + }, + "SV": { + "name": "Subsea pressure vessels", + "code": "SV", + "annex": "A.2.6.6", + "types": { + "CA": { + "description": "Coalescer", + "code": "CA" + }, + "CY": { + "description": "Cyclone", + "code": "CY" + }, + "HY": { + "description": "Hydrocyclone", + "code": "HY" + }, + "SB": { + "description": "Scrubber", + "code": "SB" + }, + "SE": { + "description": "Separator", + "code": "SE" + }, + "SC": { + "description": "Slug catcher", + "code": "SC" + }, + "SD": { + "description": "Surge drum", + "code": "SD" + } + } + }, + "CS": { + "name": "Subsea production control", + "code": "CS", + "annex": "A.2.6.1", + "types": { + "DH": { + "description": "Direct hydraulic", + "code": "DH" + }, + "EH": { + "description": "Direct electro-hydraulic", + "code": "EH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + }, + "PH": { + "description": "Discrete pilot hydraulic", + "code": "PH" + }, + "SH": { + "description": "Sequential piloted hydraulic", + "code": "SH" + }, + "TH": { + "description": "Telemetric hydraulic", + "code": "TH" + } + } + }, + "SP": { + "name": "Subsea pumps", + "code": "SP", + "annex": "A.2.6.4", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + } + } + }, + "TM": { + "name": "Subsea templates", + "code": "TM", + "annex": null, + "types": {} + }, + "XT": { + "name": "Subsea wellhead and X-mas trees", + "code": "XT", + "annex": "A.2.6.2", + "types": { + "VX": { + "description": "Vertical", + "code": "VX" + }, + "HX": { + "description": "Horizontal", + "code": "HX" + } + } + } + } + }, + "Well completion": { + "name": "Well completion", + "classes": { + "SS": { + "name": "Downhole safety valves", + "code": "SS", + "annex": "A.2.7.2", + "types": {} + }, + "WE": { + "name": "Downhole well completion", + "code": "WE", + "annex": "A.2.7.2", + "types": {} + }, + "ESP": { + "name": "Electrical submersible pumps", + "code": "ESP", + "annex": "A.2.7.2", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + }, + "AC": { + "description": "Alternative current", + "code": "AC" + } + } + }, + "XD": { + "name": "Surface wellhead and X-mas trees", + "code": "XD", + "annex": "A.2.7.7", + "types": { + "VE": { + "description": "Vertical", + "code": "VE" + }, + "HO": { + "description": "Horizontal", + "code": "HO" + } + } + } + } + }, + "Drilling": { + "name": "Drilling", + "classes": { + "CG": { + "name": "Cementing equipment", + "code": "CG", + "annex": null, + "types": {} + }, + "DC": { + "name": "Choke and manifolds", + "code": "DC", + "annex": null, + "types": {} + }, + "TB": { + "name": "Crown and travelling blocks", + "code": "TB", + "annex": null, + "types": {} + }, + "DE": { + "name": "Derrick", + "code": "DE", + "annex": null, + "types": {} + }, + "DI": { + "name": "Diverters", + "code": "DI", + "annex": null, + "types": {} + }, + "DW": { + "name": "Drawworks", + "code": "DW", + "annex": null, + "types": {} + }, + "DD": { + "name": "Drilling and completion risers", + "code": "DD", + "annex": null, + "types": {} + }, + "DS": { + "name": "Drill strings", + "code": "DS", + "annex": null, + "types": {} + }, + "DM": { + "name": "Mud-treatment equipment", + "code": "DM", + "annex": null, + "types": {} + }, + "DH": { + "name": "Pipe handling equipment", + "code": "DH", + "annex": null, + "types": {} + }, + "DR": { + "name": "Riser compensators", + "code": "DR", + "annex": null, + "types": {} + }, + "MC": { + "name": "String-motion compensators", + "code": "MC", + "annex": null, + "types": {} + }, + "BO": { + "name": "Subsea blowout preventers (BOP) - (Floating)", + "code": "BO", + "annex": "A.2.8.2", + "types": { + "PH": { + "description": "Piloted hydraulic", + "code": "PH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + } + } + }, + "BT": { + "name": "Surface blowout preventers (BOP) - (Fixed)", + "code": "BT", + "annex": "A.2.8.3", + "types": { + "PH": { + "description": "Piloted hydraulic", + "code": "PH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + } + } + }, + "TD": { + "name": "Top drives", + "code": "TD", + "annex": "A.2.8.1", + "types": { + "HD": { + "description": "Hydraulically driven", + "code": "HD" + }, + "ED": { + "description": "Electrically driven", + "code": "ED" + } + } + } + } + }, + "Well Intervention": { + "name": "Well Intervention", + "classes": { + "W1": { + "name": "Coiled tubing, surface equipment ", + "code": "W1", + "annex": null, + "types": {} + }, + "WC": { + "name": "Coiled tubing, surface well control equipment", + "code": "WC", + "annex": "A.2.9.1", + "types": { + "W1": { + "description": "Coiled tubing", + "code": "W1" + }, + "W2": { + "description": "Snubbing", + "code": "W2" + }, + "W3": { + "description": "Wireline", + "code": "W3" + } + } + }, + "W2": { + "name": "Coiled tubing, work strings ", + "code": "W2", + "annex": null, + "types": {} + }, + "W3": { + "name": "Coiled tubing, bottom-hole assemblies ", + "code": "W3", + "annex": null, + "types": {} + }, + "ΟΙ": { + "name": "Subsea well intervention", + "code": "ΟΙ", + "annex": "A.2.9.2", + "types": { + "WC": { + "description": "Well completion", + "code": "WC" + }, + "WI": { + "description": "Well intervention – open sea (tree mode)", + "code": "WI" + }, + "WO": { + "description": "Full workover (tree mode)", + "code": "WO" + } + } + } + } + }, + "Marine": { + "name": "Marine", + "classes": { + "AM": { + "name": "Anchor windlasses and mooring equipment", + "code": "AM", + "annex": null, + "types": {} + }, + "IC": { + "name": "De-icing equipment", + "code": "IC", + "annex": null, + "types": {} + }, + "DP": { + "name": "Dynamic positioning equipment", + "code": "DP", + "annex": null, + "types": {} + }, + "HT": { + "name": "Helicopter deck with equipment", + "code": "HT", + "annex": null, + "types": {} + }, + "JF": { + "name": "Jacking and fixation", + "code": "JF", + "annex": "A.2.10.1", + "types": { + "TL": { + "description": "Open-truss legs", + "code": "TL" + }, + "CL": { + "description": "Columnar legs", + "code": "CL" + } + } + }, + "MD": { + "name": "Marine disconnection equipment", + "code": "MD", + "annex": null, + "types": {} + }, + "TH": { + "name": "Thrusters", + "code": "TH", + "annex": null, + "types": {} + }, + "TO": { + "name": "Towing equipment", + "code": "TO", + "annex": null, + "types": {} + } + } + }, + "Utilities": { + "name": "Utilities", + "classes": { + "AI": { + "name": "Air-supply equipment", + "code": "AI", + "annex": null, + "types": {} + }, + "SU": { + "name": "De-superheaters", + "code": "SU", + "annex": null, + "types": {} + }, + "FE": { + "name": "Flare ignition equipment", + "code": "FE", + "annex": null, + "types": {} + }, + "HC": { + "name": "Heating/cooling media", + "code": "HC", + "annex": null, + "types": {} + }, + "HP": { + "name": "Hydraulic power units", + "code": "HP", + "annex": null, + "types": {} + }, + "NI": { + "name": "Nitrogen-supply equipment", + "code": "NI", + "annex": null, + "types": {} + }, + "OC": { + "name": "Open/Close drain equipment", + "code": "OC", + "annex": null, + "types": {} + } + } + }, + "Auxiliaries": { + "name": "Auxiliaries", + "classes": { + "HV": { + "name": "HVAC equipment", + "code": "HV", + "annex": null, + "types": {} + } + } + } + } + }, + "indices": { + "by_class_code": { + "BL": { + "category": "Rotating", + "class": { + "name": "Blowers and fans", + "code": "BL", + "annex": null, + "types": {} + } + }, + "CF": { + "category": "Rotating", + "class": { + "name": "Centrifuges", + "code": "CF", + "annex": null, + "types": {} + } + }, + "CE": { + "category": "Rotating", + "class": { + "name": "Combustion engines", + "code": "CE", + "annex": "A.2.2.1", + "types": { + "DE": { + "description": "Diesel engine", + "code": "DE" + }, + "GE": { + "description": "Otto (gas) engine", + "code": "GE" + } + } + } + }, + "CO": { + "category": "Rotating", + "class": { + "name": "Compressors", + "code": "CO", + "annex": "A.2.2.2", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "SC": { + "description": "Screw", + "code": "SC" + }, + "AX": { + "description": "Axial", + "code": "AX" + } + } + } + }, + "EG": { + "category": "Rotating", + "class": { + "name": "Electric generators", + "code": "EG", + "annex": "A.2.2.3", + "types": { + "TD": { + "description": "Gas-turbine driven", + "code": "TD" + }, + "SD": { + "description": "Steam-turbine driven", + "code": "SD" + }, + "TE": { + "description": "Turboexpander", + "code": "TE" + }, + "MD": { + "description": "Engine driven, e.g. diesel engine, gas engine", + "code": "MD" + } + } + } + }, + "EM": { + "category": "Rotating", + "class": { + "name": "Electric motors", + "code": "EM", + "annex": "A.2.2.4", + "types": { + "AC": { + "description": "Alternating current", + "code": "AC" + }, + "DC": { + "description": "Direct current", + "code": "DC" + } + } + } + }, + "GT": { + "category": "Rotating", + "class": { + "name": "Gas turbines", + "code": "GT", + "annex": "A.2.2.5", + "types": { + "IN": { + "description": "Industrial", + "code": "IN" + }, + "AD": { + "description": "Aero-derivative", + "code": "AD" + }, + "HD": { + "description": "Heavy duty", + "code": "HD" + } + } + } + }, + "LE": { + "category": "Rotating", + "class": { + "name": "Liquid expanders", + "code": "LE", + "annex": null, + "types": {} + } + }, + "MI": { + "category": "Rotating", + "class": { + "name": "Mixers", + "code": "MI", + "annex": null, + "types": {} + } + }, + "PU": { + "category": "Rotating", + "class": { + "name": "Pumps", + "code": "PU", + "annex": "A.2.2.6", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + } + } + } + }, + "ST": { + "category": "Rotating", + "class": { + "name": "Steam turbines", + "code": "ST", + "annex": "A.2.2.7", + "types": { + "MS": { + "description": "Multi-stage", + "code": "MS" + }, + "SS": { + "description": "Single-stage", + "code": "SS" + } + } + } + }, + "TE": { + "category": "Rotating", + "class": { + "name": "Turboexpanders", + "code": "TE", + "annex": "A.2.2.8", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "AX": { + "description": "Axial", + "code": "AX" + } + } + } + }, + "CV": { + "category": "Mechanical", + "class": { + "name": "Conveyors and elevators", + "code": "CV", + "annex": null, + "types": {} + } + }, + "CR": { + "category": "Mechanical", + "class": { + "name": "Cranes", + "code": "CR", + "annex": "A.2.3.1", + "types": { + "HO": { + "description": "Electro-hydraulic operated", + "code": "HO" + }, + "DO": { + "description": "Diesel hydraulic operated", + "code": "DO" + } + } + } + }, + "FS": { + "category": "Mechanical", + "class": { + "name": "Filters and strainers", + "code": "FS", + "annex": null, + "types": {} + } + }, + "HE": { + "category": "Mechanical", + "class": { + "name": "Heat exchangers", + "code": "HE", + "annex": "A.2.3.2", + "types": { + "ST": { + "description": "Shell and tube", + "code": "ST" + }, + "P": { + "description": "Plate", + "code": "P" + }, + "PF": { + "description": "Plate fin", + "code": "PF" + }, + "DP": { + "description": "Double pipe", + "code": "DP" + }, + "BY": { + "description": "Bayonet", + "code": "BY" + }, + "PC": { + "description": "Printed circuit", + "code": "PC" + }, + "AC": { + "description": "Air-cooled", + "code": "AC" + }, + "S": { + "description": "Spiral", + "code": "S" + }, + "SW": { + "description": "Spiral-wound", + "code": "SW" + } + } + } + }, + "HB": { + "category": "Mechanical", + "class": { + "name": "Heaters and boilers", + "code": "HB", + "annex": "A.2.3.3", + "types": { + "DF": { + "description": "Direct-fired heater", + "code": "DF" + }, + "EH": { + "description": "Electric heater", + "code": "EH" + }, + "IF": { + "description": "Indirect HC-fired heater", + "code": "IF" + }, + "HT": { + "description": "Heater treater", + "code": "HT" + }, + "NF": { + "description": "Non-HC-fired boiler", + "code": "NF" + }, + "EB": { + "description": "Electric boiler", + "code": "EB" + }, + "FB": { + "description": "HC-fired boiler", + "code": "FB" + } + } + } + }, + "LA": { + "category": "Mechanical", + "class": { + "name": "Loading arms", + "code": "LA", + "annex": null, + "types": {} + } + }, + "PL": { + "category": "Mechanical", + "class": { + "name": "Onshore pipelines", + "code": "PL", + "annex": null, + "types": {} + } + }, + "PI": { + "category": "Mechanical", + "class": { + "name": "Piping", + "code": "PI", + "annex": "A.2.3.5", + "types": { + "CA": { + "description": "Carbon steels", + "code": "CA" + }, + "ST": { + "description": "Stainless steels", + "code": "ST" + }, + "LO": { + "description": "High-strength low-alloy steels", + "code": "LO" + }, + "TI": { + "description": "Titanium", + "code": "TI" + }, + "PO": { + "description": "Polymers including fibre-reinforced", + "code": "PO" + } + } + } + }, + "VE": { + "category": "Mechanical", + "class": { + "name": "Pressure vessels", + "code": "VE", + "annex": "A.2.3.4", + "types": { + "SP": { + "description": "Stripper", + "code": "SP" + }, + "SE": { + "description": "Separator", + "code": "SE" + }, + "CA": { + "description": "Coalescer", + "code": "CA" + }, + "FD": { + "description": "Flash drum", + "code": "FD" + }, + "SB": { + "description": "Scrubber", + "code": "SB" + }, + "CO": { + "description": "Contactor", + "code": "CO" + }, + "SD": { + "description": "Surge drum", + "code": "SD" + }, + "CY": { + "description": "Cyclone", + "code": "CY" + }, + "HY": { + "description": "Hydrocyclone", + "code": "HY" + }, + "SC": { + "description": "Slug catcher", + "code": "SC" + }, + "AD": { + "description": "Adsorber", + "code": "AD" + }, + "DR": { + "description": "Dryer", + "code": "DR" + }, + "PT": { + "description": "Pig trap", + "code": "PT" + }, + "DC": { + "description": "Distillation column", + "code": "DC" + }, + "SA": { + "description": "Saturator", + "code": "SA" + }, + "RE": { + "description": "Reactor", + "code": "RE" + }, + "DA": { + "description": "De-aerator", + "code": "DA" + } + } + } + }, + "SI": { + "category": "Mechanical", + "class": { + "name": "Silos", + "code": "SI", + "annex": null, + "types": {} + } + }, + "SE": { + "category": "Mechanical", + "class": { + "name": "Steam ejectors", + "code": "SE", + "annex": null, + "types": {} + } + }, + "TA": { + "category": "Mechanical", + "class": { + "name": "Storage Tanks", + "code": "TA", + "annex": "A.2.3.9", + "types": { + "FR": { + "description": "Fixed-Roof", + "code": "FR" + }, + "LR": { + "description": "Lifting Roof", + "code": "LR" + }, + "DP": { + "description": "Diaphragm", + "code": "DP" + }, + "EF": { + "description": "External Floating Roof", + "code": "EF" + }, + "RL": { + "description": "Roofless", + "code": "RL" + }, + "IF": { + "description": "Fixed Roof with Internal Floating Roof", + "code": "IF" + } + } + } + }, + "SW": { + "category": "Mechanical", + "class": { + "name": "Swivels", + "code": "SW", + "annex": "A.2.3.8", + "types": { + "AX": { + "description": "Axial", + "code": "AX" + }, + "TO": { + "description": "Toroidal", + "code": "TO" + }, + "ES": { + "description": "Electric/signal", + "code": "ES" + } + } + } + }, + "TU": { + "category": "Mechanical", + "class": { + "name": "Turrets", + "code": "TU", + "annex": "A.2.3.7", + "types": { + "DT": { + "description": "Disconnectable turrets", + "code": "DT" + }, + "PT": { + "description": "Permanent turrets", + "code": "PT" + } + } + } + }, + "WI": { + "category": "Mechanical", + "class": { + "name": "Winches", + "code": "WI", + "annex": "A.2.3.6", + "types": { + "EW": { + "description": "Electric winch", + "code": "EW" + }, + "HW": { + "description": "Hydraulic winch", + "code": "HW" + } + } + } + }, + "FC": { + "category": "Electrical", + "class": { + "name": "Frequency converters", + "code": "FC", + "annex": "A.2.4.4", + "types": { + "LV": { + "description": "Low voltage", + "code": "LV" + }, + "HV": { + "description": "High voltage", + "code": "HV" + } + } + } + }, + "PC": { + "category": "Electrical", + "class": { + "name": "Power cables and terminations", + "code": "PC", + "annex": null, + "types": {} + } + }, + "PT": { + "category": "Electrical", + "class": { + "name": "Power transformers", + "code": "PT", + "annex": "A.2.4.2", + "types": { + "OT": { + "description": "Oil immersed", + "code": "OT" + }, + "DT": { + "description": "Dry", + "code": "DT" + } + } + } + }, + "SG": { + "category": "Electrical", + "class": { + "name": "Switchgears", + "code": "SG", + "annex": "A.2.4.3", + "types": { + "LV": { + "description": "Low voltage", + "code": "LV" + }, + "OV": { + "description": "Oil and vacuum insulated", + "code": "OV" + }, + "HA": { + "description": "High voltage air insulated", + "code": "HA" + }, + "HG": { + "description": "High voltage gas insulated", + "code": "HG" + } + } + } + }, + "UP": { + "category": "Electrical", + "class": { + "name": "Uninterruptible power supply", + "code": "UP", + "annex": "A.2.4.1", + "types": { + "UB": { + "description": "Dual UPS with standby bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "UB" + }, + "UD": { + "description": "Dual UPS without bypass Rectifier supplied from emergency power", + "code": "UD" + }, + "US": { + "description": "Single UPS with bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "US" + }, + "UT": { + "description": "Single UPS without bypass Rectifier supplied from emergency power", + "code": "UT" + } + } + } + }, + "CL": { + "category": "Safety and control", + "class": { + "name": "Control logic units", + "code": "CL", + "annex": "A.2.5.3", + "types": { + "LC": { + "description": "Programmable logic controller (PLC)", + "code": "LC" + }, + "PC": { + "description": "Computer", + "code": "PC" + }, + "DC": { + "description": "Distributed control unit", + "code": "DC" + }, + "RL": { + "description": "Relay", + "code": "RL" + }, + "SS": { + "description": "Solid state", + "code": "SS" + }, + "SL": { + "description": "Single-loop controller", + "code": "SL" + }, + "PA": { + "description": "Programmable automation controller (PAC)", + "code": "PA" + } + } + } + }, + "EC": { + "category": "Safety and control", + "class": { + "name": "Emergency communication equipment", + "code": "EC", + "annex": null, + "types": {} + } + }, + "ER": { + "category": "Safety and control", + "class": { + "name": "Escape, evacuation and rescue ", + "code": "ER", + "annex": null, + "types": {} + } + }, + "FG": { + "category": "Safety and control", + "class": { + "name": "Fire and gas detectors", + "code": "FG", + "annex": "A.2.5.1", + "types": { + "BS": { + "description": "Smoke/Combustion", + "code": "BS" + }, + "BH": { + "description": "Heat", + "code": "BH" + }, + "BF": { + "description": "Flame", + "code": "BF" + }, + "BM": { + "description": "Manual pushbutton", + "code": "BM" + }, + "BA": { + "description": "Others (Fire)", + "code": "BA" + }, + "AB": { + "description": "Hydrocarbon", + "code": "AB" + }, + "AS": { + "description": "Toxic gases", + "code": "AS" + }, + "AO": { + "description": "Others (Gas)", + "code": "AO" + } + } + } + }, + "FF": { + "category": "Safety and control", + "class": { + "name": "Fire-fighting equipment", + "code": "FF", + "annex": null, + "types": {} + } + }, + "FI": { + "category": "Safety and control", + "class": { + "name": "Flare ignition", + "code": "FI", + "annex": null, + "types": {} + } + }, + "IG": { + "category": "Safety and control", + "class": { + "name": "Inert-gas equipment", + "code": "IG", + "annex": null, + "types": {} + } + }, + "IP": { + "category": "Safety and control", + "class": { + "name": "Input devices", + "code": "IP", + "annex": "A.2.5.2", + "types": { + "PS": { + "description": "Pressure", + "code": "PS" + }, + "LS": { + "description": "Level", + "code": "LS" + }, + "TS": { + "description": "Temperature", + "code": "TS" + }, + "FS": { + "description": "Flow", + "code": "FS" + }, + "SP": { + "description": "Speed", + "code": "SP" + }, + "VI": { + "description": "Vibration", + "code": "VI" + }, + "DI": { + "description": "Displacement", + "code": "DI" + }, + "AN": { + "description": "Analyser", + "code": "AN" + }, + "WE": { + "description": "Weight", + "code": "WE" + }, + "CO": { + "description": "Corrosion", + "code": "CO" + }, + "LP": { + "description": "Limit switch", + "code": "LP" + }, + "PB": { + "description": "On/off (pushbutton)", + "code": "PB" + }, + "OT": { + "description": "Others", + "code": "OT" + } + } + } + }, + "LB": { + "category": "Safety and control", + "class": { + "name": "Lifeboats", + "code": "LB", + "annex": "A.2.5.6", + "types": { + "FF": { + "description": "Free fall", + "code": "FF" + }, + "DL": { + "description": "Davit launched", + "code": "DL" + } + } + } + }, + "NO": { + "category": "Safety and control", + "class": { + "name": "Nozzles", + "code": "NO", + "annex": "A.2.5.5", + "types": { + "DN": { + "description": "Deluge", + "code": "DN" + }, + "SR": { + "description": "Sprinkler", + "code": "SR" + }, + "WM": { + "description": "Water mist", + "code": "WM" + }, + "GA": { + "description": "Gaseous", + "code": "GA" + } + } + } + }, + "TC": { + "category": "Safety and control", + "class": { + "name": "Telecommunications", + "code": "TC", + "annex": null, + "types": {} + } + }, + "VA": { + "category": "Safety and control", + "class": { + "name": "Valves", + "code": "VA", + "annex": "A.2.5.4", + "types": { + "BA": { + "description": "Ball", + "code": "BA" + }, + "GA": { + "description": "Gate", + "code": "GA" + }, + "GL": { + "description": "Globe", + "code": "GL" + }, + "BP": { + "description": "Butterfly", + "code": "BP" + }, + "PG": { + "description": "Plug", + "code": "PG" + }, + "NE": { + "description": "Needle", + "code": "NE" + }, + "CH": { + "description": "Check", + "code": "CH" + }, + "DI": { + "description": "Disc", + "code": "DI" + }, + "FL": { + "description": "Flapper", + "code": "FL" + }, + "MO": { + "description": "Multiple orifice", + "code": "MO" + }, + "WA": { + "description": "Three-way", + "code": "WA" + }, + "SC": { + "description": "PSV-conventional", + "code": "SC" + }, + "SB": { + "description": "PSV-conventional with bellow", + "code": "SB" + }, + "SP": { + "description": "PSV-pilot operated", + "code": "SP" + }, + "SV": { + "description": "PSV-vacuum relief", + "code": "SV" + }, + "PC": { + "description": "Plug and cage", + "code": "PC" + }, + "ES": { + "description": "External sleeve", + "code": "ES" + }, + "AF": { + "description": "Axial flow", + "code": "AF" + }, + "PI": { + "description": "Pinch", + "code": "PI" + }, + "OH": { + "description": "Others", + "code": "OH" + } + } + } + }, + "DT": { + "category": "Subsea", + "class": { + "name": "Dry tree risers", + "code": "DT", + "annex": null, + "types": {} + } + }, + "PR": { + "category": "Subsea", + "class": { + "name": "Risers", + "code": "PR", + "annex": "A.2.6.3", + "types": { + "RI": { + "description": "Rigid", + "code": "RI" + }, + "FL": { + "description": "Flexible", + "code": "FL" + } + } + } + }, + "SC": { + "category": "Subsea", + "class": { + "name": "Subsea compressors", + "code": "SC", + "annex": null, + "types": {} + } + }, + "SD": { + "category": "Subsea", + "class": { + "name": "Subsea diving equipment", + "code": "SD", + "annex": null, + "types": {} + } + }, + "EP": { + "category": "Subsea", + "class": { + "name": "Subsea electrical power", + "code": "EP", + "annex": "A.2.6.5", + "types": { + "SU": { + "description": "Single consumer without subsea step-down", + "code": "SU" + }, + "SD": { + "description": "Single consumer with subsea step-down", + "code": "SD" + }, + "MC": { + "description": "Multiple consumer", + "code": "MC" + } + } + } + }, + "FL": { + "category": "Subsea", + "class": { + "name": "Subsea flowlines", + "code": "FL", + "annex": null, + "types": {} + } + }, + "SH": { + "category": "Subsea", + "class": { + "name": "Subsea heat exchangers", + "code": "SH", + "annex": null, + "types": {} + } + }, + "CI": { + "category": "Subsea", + "class": { + "name": "Subsea intervention", + "code": "CI", + "annex": null, + "types": {} + } + }, + "MA": { + "category": "Subsea", + "class": { + "name": "Subsea manifolds", + "code": "MA", + "annex": null, + "types": {} + } + }, + "SL": { + "category": "Subsea", + "class": { + "name": "Subsea pipelines", + "code": "SL", + "annex": "A.2.6.7", + "types": { + "FL": { + "description": "Flexible", + "code": "FL" + }, + "RI": { + "description": "Rigid", + "code": "RI" + } + } + } + }, + "CA": { + "category": "Subsea", + "class": { + "name": "Submarine power cables", + "code": "CA", + "annex": null, + "types": {} + } + }, + "SV": { + "category": "Subsea", + "class": { + "name": "Subsea pressure vessels", + "code": "SV", + "annex": "A.2.6.6", + "types": { + "CA": { + "description": "Coalescer", + "code": "CA" + }, + "CY": { + "description": "Cyclone", + "code": "CY" + }, + "HY": { + "description": "Hydrocyclone", + "code": "HY" + }, + "SB": { + "description": "Scrubber", + "code": "SB" + }, + "SE": { + "description": "Separator", + "code": "SE" + }, + "SC": { + "description": "Slug catcher", + "code": "SC" + }, + "SD": { + "description": "Surge drum", + "code": "SD" + } + } + } + }, + "CS": { + "category": "Subsea", + "class": { + "name": "Subsea production control", + "code": "CS", + "annex": "A.2.6.1", + "types": { + "DH": { + "description": "Direct hydraulic", + "code": "DH" + }, + "EH": { + "description": "Direct electro-hydraulic", + "code": "EH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + }, + "PH": { + "description": "Discrete pilot hydraulic", + "code": "PH" + }, + "SH": { + "description": "Sequential piloted hydraulic", + "code": "SH" + }, + "TH": { + "description": "Telemetric hydraulic", + "code": "TH" + } + } + } + }, + "SP": { + "category": "Subsea", + "class": { + "name": "Subsea pumps", + "code": "SP", + "annex": "A.2.6.4", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RE": { + "description": "Reciprocating", + "code": "RE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + } + } + } + }, + "TM": { + "category": "Subsea", + "class": { + "name": "Subsea templates", + "code": "TM", + "annex": null, + "types": {} + } + }, + "XT": { + "category": "Subsea", + "class": { + "name": "Subsea wellhead and X-mas trees", + "code": "XT", + "annex": "A.2.6.2", + "types": { + "VX": { + "description": "Vertical", + "code": "VX" + }, + "HX": { + "description": "Horizontal", + "code": "HX" + } + } + } + }, + "SS": { + "category": "Well completion", + "class": { + "name": "Downhole safety valves", + "code": "SS", + "annex": "A.2.7.2", + "types": {} + } + }, + "WE": { + "category": "Well completion", + "class": { + "name": "Downhole well completion", + "code": "WE", + "annex": "A.2.7.2", + "types": {} + } + }, + "ESP": { + "category": "Well completion", + "class": { + "name": "Electrical submersible pumps", + "code": "ESP", + "annex": "A.2.7.2", + "types": { + "CE": { + "description": "Centrifugal", + "code": "CE" + }, + "RO": { + "description": "Rotary", + "code": "RO" + }, + "AC": { + "description": "Alternative current", + "code": "AC" + } + } + } + }, + "XD": { + "category": "Well completion", + "class": { + "name": "Surface wellhead and X-mas trees", + "code": "XD", + "annex": "A.2.7.7", + "types": { + "VE": { + "description": "Vertical", + "code": "VE" + }, + "HO": { + "description": "Horizontal", + "code": "HO" + } + } + } + }, + "CG": { + "category": "Drilling", + "class": { + "name": "Cementing equipment", + "code": "CG", + "annex": null, + "types": {} + } + }, + "DC": { + "category": "Drilling", + "class": { + "name": "Choke and manifolds", + "code": "DC", + "annex": null, + "types": {} + } + }, + "TB": { + "category": "Drilling", + "class": { + "name": "Crown and travelling blocks", + "code": "TB", + "annex": null, + "types": {} + } + }, + "DE": { + "category": "Drilling", + "class": { + "name": "Derrick", + "code": "DE", + "annex": null, + "types": {} + } + }, + "DI": { + "category": "Drilling", + "class": { + "name": "Diverters", + "code": "DI", + "annex": null, + "types": {} + } + }, + "DW": { + "category": "Drilling", + "class": { + "name": "Drawworks", + "code": "DW", + "annex": null, + "types": {} + } + }, + "DD": { + "category": "Drilling", + "class": { + "name": "Drilling and completion risers", + "code": "DD", + "annex": null, + "types": {} + } + }, + "DS": { + "category": "Drilling", + "class": { + "name": "Drill strings", + "code": "DS", + "annex": null, + "types": {} + } + }, + "DM": { + "category": "Drilling", + "class": { + "name": "Mud-treatment equipment", + "code": "DM", + "annex": null, + "types": {} + } + }, + "DH": { + "category": "Drilling", + "class": { + "name": "Pipe handling equipment", + "code": "DH", + "annex": null, + "types": {} + } + }, + "DR": { + "category": "Drilling", + "class": { + "name": "Riser compensators", + "code": "DR", + "annex": null, + "types": {} + } + }, + "MC": { + "category": "Drilling", + "class": { + "name": "String-motion compensators", + "code": "MC", + "annex": null, + "types": {} + } + }, + "BO": { + "category": "Drilling", + "class": { + "name": "Subsea blowout preventers (BOP) - (Floating)", + "code": "BO", + "annex": "A.2.8.2", + "types": { + "PH": { + "description": "Piloted hydraulic", + "code": "PH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + } + } + } + }, + "BT": { + "category": "Drilling", + "class": { + "name": "Surface blowout preventers (BOP) - (Fixed)", + "code": "BT", + "annex": "A.2.8.3", + "types": { + "PH": { + "description": "Piloted hydraulic", + "code": "PH" + }, + "MX": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + } + } + } + }, + "TD": { + "category": "Drilling", + "class": { + "name": "Top drives", + "code": "TD", + "annex": "A.2.8.1", + "types": { + "HD": { + "description": "Hydraulically driven", + "code": "HD" + }, + "ED": { + "description": "Electrically driven", + "code": "ED" + } + } + } + }, + "W1": { + "category": "Well Intervention", + "class": { + "name": "Coiled tubing, surface equipment ", + "code": "W1", + "annex": null, + "types": {} + } + }, + "WC": { + "category": "Well Intervention", + "class": { + "name": "Coiled tubing, surface well control equipment", + "code": "WC", + "annex": "A.2.9.1", + "types": { + "W1": { + "description": "Coiled tubing", + "code": "W1" + }, + "W2": { + "description": "Snubbing", + "code": "W2" + }, + "W3": { + "description": "Wireline", + "code": "W3" + } + } + } + }, + "W2": { + "category": "Well Intervention", + "class": { + "name": "Coiled tubing, work strings ", + "code": "W2", + "annex": null, + "types": {} + } + }, + "W3": { + "category": "Well Intervention", + "class": { + "name": "Coiled tubing, bottom-hole assemblies ", + "code": "W3", + "annex": null, + "types": {} + } + }, + "ΟΙ": { + "category": "Well Intervention", + "class": { + "name": "Subsea well intervention", + "code": "ΟΙ", + "annex": "A.2.9.2", + "types": { + "WC": { + "description": "Well completion", + "code": "WC" + }, + "WI": { + "description": "Well intervention – open sea (tree mode)", + "code": "WI" + }, + "WO": { + "description": "Full workover (tree mode)", + "code": "WO" + } + } + } + }, + "AM": { + "category": "Marine", + "class": { + "name": "Anchor windlasses and mooring equipment", + "code": "AM", + "annex": null, + "types": {} + } + }, + "IC": { + "category": "Marine", + "class": { + "name": "De-icing equipment", + "code": "IC", + "annex": null, + "types": {} + } + }, + "DP": { + "category": "Marine", + "class": { + "name": "Dynamic positioning equipment", + "code": "DP", + "annex": null, + "types": {} + } + }, + "HT": { + "category": "Marine", + "class": { + "name": "Helicopter deck with equipment", + "code": "HT", + "annex": null, + "types": {} + } + }, + "JF": { + "category": "Marine", + "class": { + "name": "Jacking and fixation", + "code": "JF", + "annex": "A.2.10.1", + "types": { + "TL": { + "description": "Open-truss legs", + "code": "TL" + }, + "CL": { + "description": "Columnar legs", + "code": "CL" + } + } + } + }, + "MD": { + "category": "Marine", + "class": { + "name": "Marine disconnection equipment", + "code": "MD", + "annex": null, + "types": {} + } + }, + "TH": { + "category": "Marine", + "class": { + "name": "Thrusters", + "code": "TH", + "annex": null, + "types": {} + } + }, + "TO": { + "category": "Marine", + "class": { + "name": "Towing equipment", + "code": "TO", + "annex": null, + "types": {} + } + }, + "AI": { + "category": "Utilities", + "class": { + "name": "Air-supply equipment", + "code": "AI", + "annex": null, + "types": {} + } + }, + "SU": { + "category": "Utilities", + "class": { + "name": "De-superheaters", + "code": "SU", + "annex": null, + "types": {} + } + }, + "FE": { + "category": "Utilities", + "class": { + "name": "Flare ignition equipment", + "code": "FE", + "annex": null, + "types": {} + } + }, + "HC": { + "category": "Utilities", + "class": { + "name": "Heating/cooling media", + "code": "HC", + "annex": null, + "types": {} + } + }, + "HP": { + "category": "Utilities", + "class": { + "name": "Hydraulic power units", + "code": "HP", + "annex": null, + "types": {} + } + }, + "NI": { + "category": "Utilities", + "class": { + "name": "Nitrogen-supply equipment", + "code": "NI", + "annex": null, + "types": {} + } + }, + "OC": { + "category": "Utilities", + "class": { + "name": "Open/Close drain equipment", + "code": "OC", + "annex": null, + "types": {} + } + }, + "HV": { + "category": "Auxiliaries", + "class": { + "name": "HVAC equipment", + "code": "HV", + "annex": null, + "types": {} + } + } + }, + "by_type_code": { + "DE": { + "category": "Rotating", + "class_code": "CE", + "class_name": "Combustion engines", + "type": { + "description": "Diesel engine", + "code": "DE" + } + }, + "GE": { + "category": "Rotating", + "class_code": "CE", + "class_name": "Combustion engines", + "type": { + "description": "Otto (gas) engine", + "code": "GE" + } + }, + "CE": { + "category": "Well completion", + "class_code": "ESP", + "class_name": "Electrical submersible pumps", + "type": { + "description": "Centrifugal", + "code": "CE" + } + }, + "RE": { + "category": "Subsea", + "class_code": "SP", + "class_name": "Subsea pumps", + "type": { + "description": "Reciprocating", + "code": "RE" + } + }, + "SC": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Slug catcher", + "code": "SC" + } + }, + "AX": { + "category": "Mechanical", + "class_code": "SW", + "class_name": "Swivels", + "type": { + "description": "Axial", + "code": "AX" + } + }, + "TD": { + "category": "Rotating", + "class_code": "EG", + "class_name": "Electric generators", + "type": { + "description": "Gas-turbine driven", + "code": "TD" + } + }, + "SD": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Surge drum", + "code": "SD" + } + }, + "TE": { + "category": "Rotating", + "class_code": "EG", + "class_name": "Electric generators", + "type": { + "description": "Turboexpander", + "code": "TE" + } + }, + "MD": { + "category": "Rotating", + "class_code": "EG", + "class_name": "Electric generators", + "type": { + "description": "Engine driven, e.g. diesel engine, gas engine", + "code": "MD" + } + }, + "AC": { + "category": "Well completion", + "class_code": "ESP", + "class_name": "Electrical submersible pumps", + "type": { + "description": "Alternative current", + "code": "AC" + } + }, + "DC": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Distributed control unit", + "code": "DC" + } + }, + "IN": { + "category": "Rotating", + "class_code": "GT", + "class_name": "Gas turbines", + "type": { + "description": "Industrial", + "code": "IN" + } + }, + "AD": { + "category": "Mechanical", + "class_code": "VE", + "class_name": "Pressure vessels", + "type": { + "description": "Adsorber", + "code": "AD" + } + }, + "HD": { + "category": "Drilling", + "class_code": "TD", + "class_name": "Top drives", + "type": { + "description": "Hydraulically driven", + "code": "HD" + } + }, + "RO": { + "category": "Well completion", + "class_code": "ESP", + "class_name": "Electrical submersible pumps", + "type": { + "description": "Rotary", + "code": "RO" + } + }, + "MS": { + "category": "Rotating", + "class_code": "ST", + "class_name": "Steam turbines", + "type": { + "description": "Multi-stage", + "code": "MS" + } + }, + "SS": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Solid state", + "code": "SS" + } + }, + "HO": { + "category": "Well completion", + "class_code": "XD", + "class_name": "Surface wellhead and X-mas trees", + "type": { + "description": "Horizontal", + "code": "HO" + } + }, + "DO": { + "category": "Mechanical", + "class_code": "CR", + "class_name": "Cranes", + "type": { + "description": "Diesel hydraulic operated", + "code": "DO" + } + }, + "ST": { + "category": "Mechanical", + "class_code": "PI", + "class_name": "Piping", + "type": { + "description": "Stainless steels", + "code": "ST" + } + }, + "P": { + "category": "Mechanical", + "class_code": "HE", + "class_name": "Heat exchangers", + "type": { + "description": "Plate", + "code": "P" + } + }, + "PF": { + "category": "Mechanical", + "class_code": "HE", + "class_name": "Heat exchangers", + "type": { + "description": "Plate fin", + "code": "PF" + } + }, + "DP": { + "category": "Mechanical", + "class_code": "TA", + "class_name": "Storage Tanks", + "type": { + "description": "Diaphragm", + "code": "DP" + } + }, + "BY": { + "category": "Mechanical", + "class_code": "HE", + "class_name": "Heat exchangers", + "type": { + "description": "Bayonet", + "code": "BY" + } + }, + "PC": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Plug and cage", + "code": "PC" + } + }, + "S": { + "category": "Mechanical", + "class_code": "HE", + "class_name": "Heat exchangers", + "type": { + "description": "Spiral", + "code": "S" + } + }, + "SW": { + "category": "Mechanical", + "class_code": "HE", + "class_name": "Heat exchangers", + "type": { + "description": "Spiral-wound", + "code": "SW" + } + }, + "DF": { + "category": "Mechanical", + "class_code": "HB", + "class_name": "Heaters and boilers", + "type": { + "description": "Direct-fired heater", + "code": "DF" + } + }, + "EH": { + "category": "Subsea", + "class_code": "CS", + "class_name": "Subsea production control", + "type": { + "description": "Direct electro-hydraulic", + "code": "EH" + } + }, + "IF": { + "category": "Mechanical", + "class_code": "TA", + "class_name": "Storage Tanks", + "type": { + "description": "Fixed Roof with Internal Floating Roof", + "code": "IF" + } + }, + "HT": { + "category": "Mechanical", + "class_code": "HB", + "class_name": "Heaters and boilers", + "type": { + "description": "Heater treater", + "code": "HT" + } + }, + "NF": { + "category": "Mechanical", + "class_code": "HB", + "class_name": "Heaters and boilers", + "type": { + "description": "Non-HC-fired boiler", + "code": "NF" + } + }, + "EB": { + "category": "Mechanical", + "class_code": "HB", + "class_name": "Heaters and boilers", + "type": { + "description": "Electric boiler", + "code": "EB" + } + }, + "FB": { + "category": "Mechanical", + "class_code": "HB", + "class_name": "Heaters and boilers", + "type": { + "description": "HC-fired boiler", + "code": "FB" + } + }, + "CA": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Coalescer", + "code": "CA" + } + }, + "LO": { + "category": "Mechanical", + "class_code": "PI", + "class_name": "Piping", + "type": { + "description": "High-strength low-alloy steels", + "code": "LO" + } + }, + "TI": { + "category": "Mechanical", + "class_code": "PI", + "class_name": "Piping", + "type": { + "description": "Titanium", + "code": "TI" + } + }, + "PO": { + "category": "Mechanical", + "class_code": "PI", + "class_name": "Piping", + "type": { + "description": "Polymers including fibre-reinforced", + "code": "PO" + } + }, + "SP": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "PSV-pilot operated", + "code": "SP" + } + }, + "SE": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Separator", + "code": "SE" + } + }, + "FD": { + "category": "Mechanical", + "class_code": "VE", + "class_name": "Pressure vessels", + "type": { + "description": "Flash drum", + "code": "FD" + } + }, + "SB": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Scrubber", + "code": "SB" + } + }, + "CO": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Corrosion", + "code": "CO" + } + }, + "CY": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Cyclone", + "code": "CY" + } + }, + "HY": { + "category": "Subsea", + "class_code": "SV", + "class_name": "Subsea pressure vessels", + "type": { + "description": "Hydrocyclone", + "code": "HY" + } + }, + "DR": { + "category": "Mechanical", + "class_code": "VE", + "class_name": "Pressure vessels", + "type": { + "description": "Dryer", + "code": "DR" + } + }, + "PT": { + "category": "Mechanical", + "class_code": "TU", + "class_name": "Turrets", + "type": { + "description": "Permanent turrets", + "code": "PT" + } + }, + "SA": { + "category": "Mechanical", + "class_code": "VE", + "class_name": "Pressure vessels", + "type": { + "description": "Saturator", + "code": "SA" + } + }, + "DA": { + "category": "Mechanical", + "class_code": "VE", + "class_name": "Pressure vessels", + "type": { + "description": "De-aerator", + "code": "DA" + } + }, + "FR": { + "category": "Mechanical", + "class_code": "TA", + "class_name": "Storage Tanks", + "type": { + "description": "Fixed-Roof", + "code": "FR" + } + }, + "LR": { + "category": "Mechanical", + "class_code": "TA", + "class_name": "Storage Tanks", + "type": { + "description": "Lifting Roof", + "code": "LR" + } + }, + "EF": { + "category": "Mechanical", + "class_code": "TA", + "class_name": "Storage Tanks", + "type": { + "description": "External Floating Roof", + "code": "EF" + } + }, + "RL": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Relay", + "code": "RL" + } + }, + "TO": { + "category": "Mechanical", + "class_code": "SW", + "class_name": "Swivels", + "type": { + "description": "Toroidal", + "code": "TO" + } + }, + "ES": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "External sleeve", + "code": "ES" + } + }, + "DT": { + "category": "Electrical", + "class_code": "PT", + "class_name": "Power transformers", + "type": { + "description": "Dry", + "code": "DT" + } + }, + "EW": { + "category": "Mechanical", + "class_code": "WI", + "class_name": "Winches", + "type": { + "description": "Electric winch", + "code": "EW" + } + }, + "HW": { + "category": "Mechanical", + "class_code": "WI", + "class_name": "Winches", + "type": { + "description": "Hydraulic winch", + "code": "HW" + } + }, + "LV": { + "category": "Electrical", + "class_code": "SG", + "class_name": "Switchgears", + "type": { + "description": "Low voltage", + "code": "LV" + } + }, + "HV": { + "category": "Electrical", + "class_code": "FC", + "class_name": "Frequency converters", + "type": { + "description": "High voltage", + "code": "HV" + } + }, + "OT": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Others", + "code": "OT" + } + }, + "OV": { + "category": "Electrical", + "class_code": "SG", + "class_name": "Switchgears", + "type": { + "description": "Oil and vacuum insulated", + "code": "OV" + } + }, + "HA": { + "category": "Electrical", + "class_code": "SG", + "class_name": "Switchgears", + "type": { + "description": "High voltage air insulated", + "code": "HA" + } + }, + "HG": { + "category": "Electrical", + "class_code": "SG", + "class_name": "Switchgears", + "type": { + "description": "High voltage gas insulated", + "code": "HG" + } + }, + "UB": { + "category": "Electrical", + "class_code": "UP", + "class_name": "Uninterruptible power supply", + "type": { + "description": "Dual UPS with standby bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "UB" + } + }, + "UD": { + "category": "Electrical", + "class_code": "UP", + "class_name": "Uninterruptible power supply", + "type": { + "description": "Dual UPS without bypass Rectifier supplied from emergency power", + "code": "UD" + } + }, + "US": { + "category": "Electrical", + "class_code": "UP", + "class_name": "Uninterruptible power supply", + "type": { + "description": "Single UPS with bypass Rectifier supplied from emergency power Bypass from main power system", + "code": "US" + } + }, + "UT": { + "category": "Electrical", + "class_code": "UP", + "class_name": "Uninterruptible power supply", + "type": { + "description": "Single UPS without bypass Rectifier supplied from emergency power", + "code": "UT" + } + }, + "LC": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Programmable logic controller (PLC)", + "code": "LC" + } + }, + "SL": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Single-loop controller", + "code": "SL" + } + }, + "PA": { + "category": "Safety and control", + "class_code": "CL", + "class_name": "Control logic units", + "type": { + "description": "Programmable automation controller (PAC)", + "code": "PA" + } + }, + "BS": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Smoke/Combustion", + "code": "BS" + } + }, + "BH": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Heat", + "code": "BH" + } + }, + "BF": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Flame", + "code": "BF" + } + }, + "BM": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Manual pushbutton", + "code": "BM" + } + }, + "BA": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Ball", + "code": "BA" + } + }, + "AB": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Hydrocarbon", + "code": "AB" + } + }, + "AS": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Toxic gases", + "code": "AS" + } + }, + "AO": { + "category": "Safety and control", + "class_code": "FG", + "class_name": "Fire and gas detectors", + "type": { + "description": "Others (Gas)", + "code": "AO" + } + }, + "PS": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Pressure", + "code": "PS" + } + }, + "LS": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Level", + "code": "LS" + } + }, + "TS": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Temperature", + "code": "TS" + } + }, + "FS": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Flow", + "code": "FS" + } + }, + "VI": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Vibration", + "code": "VI" + } + }, + "DI": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Disc", + "code": "DI" + } + }, + "AN": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Analyser", + "code": "AN" + } + }, + "WE": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Weight", + "code": "WE" + } + }, + "LP": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "Limit switch", + "code": "LP" + } + }, + "PB": { + "category": "Safety and control", + "class_code": "IP", + "class_name": "Input devices", + "type": { + "description": "On/off (pushbutton)", + "code": "PB" + } + }, + "FF": { + "category": "Safety and control", + "class_code": "LB", + "class_name": "Lifeboats", + "type": { + "description": "Free fall", + "code": "FF" + } + }, + "DL": { + "category": "Safety and control", + "class_code": "LB", + "class_name": "Lifeboats", + "type": { + "description": "Davit launched", + "code": "DL" + } + }, + "DN": { + "category": "Safety and control", + "class_code": "NO", + "class_name": "Nozzles", + "type": { + "description": "Deluge", + "code": "DN" + } + }, + "SR": { + "category": "Safety and control", + "class_code": "NO", + "class_name": "Nozzles", + "type": { + "description": "Sprinkler", + "code": "SR" + } + }, + "WM": { + "category": "Safety and control", + "class_code": "NO", + "class_name": "Nozzles", + "type": { + "description": "Water mist", + "code": "WM" + } + }, + "GA": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Gate", + "code": "GA" + } + }, + "GL": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Globe", + "code": "GL" + } + }, + "BP": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Butterfly", + "code": "BP" + } + }, + "PG": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Plug", + "code": "PG" + } + }, + "NE": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Needle", + "code": "NE" + } + }, + "CH": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Check", + "code": "CH" + } + }, + "FL": { + "category": "Subsea", + "class_code": "SL", + "class_name": "Subsea pipelines", + "type": { + "description": "Flexible", + "code": "FL" + } + }, + "MO": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Multiple orifice", + "code": "MO" + } + }, + "WA": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Three-way", + "code": "WA" + } + }, + "SV": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "PSV-vacuum relief", + "code": "SV" + } + }, + "AF": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Axial flow", + "code": "AF" + } + }, + "PI": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Pinch", + "code": "PI" + } + }, + "OH": { + "category": "Safety and control", + "class_code": "VA", + "class_name": "Valves", + "type": { + "description": "Others", + "code": "OH" + } + }, + "RI": { + "category": "Subsea", + "class_code": "SL", + "class_name": "Subsea pipelines", + "type": { + "description": "Rigid", + "code": "RI" + } + }, + "SU": { + "category": "Subsea", + "class_code": "EP", + "class_name": "Subsea electrical power", + "type": { + "description": "Single consumer without subsea step-down", + "code": "SU" + } + }, + "MC": { + "category": "Subsea", + "class_code": "EP", + "class_name": "Subsea electrical power", + "type": { + "description": "Multiple consumer", + "code": "MC" + } + }, + "DH": { + "category": "Subsea", + "class_code": "CS", + "class_name": "Subsea production control", + "type": { + "description": "Direct hydraulic", + "code": "DH" + } + }, + "MX": { + "category": "Drilling", + "class_code": "BT", + "class_name": "Surface blowout preventers (BOP) - (Fixed)", + "type": { + "description": "Multiplexed electro-hydraulic", + "code": "MX" + } + }, + "PH": { + "category": "Drilling", + "class_code": "BT", + "class_name": "Surface blowout preventers (BOP) - (Fixed)", + "type": { + "description": "Piloted hydraulic", + "code": "PH" + } + }, + "SH": { + "category": "Subsea", + "class_code": "CS", + "class_name": "Subsea production control", + "type": { + "description": "Sequential piloted hydraulic", + "code": "SH" + } + }, + "TH": { + "category": "Subsea", + "class_code": "CS", + "class_name": "Subsea production control", + "type": { + "description": "Telemetric hydraulic", + "code": "TH" + } + }, + "VX": { + "category": "Subsea", + "class_code": "XT", + "class_name": "Subsea wellhead and X-mas trees", + "type": { + "description": "Vertical", + "code": "VX" + } + }, + "HX": { + "category": "Subsea", + "class_code": "XT", + "class_name": "Subsea wellhead and X-mas trees", + "type": { + "description": "Horizontal", + "code": "HX" + } + }, + "VE": { + "category": "Well completion", + "class_code": "XD", + "class_name": "Surface wellhead and X-mas trees", + "type": { + "description": "Vertical", + "code": "VE" + } + }, + "ED": { + "category": "Drilling", + "class_code": "TD", + "class_name": "Top drives", + "type": { + "description": "Electrically driven", + "code": "ED" + } + }, + "W1": { + "category": "Well Intervention", + "class_code": "WC", + "class_name": "Coiled tubing, surface well control equipment", + "type": { + "description": "Coiled tubing", + "code": "W1" + } + }, + "W2": { + "category": "Well Intervention", + "class_code": "WC", + "class_name": "Coiled tubing, surface well control equipment", + "type": { + "description": "Snubbing", + "code": "W2" + } + }, + "W3": { + "category": "Well Intervention", + "class_code": "WC", + "class_name": "Coiled tubing, surface well control equipment", + "type": { + "description": "Wireline", + "code": "W3" + } + }, + "WC": { + "category": "Well Intervention", + "class_code": "ΟΙ", + "class_name": "Subsea well intervention", + "type": { + "description": "Well completion", + "code": "WC" + } + }, + "WI": { + "category": "Well Intervention", + "class_code": "ΟΙ", + "class_name": "Subsea well intervention", + "type": { + "description": "Well intervention – open sea (tree mode)", + "code": "WI" + } + }, + "WO": { + "category": "Well Intervention", + "class_code": "ΟΙ", + "class_name": "Subsea well intervention", + "type": { + "description": "Full workover (tree mode)", + "code": "WO" + } + }, + "TL": { + "category": "Marine", + "class_code": "JF", + "class_name": "Jacking and fixation", + "type": { + "description": "Open-truss legs", + "code": "TL" + } + }, + "CL": { + "category": "Marine", + "class_code": "JF", + "class_name": "Jacking and fixation", + "type": { + "description": "Columnar legs", + "code": "CL" + } + } + } + }, + "metadata": { + "standard": "ISO 14224", + "description": "Equipment taxonomy with categories, classes, and types", + "total_categories": 11, + "total_classes": 100, + "total_types": 128 + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..45f3479 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + backend: + build: ./backend + ports: + - "8000:8000" + volumes: + - ./backend:/app + - ./data:/app/data + environment: + - FRONTEND_ORIGIN=http://localhost:5173 + command: poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + + frontend: + build: ./frontend + ports: + - "5173:5173" + volumes: + - ./frontend:/app + - /app/node_modules + environment: + - VITE_API_BASE_URL=http://localhost:8000 + command: pnpm dev --host 0.0.0.0 --port 5173 + depends_on: + - backend diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..510f8ff --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine + +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@9.12.3 --activate + +COPY package.json pnpm-lock.yaml* /app/ + +RUN pnpm install + +COPY . /app + +EXPOSE 5173 + +CMD ["pnpm", "dev", "--host", "0.0.0.0", "--port", "5173"] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..f00a071 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + TermSearch + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..3c54c53 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,25 @@ +{ + "name": "termsearch-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.10", + "typescript": "^5.5.4", + "vite": "^5.4.2" + } +} diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..eba70b0 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,204 @@ +import { useMemo, useState } from "react"; + +type Definition = { + source: string; + title: string; + url: string; + definition: string; +}; + +type DefinitionResponse = { + term: string; + results: Definition[]; + taxonomy?: TaxonomyMatch[]; +}; + +type TaxonomyMatch = { + category: string; + class_name: string; + class_code: string; + type_description?: string | null; + type_code?: string | null; + annex?: string | null; + full_name: string; +}; + +const API_BASE_URL = + import.meta.env.VITE_API_BASE_URL?.toString() || "http://localhost:8000"; + +export default function App() { + const [term, setTerm] = useState(""); + const [results, setResults] = useState([]); + const [taxonomy, setTaxonomy] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const canSearch = term.trim().length > 0 && !loading; + + const apiUrl = useMemo(() => { + const url = new URL("/api/definitions", API_BASE_URL); + url.searchParams.set("term", term.trim()); + return url.toString(); + }, [term]); + + const handleSearch = async (event: React.FormEvent) => { + event.preventDefault(); + + if (!canSearch) return; + + setLoading(true); + setError(null); + + try { + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error("Failed to fetch definitions."); + } + const data = (await response.json()) as DefinitionResponse; + setResults(data.results ?? []); + setTaxonomy(data.taxonomy ?? []); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

+ TermSearch +

+

+ Oil & Gas term definitions +

+

+ Search multiple glossary sources from a single interface. +

+
+ +
+ +
+ setTerm(event.target.value)} + placeholder="Ex: gas lift" + className="flex-1 rounded-xl border border-slate-200 px-4 py-3 text-base focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200" + /> + +
+

+ API base: {API_BASE_URL} +

+
+ +
+
+

Results

+ + {results.length} {results.length === 1 ? "source" : "sources"} + +
+ + {error ? ( +
+ {error} +
+ ) : null} + + {results.length === 0 && !loading ? ( +
+ No definitions yet. Try searching for a term. +
+ ) : null} + +
+ {results.map((result) => ( +
+

+ {result.source} +

+

+ {result.title} +

+ + View source + +

+ {result.definition} +

+
+ ))} +
+
+ +
+
+

+ ISO 14224 Taxonomy +

+ + {taxonomy.length} {taxonomy.length === 1 ? "match" : "matches"} + +
+ + {taxonomy.length === 0 && !loading ? ( +
+ No taxonomy matches found. +
+ ) : null} + +
+ {taxonomy.map((item) => ( +
+

+ {item.category} +

+

+ {item.full_name} +

+
+ Class: {item.class_name} ({item.class_code}) + {item.type_description ? ( + + Type: {item.type_description} ({item.type_code}) + + ) : null} + {item.annex ? Annex: {item.annex} : null} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..f418125 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,11 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color-scheme: light; +} + +body { + @apply bg-slate-50 text-slate-900; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..9b67590 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..93aa364 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ["./index.html", "./src/**/*.{ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..e84f2af --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..16dfedc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..77a11e3 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + host: true, + port: 5173, + }, +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f6af4c --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "termsearch", + "private": true, + "version": "0.1.0", + "packageManager": "pnpm@9.12.3" +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..c481741 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "frontend" + - "packages/*"