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}