# JSON API — Readable Reports

Все публичные JSON-эндпоинты живут в каталоге `api/`. Ответы в формате `{ "ok": true|false, ... }`, кодировка UTF-8.

Интерактивная документация: [docs_api.php](../docs_api.php) (playground + панель стабильности).

Реестр для UI: `includes/api_docs_registry.php` (версия **1.1**).

## Общие правила

| Правило | Значение |
|---------|----------|
| Аутентификация | Нет (внутренняя витрина) |
| Роутер | `GET api/index.php?resource=catalog` или отдельный `api/{resource}.php` |
| Ошибки | Поле `error` + HTTP-код (см. таблицы ниже) |
| Gzip | Включён для `api/*.php` через `.htaccess` |

### Ресурсы роутера (`api/index.php`)

| `resource` | Файл-обработчик |
|------------|-----------------|
| `catalog` | `api_handle_catalog` |
| `company` | `api_handle_company` |
| `financial_report` | `api_handle_financial_report` |
| `metric_ai` | `api_handle_metric_ai` |
| `market_index` | `api_handle_market_index` |
| `options_analytics` | `api_handle_options_analytics` |
| `alphavantage_market` | `api_handle_alphavantage_market` |
| `health` | `api_handle_health` |
| `admin_coverage` | `api_handle_admin_coverage` |
| `admin_source_pref` | `api_handle_admin_source_pref` |

Отдельно (не в роутере): `api/docs_status.php`, `api/test_observatory.php`, `api/admin_refresh.php`.

---

## Каталог — `GET api/catalog.php`

Пагинированный список компаний (MySQL `sec_ticker` или fallback `data/companies.json`).

### Параметры

| Параметр | По умолчанию | Описание |
|----------|--------------|----------|
| `page` | `1` | Страница |
| `page_size` | `48` | 1–100 |
| `q` | — | Поиск |
| `sort` | `name_asc` | `name_asc`, `ticker_asc`, `date_desc`, `cap_desc`, `spark_change_desc`, `spark_change_asc`, `event_first_*` |
| `group` | — | Фильтр `assetGroup` |
| `index` | — | Напр. `dji` — котировки компонентов |

### Ответ (200)

`total`, `page`, `pageSize`, `totalPages`, `count`, `items[]`, `meta` (`assetGroups`, `sortOptions`, …).

Каждый `items[]` содержит `signal` (предвычисленный тренд).

### Ошибки

| `error` | HTTP |
|---------|------|
| `data_unavailable` | 503 |
| `encode_failed` | 500 |

---

## Компания — `GET api/company.php`

| Параметр | Обязательный |
|----------|--------------|
| `ticker` | да |
| `multi_source` | нет — `1`/`true` добавляет блок `multiSource` (см. ниже) |

### Ответ (200)

`company`, `signal`, `positiveSignals[]`, `watchSignals[]`.

При `multi_source=1` дополнительно возвращается `multiSource` — map каждой
метрики реестра на `{source, sourceLabel, value, availableSources}`. Источник
резолвится по настройкам admin_settings.php (per-ticker → global → первый
доступный). Существующие поля не меняются — флаг обратно-совместим.

### Ошибки

`ticker_required` 400, `ticker_invalid` / `ticker_not_found` 404.

---

## Финансовый отчёт — `GET api/financial_report.php`

Единая точка для **числовых** XBRL (`sec_xbrl_period_json`) и **текстовых** narrative (`sec_xbrl_period_text_json`).

Параметр метода: **`method`** (алиас: `action`).

### Числовые методы

#### `method=bootstrap`

Метаданные тикера и индекс периодов (без тяжёлого payload).

```
GET api/financial_report.php?method=bootstrap&ticker=AAPL
```

**Ответ:** `ticker`, `companyName`, `cik`, `sector`, `industry`, `periods[]`, `periodCount`, `tradingViewSymbol`.

**Ошибки:** `ticker_required` 400, `ticker_not_found` / `no_periods` 404.

#### `method=period_snapshots_batch`

Пакет снимков в формате **`compact_v1`** (метрики в «миллионах» USD, словарь в `metadata.metrics`).

```
GET api/financial_report.php?method=period_snapshots_batch&ticker=AAPL&period_ids=1,2,3
```

| Ограничение | Значение |
|-------------|----------|
| `period_ids` | до **10**, через запятую |
| CPU-бюджет | ~28 с (возможен `partial: true`, `pendingPeriodIds`) |

**Ответ:** `format`, `metadata`, `data[]` (на период: `metrics`, `chart`, `synth`, `quote`).

### Текстовые методы (`sec_xbrl_period_text_json`)

Двухшаговая загрузка: сначала **outline** (preview), затем **body** (полный текст по `block_keys`).

#### `method=text_bootstrap`

```
GET api/financial_report.php?method=text_bootstrap&ticker=AAPL
```

**Ответ:** `periods[]` (с `textBlockCount`, `metricPeriodId`), `textPeriodCount`, `categories` (словарь категорий).

**Ошибки:** `no_text_periods` 404, `text_table_missing` 503.

#### `method=text_period_outline`

```
GET api/financial_report.php?method=text_period_outline&ticker=AAPL&period_id=34
```

**Формат:** `text_outline_v1` — `period`, `categories[]`, `blocks[]` (`blockId`, `preview`, `charCount`, `categoryLabel`, …).

#### `method=text_period_body`

```
GET api/financial_report.php?method=text_period_body&ticker=AAPL&period_id=34&block_keys=us-gaap:RiskFactors
```

| Параметр | Описание |
|----------|----------|
| `block_keys` | **Обязательно.** `blockId` из outline, несколько через запятую |
| `include_html` | `1` — добавить поле `textHtml` |

