Files
goodgo-platform/docs/api/market-index-ticker-contract.md
2026-04-21 01:37:02 +07:00

356 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Contract API Chỉ số Thị trường & Ticker (Goodgo Platform AI)
> Liên quan: [TEC-3036](/TEC/issues/TEC-3036), [TEC-3042](/TEC/issues/TEC-3042), [TEC-3043](/TEC/issues/TEC-3043).
> Trạng thái: Draft v1 (chờ CTO + BE TechLead + FE TechLead duyệt).
> Chủ trì: API Architect. Phối hợp: Database Architect, Backend TechLead, Frontend TechLead.
Tài liệu này định nghĩa contract cho các endpoint mới phục vụ UI phong cách "sàn giao dịch" (trading-floor) của Goodgo Platform AI. Frontend **chỉ gọi API thực**, cấm mock. Các endpoint mới được thêm **không breaking** endpoint hiện có (`/analytics/*`, `/listings/*`, `/search/*`).
## 1. Nguyên tắc thiết kế
- **Base path**: `/api/v1/` (prefix chung toàn platform, bearer JWT khi cần).
- **Auth**: Public cho các chỉ số tổng hợp (cache-able). JWT cho các API cá nhân hoá (watchlist ticker).
- **Caching**: `Cache-Control: public, max-age=30, stale-while-revalidate=60` cho chỉ số ticker. `max-age=300` cho heatmap/price-trends.
- **Đơn vị**:
- Tiền tệ: `VND` (integer, không thập phân). Field nào dùng VND/m² phải ghi rõ `unit: "VND_PER_SQM"`.
- Thời gian: ISO-8601 UTC (`2026-04-21T03:15:00Z`). Client tự chuyển sang Asia/Ho_Chi_Minh.
- % change: `number` (phần trăm, ví dụ `2.35` = `+2.35%`). `delta` là giá trị tuyệt đối cùng đơn vị với `value`.
- **Response envelope**: thống nhất với module analytics hiện có:
```json
{
"data": { ... },
"meta": {
"generatedAt": "2026-04-21T03:15:00Z",
"ttlSeconds": 30,
"source": "aggregation_v1",
"baselinePeriod": "PT24H"
}
}
```
- **Lỗi**: tuân theo `docs/api-error-codes.md` (ErrorResponse `{ code, message, details }`).
- **Pagination** (nếu có): `page`, `pageSize` (max 100), response có `meta.pagination`.
- **i18n**: label/description phục vụ UI trả về tiếng Việt; field system (`code`, `slug`) dùng tiếng Anh.
## 2. Phân loại endpoint
| Nhóm | Mục đích UI | Cache | Auth |
|------|-------------|-------|------|
| Market Index | Header chỉ số GGI/khu vực | 30s | Public |
| Price Trends | Biểu đồ candlestick/line | 300s | Public |
| District Volume | Bar chart volume theo quận | 120s | Public |
| Listing Ticker | Dải chạy real-time tin mới/giá mới | 15s + SSE | Public + (JWT cho watchlist) |
| Top Movers | Bảng top tăng/giảm | 60s | Public |
| Heatmap Summary | Overlay heatmap bản đồ | 300s | Public |
## 3. Đặc tả chi tiết
### 3.1. `GET /api/v1/analytics/market-index`
Trả về chỉ số thị trường tổng hợp (Goodgo Market Index GGI) theo phân loại.
**Query params**
| Name | Type | Required | Default | Mô tả |
|------|------|----------|---------|-------|
| `scope` | enum(`national`, `city`, `district`) | no | `city` | Phạm vi tính chỉ số |
| `cityCode` | string | when scope≠national | `HCM` | Mã thành phố (HCM, HN, DN...) |
| `districtCode` | string | when scope=district | | Mã quận/huyện |
| `propertyType` | enum(`all`, `apartment`, `house`, `land`, `commercial`) | no | `all` | Loại hình |
| `baseline` | enum(`PT24H`, `P7D`, `P30D`, `P1Y`) | no | `PT24H` | Khoảng so sánh delta |
**Response 200**
```json
{
"data": {
"indexCode": "GGI-HCM-ALL",
"indexLabel": "Chỉ số Goodgo TP.HCM - Tất cả",
"value": 1284.35,
"unit": "INDEX_POINT",
"baseValue": 1000,
"baselinePeriod": "PT24H",
"delta": 29.62,
"changePercent": 2.36,
"direction": "up",
"samples": 12845,
"breakdown": [
{ "code": "apartment", "label": "Chung cư", "value": 1311.2, "changePercent": 3.1 },
{ "code": "house", "label": "Nhà phố", "value": 1198.4, "changePercent": 1.0 },
{ "code": "land", "label": "Đất nền", "value": 1342.7, "changePercent": 4.2 }
],
"timestamp": "2026-04-21T03:15:00Z"
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 30, "source": "aggregation_v1", "baselinePeriod": "PT24H" }
}
```
### 3.2. `GET /api/v1/analytics/price-trends`
Chuỗi giá trung bình theo thời gian (dạng nến OHLC hoặc line tuỳ `granularity`).
**Query params**
| Name | Type | Required | Default | Mô tả |
|------|------|----------|---------|-------|
| `scope` | enum(`city`, `district`, `ward`) | yes | | |
| `scopeCode` | string | yes | | Mã tương ứng |
| `propertyType` | enum(...) | no | `all` | |
| `granularity` | enum(`1h`, `1d`, `1w`, `1m`) | no | `1d` | Bucket thời gian |
| `from` | ISO-8601 | no | now-30d | |
| `to` | ISO-8601 | no | now | |
| `series` | enum(`avg`, `ohlc`) | no | `ohlc` | |
**Response 200**
```json
{
"data": {
"scope": "district",
"scopeCode": "HCM-Q1",
"unit": "VND_PER_SQM",
"granularity": "1d",
"points": [
{
"timestamp": "2026-04-20T00:00:00Z",
"open": 95200000,
"high": 96800000,
"low": 94900000,
"close": 96100000,
"avg": 95600000,
"volume": 184,
"changePercent": 0.95
}
]
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 300, "source": "listing_snapshot_v1", "baselinePeriod": "P30D" }
}
```
### 3.3. `GET /api/v1/analytics/district-volume`
Khối lượng tin đăng/giao dịch theo quận cho bar chart.
**Query params**
| Name | Type | Required | Default |
|------|------|----------|---------|
| `cityCode` | string | no | `HCM` |
| `metric` | enum(`listings_new`, `listings_active`, `inquiries`, `transactions`) | no | `listings_new` |
| `window` | enum(`PT1H`, `PT24H`, `P7D`, `P30D`) | no | `PT24H` |
| `limit` | int (1..50) | no | 24 |
| `sort` | enum(`value_desc`, `value_asc`, `change_desc`) | no | `value_desc` |
**Response 200**
```json
{
"data": {
"cityCode": "HCM",
"metric": "listings_new",
"window": "PT24H",
"items": [
{
"districtCode": "HCM-Q7",
"districtName": "Quận 7",
"value": 312,
"previousValue": 284,
"delta": 28,
"changePercent": 9.86,
"rank": 1
}
]
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 120, "source": "listing_stream_v1", "baselinePeriod": "PT24H" }
}
```
### 3.4. `GET /api/v1/listings/ticker`
Danh sách tick cho dải chạy real-time. Có cả poll (REST) và stream (SSE/WebSocket) version.
**Query params**
| Name | Type | Required | Default | Mô tả |
|------|------|----------|---------|-------|
| `channel` | enum(`new_listing`, `price_change`, `hot`, `featured`, `watchlist`) | no | `new_listing` | `watchlist` cần JWT |
| `scope` | enum(`national`, `city`, `district`) | no | `city` | |
| `scopeCode` | string | cond | `HCM` | |
| `propertyType` | enum(...) | no | `all` | |
| `limit` | int(1..50) | no | 20 | |
| `sinceTickId` | string | no | | Lấy tick mới hơn ID này (polling incremental) |
**Response 200**
```json
{
"data": {
"channel": "new_listing",
"ticks": [
{
"tickId": "tick_01J...",
"listingId": "lst_01J...",
"symbol": "HCM-Q7-APT-0934",
"title": "Căn hộ 2PN view sông Phú Mỹ Hưng",
"propertyType": "apartment",
"districtCode": "HCM-Q7",
"districtName": "Quận 7",
"priceVnd": 5200000000,
"pricePerSqmVnd": 68400000,
"areaSqm": 76,
"changePercent": -1.45,
"direction": "down",
"event": "price_change",
"timestamp": "2026-04-21T03:14:57Z",
"url": "/listings/lst_01J..."
}
],
"nextSinceTickId": "tick_01J...ZZ"
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 15, "source": "ticker_stream_v1" }
}
```
#### 3.4.1. `GET /api/v1/listings/ticker/stream` (SSE)
- Content-Type: `text/event-stream`.
- Event types: `tick`, `heartbeat`, `reset`.
- Payload `data:` giống một phần tử `ticks[]` phía trên.
- Query params như REST, thêm `heartbeatSeconds` (default 15).
### 3.5. `GET /api/v1/analytics/top-movers`
Top tăng/giảm theo khu vực/loại hình.
**Query params**
| Name | Type | Required | Default |
|------|------|----------|---------|
| `direction` | enum(`gainers`, `losers`, `both`) | no | `both` |
| `scope` | enum(`city`, `district`, `ward`, `project`) | no | `district` |
| `cityCode` | string | no | `HCM` |
| `metric` | enum(`price_per_sqm`, `index_value`, `volume`) | no | `price_per_sqm` |
| `window` | enum(`PT24H`, `P7D`, `P30D`) | no | `P7D` |
| `limit` | int(1..50) | no | 10 |
**Response 200**
```json
{
"data": {
"window": "P7D",
"metric": "price_per_sqm",
"gainers": [
{
"code": "HCM-Q9",
"label": "TP Thủ Đức (Q9 cũ)",
"value": 72400000,
"previousValue": 68900000,
"delta": 3500000,
"changePercent": 5.08,
"rank": 1,
"sampleSize": 412
}
],
"losers": [ /* same schema */ ]
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 60, "source": "aggregation_v1", "baselinePeriod": "P7D" }
}
```
### 3.6. `GET /api/v1/analytics/heatmap-summary`
Dữ liệu tóm tắt heatmap cho overlay bản đồ.
**Query params**
| Name | Type | Required | Default | Mô tả |
|------|------|----------|---------|-------|
| `cityCode` | string | no | `HCM` | |
| `metric` | enum(`price_per_sqm`, `volume`, `demand_index`, `change_percent`) | no | `price_per_sqm` | |
| `resolution` | enum(`district`, `ward`, `h3_r7`, `h3_r8`) | no | `district` | H3 cell khi cần granularity cao |
| `window` | enum(`PT24H`, `P7D`, `P30D`) | no | `P30D` | |
| `bbox` | string `minLon,minLat,maxLon,maxLat` | no | | Bounding box bản đồ |
**Response 200**
```json
{
"data": {
"metric": "price_per_sqm",
"unit": "VND_PER_SQM",
"resolution": "district",
"legend": {
"buckets": [
{ "min": 0, "max": 40000000, "color": "#1a9850", "label": "Thấp" },
{ "min": 40000000, "max": 80000000, "color": "#fee08b", "label": "Trung bình" },
{ "min": 80000000, "max": 140000000, "color": "#d73027", "label": "Cao" }
]
},
"cells": [
{
"code": "HCM-Q1",
"label": "Quận 1",
"centroid": { "lat": 10.776, "lon": 106.700 },
"value": 128400000,
"changePercent": 1.8,
"sampleSize": 532,
"polygonRef": "geo/districts/HCM-Q1.geojson"
}
]
},
"meta": { "generatedAt": "2026-04-21T03:15:00Z", "ttlSeconds": 300, "source": "aggregation_v1", "baselinePeriod": "P30D" }
}
```
## 4. Nguồn dữ liệu & aggregation (phối hợp Database Architect)
| Endpoint | Nguồn | Aggregation chính | Refresh |
|----------|-------|-------------------|---------|
| `market-index` | `Listing`, `Transaction`, `PriceSnapshot` | Weighted avg price/m² chuẩn hoá về base 2025-01-01 = 1000 | 1 phút (materialized view) |
| `price-trends` | `PriceSnapshot` (daily), `Listing.price_history` | OHLC theo bucket | 5 phút |
| `district-volume` | `Listing.createdAt`, `Inquiry`, `Transaction` | COUNT + window function | 1 phút |
| `listings/ticker` | Redis stream `listings:events` + DB fallback | Event bus (create/price_change/feature) | real-time |
| `top-movers` | `PriceSnapshot` | % change theo bucket + ranking | 5 phút |
| `heatmap-summary` | `Listing` + `District`/`H3Cell` precomputed | PostGIS aggregate theo polygon/H3 | 15 phút |
Database Architect xác nhận: (i) thêm materialized views `mv_market_index_*`, `mv_price_snapshot_*`; (ii) Redis stream `listings:events` dùng cho ticker/SSE; (iii) bảng `H3Cell` cho heatmap r7/r8.
## 5. Phân việc cho Backend TechLead ([TEC-3042](/TEC/issues/TEC-3042))
| Endpoint | Đề xuất cấp độ BE | Ghi chú |
|----------|-------------------|--------|
| `market-index` | Senior | Cần design materialized view + cache invalidation |
| `price-trends` | Middle | Query trên view sẵn có |
| `district-volume` | Middle | Window function + Redis cache |
| `listings/ticker` (REST+SSE) | Senior | Pub/sub, backpressure, auth watchlist |
| `top-movers` | Middle | Dựa trên view price-trends |
| `heatmap-summary` | Senior | PostGIS + H3, geojson ref |
| DTO/OpenAPI sync | Junior | Viết DTO, Swagger decorators, update `docs/api-endpoints.md` |
## 6. OpenAPI & Swagger
- DTO mới ở `apps/api/src/modules/analytics/presentation/dto/` và `apps/api/src/modules/listings/presentation/dto/` (ticker).
- Controller:
- `analytics.controller.ts`: thêm `@Get('market-index')`, `@Get('price-trends')`, `@Get('district-volume')`, `@Get('top-movers')`, `@Get('heatmap-summary')`.
- `listings.controller.ts`: thêm `@Get('ticker')`, `@Get('ticker/stream')`.
- Tag Swagger: `Analytics - Market`, `Listings - Ticker`.
- Cập nhật `docs/api-endpoints.md` (phần Analytics & Listings) sau khi merge.
## 7. Versioning & rollout
- Tất cả endpoint mới nằm dưới `/api/v1/` hiện tại — **không bump major**.
- Feature flag (nếu cần) dùng `FEATURE_MARKET_TICKER` cho ticker SSE.
- SLO: p95 < 250ms cho REST; SSE duy trì kết nối ≥ 5 phút, heartbeat 15s.
## 8. Checklist duyệt
- [ ] CTO phê duyệt phạm vi & SLO.
- [ ] BE TechLead xác nhận phân rã task & timeline.
- [ ] FE TechLead xác nhận response schema đủ cho UI sàn giao dịch.
- [ ] Database Architect ký nguồn dữ liệu & materialized views.
- [ ] DTO/OpenAPI/Swagger & `docs/api-endpoints.md` cập nhật.
---
*Revision 1 — 2026-04-21 — API Architect (TEC-3043).*