365 lines
15 KiB
Python
365 lines
15 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FieldDefinition:
|
|
code: str
|
|
label: str
|
|
sql_type: str
|
|
data_type: str = "text"
|
|
required: bool = False
|
|
searchable: bool = False
|
|
sortable: bool = True
|
|
editable: bool = True
|
|
visible_in_list: bool = True
|
|
default_sql: str | None = None
|
|
options: tuple[str, ...] = ()
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"code": self.code,
|
|
"label": self.label,
|
|
"data_type": self.data_type,
|
|
"required": self.required,
|
|
"searchable": self.searchable,
|
|
"sortable": self.sortable,
|
|
"editable": self.editable,
|
|
"visible_in_list": self.visible_in_list,
|
|
"options": list(self.options),
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TableDefinition:
|
|
code: str
|
|
label: str
|
|
group: str
|
|
description: str
|
|
fields: tuple[FieldDefinition, ...]
|
|
allow_create: bool = True
|
|
allow_update: bool = True
|
|
allow_delete: bool = True
|
|
|
|
@property
|
|
def primary_key(self) -> str:
|
|
return "id"
|
|
|
|
@property
|
|
def title_field(self) -> str:
|
|
for candidate in ("name", "room_name", "facility_name", "deal_name", "route_name", "tag_name"):
|
|
if any(field.code == candidate for field in self.fields):
|
|
return candidate
|
|
return "id"
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
system_fields = (
|
|
FieldDefinition("id", "记录 ID", "UUID", editable=False),
|
|
FieldDefinition("created_at", "创建时间", "TIMESTAMPTZ", "datetime", editable=False),
|
|
FieldDefinition("updated_at", "更新时间", "TIMESTAMPTZ", "datetime", editable=False),
|
|
)
|
|
return {
|
|
"code": self.code,
|
|
"label": self.label,
|
|
"group": self.group,
|
|
"description": self.description,
|
|
"primary_key": self.primary_key,
|
|
"title_field": self.title_field,
|
|
"allow_create": self.allow_create,
|
|
"allow_update": self.allow_update,
|
|
"allow_delete": self.allow_delete,
|
|
"fields": [field.as_dict() for field in (*system_fields, *self.fields)],
|
|
}
|
|
|
|
|
|
def field(
|
|
code: str,
|
|
label: str,
|
|
sql_type: str = "TEXT",
|
|
data_type: str = "text",
|
|
*,
|
|
required: bool = False,
|
|
searchable: bool = False,
|
|
visible: bool = True,
|
|
default_sql: str | None = None,
|
|
options: tuple[str, ...] = (),
|
|
) -> FieldDefinition:
|
|
return FieldDefinition(
|
|
code=code,
|
|
label=label,
|
|
sql_type=sql_type,
|
|
data_type=data_type,
|
|
required=required,
|
|
searchable=searchable,
|
|
visible_in_list=visible,
|
|
default_sql=default_sql,
|
|
options=options,
|
|
)
|
|
|
|
|
|
TABLE_DEFINITIONS: tuple[TableDefinition, ...] = (
|
|
TableDefinition(
|
|
"poi_entities",
|
|
"地点实体",
|
|
"正式实体",
|
|
"酒店、美食、景区、交通和公交站统一基础信息。",
|
|
(
|
|
field("entity_type", "实体类型", required=True, searchable=True, options=("hotel", "restaurant", "scenic", "transport", "bus_stop")),
|
|
field("name", "名称", required=True, searchable=True),
|
|
field("category_l1", "一级分类", searchable=True),
|
|
field("category_l2", "二级分类", searchable=True),
|
|
field("category_l3", "细分类", searchable=True),
|
|
field("address", "地址", searchable=True),
|
|
field("district", "行政区", searchable=True),
|
|
field("adcode", "行政区划代码"),
|
|
field("phone", "商家电话"),
|
|
field("longitude", "经度", "DOUBLE PRECISION", "number"),
|
|
field("latitude", "纬度", "DOUBLE PRECISION", "number"),
|
|
field("h3_r9", "片区 R9"),
|
|
field("h3_r10", "片区 R10"),
|
|
field("status", "状态", default_sql="'active'", options=("active", "disabled")),
|
|
field("version", "版本", "INTEGER", "number", default_sql="1"),
|
|
field("extra_data", "扩展数据", "JSONB", "json", visible=False, default_sql="'{}'::jsonb"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"entity_external_links",
|
|
"外部平台标识",
|
|
"公共明细",
|
|
"高德、携程、大众点评等已确认的平台 ID 与详情链接。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("platform", "平台", required=True, searchable=True, options=("amap", "ctrip", "dianping", "other")),
|
|
field("external_id", "平台 ID", required=True, searchable=True),
|
|
field("external_name", "平台名称", searchable=True),
|
|
field("external_url", "平台链接", "TEXT", "url"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"entity_images",
|
|
"实体图片",
|
|
"公共明细",
|
|
"实体、房型、团购套餐等图片及来源。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("owner_type", "归属类型", default_sql="'entity'", options=("entity", "room", "deal")),
|
|
field("owner_id", "归属记录 ID", "UUID"),
|
|
field("image_url", "图片 URL", "TEXT", "url", required=True),
|
|
field("caption", "图片说明", searchable=True),
|
|
field("display_order", "排序", "INTEGER", "number", default_sql="0"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"entity_relations",
|
|
"实体关系",
|
|
"公共明细",
|
|
"已经确认的包含、附近、分类和空间关系。",
|
|
(
|
|
field("source_entity_id", "源实体 ID", "UUID", required=True, searchable=True),
|
|
field("relation_type", "关系类型", required=True, searchable=True),
|
|
field("target_entity_id", "目标实体 ID", "UUID", required=True, searchable=True),
|
|
field("properties", "关系属性", "JSONB", "json", visible=False, default_sql="'{}'::jsonb"),
|
|
field("status", "状态", default_sql="'active'", options=("active", "disabled")),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"reviews",
|
|
"真实评论",
|
|
"公共明细",
|
|
"酒店、美食等实体的真实用户评论。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("platform", "评论平台", searchable=True),
|
|
field("external_review_id", "平台评论 ID", searchable=True),
|
|
field("rating", "评分", "DOUBLE PRECISION", "number"),
|
|
field("reviewer_name", "用户名称", searchable=True),
|
|
field("content", "评论内容", "TEXT", "long_text", required=True, searchable=True, visible=False),
|
|
field("reviewed_at", "评论时间", "TIMESTAMPTZ", "datetime"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"review_tags",
|
|
"评价标签",
|
|
"公共明细",
|
|
"用于评价摘要展示的标签及出现次数。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("tag_name", "标签", required=True, searchable=True),
|
|
field("mention_count", "出现次数", "INTEGER", "number", default_sql="0"),
|
|
field("sentiment", "倾向", options=("positive", "neutral", "negative")),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"hotel_profiles",
|
|
"酒店详情",
|
|
"酒店",
|
|
"酒店的携程名称、开业时间、客房数量、钻级和评分。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("ctrip_name", "携程酒店名", searchable=True),
|
|
field("opened_year", "开业年份", "INTEGER", "number"),
|
|
field("room_count", "客房数量", "INTEGER", "number"),
|
|
field("diamond_level", "酒店钻级", "INTEGER", "number"),
|
|
field("ctrip_rating", "携程评分", "DOUBLE PRECISION", "number"),
|
|
field("review_count", "用户点评数", "INTEGER", "number"),
|
|
field("reference_price", "参考起价", "NUMERIC(12,2)", "number"),
|
|
field("introduction", "酒店简介", "TEXT", "long_text", visible=False),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"hotel_room_types",
|
|
"酒店房型",
|
|
"酒店",
|
|
"一个房型一条记录,不保存实时库存文案。",
|
|
(
|
|
field("entity_id", "酒店实体 ID", "UUID", required=True, searchable=True),
|
|
field("room_name", "房型名称", required=True, searchable=True),
|
|
field("image_url", "房型图片", "TEXT", "url"),
|
|
field("bed_type", "床型", searchable=True),
|
|
field("area_text", "面积"),
|
|
field("floor_text", "楼层"),
|
|
field("window_text", "窗户"),
|
|
field("max_guests", "可住人数", "INTEGER", "number"),
|
|
field("breakfast", "早餐"),
|
|
field("cancellation_policy", "取消政策", "TEXT", "long_text", visible=False),
|
|
field("payment_method", "支付方式"),
|
|
field("reference_price", "参考价格", "NUMERIC(12,2)", "number"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"hotel_facilities",
|
|
"酒店设施",
|
|
"酒店",
|
|
"酒店设施分类、是否免费、收费说明和来源链接。",
|
|
(
|
|
field("entity_id", "酒店实体 ID", "UUID", required=True, searchable=True),
|
|
field("facility_category", "设施分类", required=True, searchable=True),
|
|
field("facility_name", "设施名称", required=True, searchable=True),
|
|
field("is_free", "是否免费", "BOOLEAN", "boolean"),
|
|
field("charge_note", "收费说明"),
|
|
field("source_url", "来源 URL", "TEXT", "url"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"hotel_policies",
|
|
"酒店政策",
|
|
"酒店",
|
|
"入住、退房、宠物、儿童及其他酒店政策。",
|
|
(
|
|
field("entity_id", "酒店实体 ID", "UUID", required=True, searchable=True),
|
|
field("policy_type", "政策类型", required=True, searchable=True),
|
|
field("policy_name", "政策名称", required=True, searchable=True),
|
|
field("policy_content", "政策内容", "TEXT", "long_text", required=True, visible=False),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"restaurant_profiles",
|
|
"美食详情",
|
|
"美食",
|
|
"大众点评店名、分类、评分、人均消费和营业信息。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("dianping_name", "大众点评店名", searchable=True),
|
|
field("dianping_category", "点评分类", searchable=True),
|
|
field("ranking_text", "榜单排名"),
|
|
field("business_status", "营业状态", options=("open", "closed", "unknown")),
|
|
field("business_hours", "营业时间"),
|
|
field("rating", "点评评分", "DOUBLE PRECISION", "number"),
|
|
field("review_count", "用户点评数", "INTEGER", "number"),
|
|
field("average_price", "人均消费", "NUMERIC(12,2)", "number"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"restaurant_deals",
|
|
"美食团购",
|
|
"美食",
|
|
"团购套餐名称、图片、价格、折扣和使用规则。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("deal_name", "套餐名称", required=True, searchable=True),
|
|
field("image_url", "套餐图片", "TEXT", "url"),
|
|
field("current_price", "当前价格", "NUMERIC(12,2)", "number"),
|
|
field("original_price", "原价", "NUMERIC(12,2)", "number"),
|
|
field("discount_text", "折扣"),
|
|
field("usage_rules", "使用规则", "TEXT", "long_text", visible=False),
|
|
field("validity_text", "有效期"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"scenic_profiles",
|
|
"景区详情",
|
|
"景区",
|
|
"景区等级、游客价值分类、开放时间和门票说明。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("scenic_type", "景区类型", searchable=True),
|
|
field("scenic_level", "景区等级", searchable=True),
|
|
field("is_national", "是否国家级", "BOOLEAN", "boolean"),
|
|
field("visitor_value_type", "游客价值分类", searchable=True),
|
|
field("opening_hours", "开放时间"),
|
|
field("ticket_note", "门票说明", "TEXT", "long_text", visible=False),
|
|
field("official_intro", "官方简介", "TEXT", "long_text", visible=False),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"scenic_children",
|
|
"景区子景点",
|
|
"景区",
|
|
"景区内部子景点、类型、坐标及游览说明。",
|
|
(
|
|
field("parent_entity_id", "所属景区实体 ID", "UUID", required=True, searchable=True),
|
|
field("name", "子景点名称", required=True, searchable=True),
|
|
field("child_type", "子景点类型", searchable=True),
|
|
field("longitude", "经度", "DOUBLE PRECISION", "number"),
|
|
field("latitude", "纬度", "DOUBLE PRECISION", "number"),
|
|
field("visit_note", "游览说明", "TEXT", "long_text", visible=False),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"transport_profiles",
|
|
"交通设施详情",
|
|
"交通",
|
|
"汽车站、客运站、火车站等交通设施信息。",
|
|
(
|
|
field("entity_id", "实体 ID", "UUID", required=True, searchable=True),
|
|
field("transport_type", "交通类型", required=True, searchable=True),
|
|
field("service_hours", "服务时间"),
|
|
field("route_note", "交通说明", "TEXT", "long_text", visible=False),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"bus_routes",
|
|
"公交线路",
|
|
"交通",
|
|
"公交线路名称、方向、运营时间及线路颜色。",
|
|
(
|
|
field("route_name", "线路名称", required=True, searchable=True),
|
|
field("direction_name", "方向", searchable=True),
|
|
field("start_stop_name", "起点站", searchable=True),
|
|
field("end_stop_name", "终点站", searchable=True),
|
|
field("service_hours", "运营时间"),
|
|
field("route_color", "线路颜色"),
|
|
field("geometry", "线路坐标", "JSONB", "json", visible=False, default_sql="'[]'::jsonb"),
|
|
),
|
|
),
|
|
TableDefinition(
|
|
"bus_route_stops",
|
|
"公交站序",
|
|
"交通",
|
|
"公交线路方向下的站点顺序。",
|
|
(
|
|
field("route_id", "线路 ID", "UUID", required=True, searchable=True),
|
|
field("stop_entity_id", "站点实体 ID", "UUID", required=True, searchable=True),
|
|
field("stop_name", "站点名称", required=True, searchable=True),
|
|
field("stop_order", "站序", "INTEGER", "number", required=True),
|
|
field("longitude", "经度", "DOUBLE PRECISION", "number"),
|
|
field("latitude", "纬度", "DOUBLE PRECISION", "number"),
|
|
),
|
|
),
|
|
)
|
|
|
|
TABLE_REGISTRY: dict[str, TableDefinition] = {table.code: table for table in TABLE_DEFINITIONS}
|
|
|