**Формат:** `text_body_v1` — `blocks[].textPlain`, `truncated`, `totalChars` (лимит ~1.5M символов на ответ).

#### `method=text_periods_outline_batch`

```
GET api/financial_report.php?method=text_periods_outline_batch&ticker=AAPL&period_ids=34,27
```

До **10** text `period_ids`, ответ как пакет outline (аналог batch для чисел).

### Рекомендуемый UI-flow (текст)

1. `text_bootstrap` → список периодов  
2. `text_period_outline` → навигация по блокам  
3. `text_period_body` + `block_keys` → чтение  
4. Для нескольких кварталов: `text_periods_outline_batch`

### Общие ошибки financial_report

| `error` | HTTP |
|---------|------|
| `unknown_method` | 400 |
| `period_ids_required` / `period_id_required` / `block_keys_required` | 400 |
| `text_period_not_found` / `block_keys_not_found` | 404 |
| `payload_invalid` | 422 |
| `server_error` | 500 |

---

## Text AI Summary — `api/text_ai_summary.php`

AI-выжимки narrative SEC (4 линзы) через Ollama; кэш в `fr_text_ai_summary_cache`. Запросы к Ollama идут через **глобальную FIFO-очередь** `fr_text_ai_job_queue` (один `running` job).

Миграция: `php bin/migrate_fr_text_ai_cache.php`

| Метод | Запрос |
|-------|--------|
| Статус линз | `GET ?method=status&ticker=AAPL&text_period_id=34` |
| Timeline сдвига | `GET ?method=timeline&ticker=AAPL&period_ids=34,27` (до 20 периодов / 5 лет) |
| Очередь (poll) | `GET ?method=queue&client_id=fr-abc123` (+ `batch_id` для пакета) |
| Поставить в очередь | `POST ?method=enqueue` JSON `{ "clientId", "ticker", "textPeriodId", "lens", "force"? }` |
| Подтянуть всё | `POST ?method=enqueue_batch` JSON `{ "clientId", "ticker", "force"? }` — периоды от старых к новым, все 4 линзы |
| Прямая генерация | `POST` JSON `{ "ticker", "textPeriodId", "lens", "force"? }` (без очереди, для API/тестов) |
| Stream | `POST ?stream=1` → SSE `progress` / `result` |

`lens`: `business` | `mdna` | `risk` | `other`

`clientId`: 8–64 символов `[a-zA-Z0-9_-]`, хранится в `localStorage` на клиенте.

Ответ `queue`: `{ queue: { pendingTotal, yourPending, running, ollamaBusy }, jobs[], batch? }`. У каждой задачи: `position`, `aheadCount`, `isNext`.

Ответ summarize: `{ source: "cache"|"live", summary, shift, shiftScore, fetchedAt }`

Ошибки: `text_ai_cache_missing` 503, `text_ai_queue_missing` 503, `client_id_required` 400, `pull_all_nothing_to_enqueue` 404, `lens_text_empty` 404, `ollama_unavailable` 503, `analysis_failed` 422.

---

## Metric AI — `api/metric_ai.php`

| Режим | Запрос |
|-------|--------|
| Smoke | `GET ?mode=test` |
| Анализ | `POST` JSON (тело из UI отчёта) |
| Stream | `POST ?stream=1` → SSE `event: progress` / `result` |

Обязательные поля POST: `ticker`, `metricKey`, `metricLabel`, `periodLabel`, `value`, `source` (`compare` | `synth`).

---

## Рынок и внешние данные

| Эндпоинт | Параметры | Назначение |
|----------|-----------|------------|
| `GET api/market_index.php` | `symbol` (^DJI) | Индекс из кэша cron |
| `GET api/options_analytics.php` | `ticker`, `refresh=1` | FlashAlpha (опционы) |
| `GET api/alphavantage_market.php` | `ticker`, `refresh=1` | Twelve Data / Alpha Vantage |

---

## Служебные

| Эндпоинт | Назначение |
|----------|------------|
| `GET api/health.php` | Диагностика XBRL + synth (`ticker`, `period_id`) |
| `GET api/docs_status.php` | Статус MySQL, Ollama, Exa, кэшей (`?probe=1`) |
| `GET api/test_observatory.php` | `action=registry\|run` для QA |

## Админ-панель (мульти-сорс покрытие)

См. подробную документацию: `docs/admin_panel.md`.

| Эндпоинт | Метод | Назначение |
|----------|-------|------------|
| `api/admin_coverage.php` | GET | Покрытие метрик по тикерам (`scope`, `page`, `q`, `filter`, `sort`, `detail`, `ticker`) |
| `api/admin_refresh.php` | POST | Синхронное обновление тикера по источникам (`{ticker, sources:[...]}`); лог в `metric_refresh_log` |
| `api/admin_source_pref.php` | GET/POST | Настройки источника: global + per-ticker overrides + reset |

---

## База данных (контекст API)

| Таблица | API |
|---------|-----|
| `sec_ticker` | catalog, company, bootstrap |
| `sec_xbrl_period_json` | `bootstrap`, `period_snapshots_batch` |
| `sec_xbrl_period_text_json` | `text_*` |
| `fr_text_ai_summary_cache` | `text_ai_summary` |
| `fr_text_ai_job_queue` | `text_ai_summary` (enqueue, queue) |
| `market_*`, `*_cache` | market_index, options, alphavantage_market |

Схема текста: `sql/schema_sec_xbrl_period_text_json.sql`. ETL: `bin/sec_sync_reports.py`.

---

## Тесты

```bash
php test/api_endpoints.php
php test/service_financial_report.php
php test/service_financial_report_text.php
php test/api_docs_registry_test.php
```
