13 KiB
Contract API – Chỉ số Thị trường & Ticker (Goodgo Platform AI)
Liên quan: TEC-3036, TEC-3042, 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=60cho chỉ số ticker.max-age=300cho 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%).deltalà giá trị tuyệt đối cùng đơn vị vớivalue.
- Tiền tệ:
- Response envelope: thống nhất với module analytics hiện có:
{ "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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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)
| 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_TICKERcho 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.mdcập nhật.
Revision 1 — 2026-04-21 — API Architect (TEC-3043).