diff --git a/Project/fonrey/DATA_MODEL/DATA_MODEL.md b/Project/fonrey/DATA_MODEL/DATA_MODEL.md new file mode 100644 index 00000000..b1b1e874 --- /dev/null +++ b/Project/fonrey/DATA_MODEL/DATA_MODEL.md @@ -0,0 +1,1569 @@ +# Fonrey 房产经纪管理系统 — DATA MODEL 设计文档 + +> **作者**: Backend Architect +> **版本**: v1.0 +> **日期**: 2026-04-24 +> **技术栈**: Django 4.x + PostgreSQL + django-tenants + Redis +> **设计目标**: 支撑 89,000+ 房源、多租户隔离、sub-100ms 查询、合规审计 + +--- + +## 一、架构决策总览 (Architecture Decision Records) + +### 1.1 多租户策略:Schema-per-Tenant + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PostgreSQL Instance │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ public schema│ │tenant_abc │ │tenant_xyz │ │ +│ │ (shared) │ │ schema │ │ schema │ │ +│ │ │ │ │ │ │ │ +│ │ - tenants │ │ - properties │ │ - properties │ │ +│ │ - domains │ │ - clients │ │ - clients │ │ +│ │ │ │ - complexes │ │ - complexes │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**选型理由**: +- `django-tenants` 的 Schema 隔离提供最强的数据安全边界 +- 房产经纪公司之间数据绝对不能互通(合规要求) +- 每个 Schema 独立索引,避免全局锁竞争 +- 支持按租户独立备份/恢复 + +### 1.2 核心领域模型关系图 + +``` + [区域/商圈]──────────────────────────────┐ + │ │ + [学校管理] │ + │ ▼ + [楼盘/小区] ──── [楼栋] ─────────► [房源] ◄──── [挂牌历史] + │ │ + │ ┌────────┼────────┐ + │ │ │ │ + │ [联系人] [跟进日志] [维护完成度] + │ │ │ + │ ┌─────┘ ┌────┴──────┐ + │ │ │ │ + │ [电话查看] [钥匙] [委托] [实勘] + │ + [客源] ──── [配对记录] ──── [带看记录] + │ + [员工/组织] ──── [权限] +``` + +### 1.3 关键设计原则 + +| 原则 | 决策 | +| ----- | -------------------------------------- | +| 主键类型 | `UUID v4`(跨环境安全,避免枚举攻击) | +| 软删除 | 所有核心表含 `deleted_at`(历史可追溯) | +| 时间戳 | 全部使用 `TIMESTAMPTZ`(含时区) | +| 手机号存储 | AES-256-GCM 加密存储,建立 SHA-256 哈希索引 | +| 审计字段 | `created_by`, `updated_by` 全表覆盖 | +| 枚举值 | 业务枚举用 `VARCHAR` + CHECK,系统枚举用 lookup 表 | +| 大文本 | `TEXT` 类型,不设长度(PG 内部优化) | +| 金额 | `NUMERIC(12,2)` 万元精度,避免浮点误差 | + +--- + +## 二、公共 Schema(Shared / Public) + +```sql +-- ============================================================ +-- 文件: shared_schema.sql +-- 用途: django-tenants 公共 Schema,存放租户注册信息 +-- ============================================================ + +-- 租户表(每家房产公司一条记录) +CREATE TABLE public.tenants ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + schema_name VARCHAR(63) UNIQUE NOT NULL, -- PG schema 名,最长 63 字符 + name VARCHAR(255) NOT NULL, -- 公司名称 + short_name VARCHAR(100), -- 简称/品牌名 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + is_active BOOLEAN NOT NULL DEFAULT TRUE, + paid_until DATE, -- 订阅到期日 + on_trial BOOLEAN NOT NULL DEFAULT TRUE, + extra JSONB NOT NULL DEFAULT '{}' -- 预留扩展字段 +); + +-- 域名映射表(支持多域名绑定一个租户) +CREATE TABLE public.domains ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + domain VARCHAR(253) UNIQUE NOT NULL, -- 含子域名的完整域名 + tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE, + is_primary BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_domains_tenant ON public.domains(tenant_id); +CREATE INDEX idx_domains_primary ON public.domains(tenant_id) WHERE is_primary = TRUE; +``` + +--- + +## 三、租户 Schema(Tenant Schema) + +以下所有表均在每个租户的独立 Schema 内创建。 + +--- + +### 3.1 组织人事模块(Organization & HR) + +```sql +-- ============================================================ +-- 组织架构:公司 → 区域 → 门店 → 组 +-- ============================================================ + +-- 组织节点表(树形结构,支持无限层级) +CREATE TABLE org_units ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(100) NOT NULL, + type VARCHAR(20) NOT NULL + CHECK (type IN ('company','region','store','group')), + parent_id UUID REFERENCES org_units(id) ON DELETE RESTRICT, + path TEXT NOT NULL, -- 物化路径:/root_id/parent_id/self_id/ + depth SMALLINT NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX idx_org_units_parent ON org_units(parent_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_org_units_path ON org_units USING gist(path gist_trgm_ops); +-- 注:gist_trgm_ops 需要 pg_trgm 扩展,用于路径前缀查询 + +-- 员工表 +CREATE TABLE staff ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + org_unit_id UUID NOT NULL REFERENCES org_units(id) ON DELETE RESTRICT, + name VARCHAR(50) NOT NULL, + phone_hash VARCHAR(64), -- SHA-256 哈希,用于唯一性校验 + phone_enc BYTEA, -- AES-256-GCM 加密后的手机号 + email VARCHAR(255), + role VARCHAR(30) NOT NULL + CHECK (role IN ('agent','store_manager','admin','operator','system')), + job_title VARCHAR(100), -- 职务描述 + avatar_key TEXT, -- R2/S3 存储路径 + is_active BOOLEAN NOT NULL DEFAULT TRUE, + joined_at DATE, + left_at DATE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + + -- 关联 Django auth user(用于登录认证) + user_id INTEGER UNIQUE -- FK to django auth_user +); + +CREATE INDEX idx_staff_org ON staff(org_unit_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_staff_role ON staff(role) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX idx_staff_phone_hash ON staff(phone_hash) WHERE deleted_at IS NULL; +``` + +--- + +### 3.2 区域与楼盘模块(Region & Complex Management) + +```sql +-- ============================================================ +-- 行政区 → 商圈 → 楼盘/小区 → 楼栋 +-- 注:楼盘数据是房源录入的基础底座,数据质量直接影响房源录入效率 +-- ============================================================ + +-- 城市/行政区 +CREATE TABLE districts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) NOT NULL, + city VARCHAR(50) NOT NULL DEFAULT '', + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +-- 商圈/板块 +CREATE TABLE business_areas ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + district_id UUID NOT NULL REFERENCES districts(id) ON DELETE RESTRICT, + name VARCHAR(100) NOT NULL, + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE INDEX idx_business_areas_district ON business_areas(district_id); + +-- 地铁线路 +CREATE TABLE metro_lines ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) NOT NULL, + color VARCHAR(7) -- 线路颜色 HEX +); + +-- 地铁站 +CREATE TABLE metro_stations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + metro_line_id UUID NOT NULL REFERENCES metro_lines(id) ON DELETE CASCADE, + name VARCHAR(50) NOT NULL, + sort_order INTEGER NOT NULL DEFAULT 0 +); + +-- 学校 +CREATE TABLE schools ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + district_id UUID REFERENCES districts(id) ON DELETE SET NULL, + name VARCHAR(100) NOT NULL, + type VARCHAR(20) -- 小学/初中/高中/九年一贯制 等 +); + +CREATE INDEX idx_schools_district ON schools(district_id); + +-- 楼盘/小区(核心基础表) +CREATE TABLE complexes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(200) NOT NULL, + alias VARCHAR(200), -- 别名/曾用名 + district_id UUID REFERENCES districts(id) ON DELETE SET NULL, + business_area_id UUID REFERENCES business_areas(id) ON DELETE SET NULL, + address VARCHAR(500), + latitude NUMERIC(10,7), + longitude NUMERIC(10,7), + + -- 楼盘物理属性 + developer VARCHAR(200), -- 开发商 + property_company VARCHAR(200), -- 物业公司 + property_fee NUMERIC(8,2), -- 物业费 元/㎡/月 + green_rate NUMERIC(5,2), -- 绿化率 % + plot_ratio NUMERIC(5,2), -- 容积率 + built_year SMALLINT, -- 竣工年份 + ownership_years VARCHAR(20), -- 产权年限枚举 + + -- 配套信息 + has_elevator BOOLEAN, + parking_info TEXT, -- 车位情况描述 + + -- 全文检索向量(定期更新) + search_vector TSVECTOR, + + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_complexes_district ON complexes(district_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_complexes_name_trgm ON complexes USING gin(name gin_trgm_ops); +CREATE INDEX idx_complexes_search ON complexes USING gin(search_vector); +CREATE INDEX idx_complexes_geo ON complexes(latitude, longitude) WHERE deleted_at IS NULL; + +-- 楼盘与商圈多对多(一个楼盘可跨多个商圈) +CREATE TABLE complex_business_areas ( + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, + business_area_id UUID NOT NULL REFERENCES business_areas(id) ON DELETE CASCADE, + PRIMARY KEY (complex_id, business_area_id) +); + +-- 楼盘与学校关联 +CREATE TABLE complex_schools ( + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, + school_id UUID NOT NULL REFERENCES schools(id) ON DELETE CASCADE, + school_zone VARCHAR(50), -- 学区情况:对口/参考等 + PRIMARY KEY (complex_id, school_id) +); + +-- 楼盘与地铁站关联 +CREATE TABLE complex_metro_stations ( + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, + station_id UUID NOT NULL REFERENCES metro_stations(id) ON DELETE CASCADE, + distance_meters INTEGER, -- 步行距离(米) + PRIMARY KEY (complex_id, station_id) +); + +-- 楼栋 +CREATE TABLE buildings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, + name VARCHAR(50) NOT NULL, -- 楼栋名,如"1号楼" + total_floors SMALLINT NOT NULL, + has_elevator BOOLEAN, + building_type VARCHAR(30), -- 楼型:板楼/塔楼/板塔结合 + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_buildings_complex ON buildings(complex_id); +``` + +--- + +### 3.3 房源核心模块(Property Core) + +```sql +-- ============================================================ +-- 房源主表:系统最核心的表,全部筛选/排序/搜索围绕此表展开 +-- 设计重点:89,000+ 数据量,复合索引策略,分区预留 +-- ============================================================ + +CREATE TABLE properties ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- ── 基础分类 ── + property_type VARCHAR(20) NOT NULL + CHECK (property_type IN ('residential','villa','commercial_residential', + 'shop','office','other')), + -- residential=住宅, villa=别墅, commercial_residential=商住, + -- shop=商铺, office=写字楼, other=其他 + + -- ── 交易状态 ── + status VARCHAR(20) NOT NULL DEFAULT 'for_sale' + CHECK (status IN ('for_sale','for_rent','for_sale_rent', + 'suspended','sold_elsewhere','rented_elsewhere', + 'sold','unlisted')), + -- for_sale=出售, for_rent=出租, for_sale_rent=租售, + -- suspended=暂缓, sold_elsewhere=他售, rented_elsewhere=他租, + -- sold=成交, unlisted=未挂牌 + + -- ── 流通属性 ── + attribute VARCHAR(20) NOT NULL DEFAULT 'public' + CHECK (attribute IN ('public','private','special','sealed')), + -- public=公盘, private=私盘, special=特盘, sealed=封盘 + private_reason TEXT, -- 私盘/封盘必填说明 + + -- ── 位置信息 ── + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE RESTRICT, + building_id UUID REFERENCES buildings(id) ON DELETE SET NULL, + block_no VARCHAR(30), -- 栋/幢/弄号 + unit_no VARCHAR(30), -- 单元号 + room_no VARCHAR(30), -- 房号/门牌号 + floor SMALLINT NOT NULL, -- 所在楼层 + total_floors SMALLINT NOT NULL, -- 总楼层 + CONSTRAINT chk_floor CHECK (floor > 0 AND floor <= total_floors), + + -- ── 户型 ── + bedroom_count SMALLINT NOT NULL DEFAULT 0, -- 室 + living_room_count SMALLINT NOT NULL DEFAULT 0, -- 厅 + bathroom_count SMALLINT NOT NULL DEFAULT 0, -- 卫 + kitchen_count SMALLINT NOT NULL DEFAULT 0, -- 厨 + balcony_count SMALLINT NOT NULL DEFAULT 0, -- 阳台数 + + -- ── 面积 ── + area NUMERIC(8,2) NOT NULL, -- 建筑面积 m² + inner_area NUMERIC(8,2), -- 套内面积 m²(编辑时填写) + + -- ── 价格 ── + sale_price NUMERIC(12,2), -- 挂牌售价(万元) + sale_bottom_price NUMERIC(12,2), -- 售底价(万元,内部可见) + sale_record_price NUMERIC(12,2), -- 备案/核验价(万元) + rent_price NUMERIC(10,2), -- 挂牌租价(元/月) + + -- ── 基础物理属性 ── + orientation VARCHAR(10) + CHECK (orientation IN ('east','south','west','north', + 'southeast','northeast','east_west', + 'south_north','northwest','southwest')), + decoration VARCHAR(10) + CHECK (decoration IN ('rough','plain','simple','medium', + 'fine','luxury')), + -- rough=毛坯, plain=清水, simple=简装, medium=中装, fine=精装, luxury=豪装 + + has_elevator BOOLEAN, + built_year SMALLINT, + + -- ── 用途 ── + usage_type VARCHAR(30), -- 住宅/商住/商业/普通住宅/花园洋房 等 + usage_subtype VARCHAR(30), -- 细分用途 + + -- ── 商铺专属 ── + shop_frontage NUMERIC(6,2), -- 开间(米) + shop_depth NUMERIC(6,2), -- 进深(米) + shop_height NUMERIC(6,2), -- 层高(米) + shop_location VARCHAR(20) + CHECK (shop_location IS NULL OR + shop_location IN ('street','mall','residential', + 'ground_floor','complex')), + + -- ── 房屋状态 ── + house_status VARCHAR(20) + CHECK (house_status IN ('owner_occupied','vacant', + 'tenant_occupied','unknown')), + viewing_time VARCHAR(20) + CHECK (viewing_time IN ('anytime','by_appointment','inconvenient')), + + -- ── 等级与标签 ── + grade VARCHAR(5) + CHECK (grade IN ('A_urgent','A','B','C','D')), + -- A_urgent=A(急迫), A=A, B=B(较强), C=C(一般), D=D + + -- ── 交易属性 ── + ownership_years VARCHAR(30), -- 房本年限:不满2年/满2年/满5年 等 + ownership_years_detail VARCHAR(20), -- 满五/不满五 + ownership_nature VARCHAR(20) + CHECK (ownership_nature IS NULL OR + ownership_nature IN ('commercial','reform_housing', + 'collective','economic')), + -- commercial=商品房, reform_housing=房改房, collective=集资房, economic=经济适用房 + is_only_house BOOLEAN, -- 唯一住房 + payment_method VARCHAR(30) + CHECK (payment_method IS NULL OR + payment_method IN ('full','mortgage','installment','advance')), + tax_included VARCHAR(10) + CHECK (tax_included IS NULL OR + tax_included IN ('each_party','net','inclusive')), + has_mortgage BOOLEAN, + has_loan BOOLEAN, + has_seal BOOLEAN, + has_restriction BOOLEAN, + original_price NUMERIC(12,2), -- 原购价(万元) + sale_reason TEXT, -- 售房原因(最多200字) + + -- ── 营销备注 ── + remarks TEXT, -- 房源备注(最多500字) + + -- ── 相关方(冗余存储 UUID,完整信息查 staff 表)── + first_recorder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 首录方 + number_holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 号码方 + seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 出售方 + buyer_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 实买方 + + -- ── 来源 ── + source VARCHAR(50), -- 房源来源渠道(由运营维护枚举) + + -- ── 维护完成度(冗余缓存,定期重算)── + completeness_score SMALLINT NOT NULL DEFAULT 0, -- 0-100 分 + + -- ── 时间轨迹 ── + listed_at TIMESTAMPTZ, -- 最近一次挂牌时间 + last_followed_at TIMESTAMPTZ, -- 最后跟进时间(冗余,加速排序) + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL, + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, + + -- ── 全文检索向量 ── + search_vector TSVECTOR +); + +-- ── 索引策略(针对高频查询路径设计)── + +-- 1. 最核心的列表页:按状态 + 属性 + 类型过滤 +CREATE INDEX idx_properties_status_attr ON properties(status, attribute, property_type) + WHERE deleted_at IS NULL; + +-- 2. 区域筛选(通过 complex 表 JOIN 优化) +CREATE INDEX idx_properties_complex ON properties(complex_id) + WHERE deleted_at IS NULL; + +-- 3. 价格排序(出售最常用) +CREATE INDEX idx_properties_sale_price ON properties(sale_price DESC NULLS LAST) + WHERE deleted_at IS NULL AND status IN ('for_sale','for_sale_rent'); + +-- 4. 面积区间筛选 +CREATE INDEX idx_properties_area ON properties(area) + WHERE deleted_at IS NULL; + +-- 5. 挂牌日期倒序(最新挂牌) +CREATE INDEX idx_properties_listed_at ON properties(listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 6. 最后跟进日期(超时未跟进功能) +CREATE INDEX idx_properties_last_followed ON properties(last_followed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 7. 户型筛选 +CREATE INDEX idx_properties_bedroom ON properties(bedroom_count) + WHERE deleted_at IS NULL; + +-- 8. 等级筛选 +CREATE INDEX idx_properties_grade ON properties(grade) + WHERE deleted_at IS NULL; + +-- 9. 完成度排序(引导补全信息) +CREATE INDEX idx_properties_completeness ON properties(completeness_score) + WHERE deleted_at IS NULL; + +-- 10. 全文搜索 +CREATE INDEX idx_properties_search ON properties USING gin(search_vector); + +-- 11. 与我相关(相关方快速定位) +CREATE INDEX idx_properties_seller_agent ON properties(seller_agent_id) + WHERE deleted_at IS NULL; +CREATE INDEX idx_properties_number_holder ON properties(number_holder_id) + WHERE deleted_at IS NULL; + +-- 12. 复合索引:列表默认排序(状态 + 挂牌时间) +CREATE INDEX idx_properties_list_default ON properties(status, listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; +``` + +--- + +### 3.4 房源联系人(Property Contacts) + +```sql +-- ============================================================ +-- 业主/联系人:手机号加密存储,哈希值支持重复检测 +-- 安全要点:任何查看明文号码的行为均触发审计日志 +-- ============================================================ + +CREATE TABLE property_contacts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + name VARCHAR(50) NOT NULL, + gender VARCHAR(10) NOT NULL DEFAULT 'male' + CHECK (gender IN ('male','female')), + identity VARCHAR(20) NOT NULL DEFAULT 'contact' + CHECK (identity IN ('owner','contact','subletter', + 'tenant','agent','corporate')), + -- owner=业主, contact=联系人, subletter=二房东, tenant=租客, + -- agent=代理人, corporate=企业法人 + + -- 手机号:加密存储 + 哈希索引(重复检测用) + phone_enc BYTEA NOT NULL, -- AES-256-GCM 加密 + phone_hash VARCHAR(64) NOT NULL, -- SHA-256(phone) 用于去重查询 + phone2_enc BYTEA, + phone2_hash VARCHAR(64), + + wechat VARCHAR(100), -- 微信号(相对不敏感,可明文) + qq VARCHAR(20), + remarks TEXT, + + -- 是否为号码方(关联审批流) + is_number_holder BOOLEAN NOT NULL DEFAULT FALSE, + number_holder_approved_at TIMESTAMPTZ, -- 审批通过时间 + + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL, + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_contacts_property ON property_contacts(property_id) + WHERE deleted_at IS NULL; + +-- 关键:手机号哈希全局索引(用于重复房源检测) +CREATE INDEX idx_contacts_phone_hash ON property_contacts(phone_hash) + WHERE deleted_at IS NULL; +CREATE INDEX idx_contacts_phone2_hash ON property_contacts(phone2_hash) + WHERE phone2_hash IS NOT NULL AND deleted_at IS NULL; +``` + +--- + +### 3.5 挂牌历史(Listing History) + +```sql +-- ============================================================ +-- 挂牌历史:记录房源每次上架的完整快照 +-- 设计重点:不可删除(合规),仅追加 +-- ============================================================ + +CREATE TABLE listing_histories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + + listing_type VARCHAR(20) NOT NULL + CHECK (listing_type IN ('for_sale','for_rent')), + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active','ended')), + + -- 价格快照 + sale_price NUMERIC(12,2), + rent_price NUMERIC(10,2), + sale_unit_price NUMERIC(10,2), -- 元/m²,计算字段 + + -- 交易信息快照 + ownership_years VARCHAR(30), + is_only_house BOOLEAN, + tax_included VARCHAR(10), + sale_reason TEXT, + + -- 经纪人快照 + seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + seller_agent_snapshot JSONB, -- 存储经纪人姓名+门店(防止变更后丢失) + + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + ended_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + -- 注:无 deleted_at,此表记录不可删除 +); + +CREATE INDEX idx_listing_histories_property ON listing_histories(property_id); +CREATE INDEX idx_listing_histories_active ON listing_histories(property_id) + WHERE status = 'active'; +``` + +--- + +### 3.6 调价记录(Price Change Log) + +```sql +-- ============================================================ +-- 调价记录:支持折线图展示,不可删除 +-- ============================================================ + +CREATE TABLE price_changes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + + old_sale_price NUMERIC(12,2), + new_sale_price NUMERIC(12,2), + old_bottom_price NUMERIC(12,2), + new_bottom_price NUMERIC(12,2), + old_record_price NUMERIC(12,2), + new_record_price NUMERIC(12,2), + old_rent_price NUMERIC(10,2), + new_rent_price NUMERIC(10,2), + + change_reason TEXT NOT NULL, -- 最多200字 + changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + changed_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT +); + +CREATE INDEX idx_price_changes_property ON price_changes(property_id); +CREATE INDEX idx_price_changes_time ON price_changes(property_id, changed_at DESC); +``` + +--- + +### 3.7 跟进日志(Follow-up Logs) + +```sql +-- ============================================================ +-- 跟进日志:系统最高写入频率的表,按 property_id 分区预留 +-- 6 种类型:写入跟进/修改跟进/敏感信息跟进/敏感信息查看/其他跟进/系统日志 +-- ============================================================ + +CREATE TABLE follow_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + log_type VARCHAR(30) NOT NULL + CHECK (log_type IN ('written','modified','sensitive_op', + 'sensitive_view','other','system')), + -- written=写入跟进(经纪人主动写) + -- modified=修改跟进(字段变更自动生成) + -- sensitive_op=敏感信息跟进(相关方保护变更) + -- sensitive_view=敏感信息查看(查看号码等) + -- other=其他跟进(钥匙/新增联系人等) + -- system=系统日志 + + -- 写入跟进专用字段 + purpose VARCHAR(50), -- 跟进目的(由运营维护枚举值) + content TEXT, -- 跟进内容,最少6字最多500字 + ai_tag VARCHAR(20) + CHECK (ai_tag IS NULL OR ai_tag IN ('ai_for_sale','ai_not_for_sale')), + + -- 修改跟进专用字段 + change_detail JSONB, + -- 格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"} + -- 支持多字段同时变更 + + -- 系统标签(显示在日志时间线上的 tag) + log_tag VARCHAR(50), + -- 如:查看号码/图片下载/改状态/改价格/改等级/修改相关方 等 + + -- 可见性控制 + is_public BOOLEAN NOT NULL DEFAULT TRUE, + -- FALSE = 仅本人及上级可见 + + -- 操作人 + operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, + operator_snapshot JSONB, -- {name, role, org_unit_name, store_group} + + -- 是否可删除(敏感信息查看类型 = FALSE) + is_deletable BOOLEAN NOT NULL DEFAULT TRUE, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ -- 仅 is_deletable=TRUE 时才能软删 +); + +-- 核心索引:时间线展示 +CREATE INDEX idx_follow_logs_property_time ON follow_logs(property_id, created_at DESC) + WHERE deleted_at IS NULL; + +-- 按类型过滤(6个 Tab 查询) +CREATE INDEX idx_follow_logs_type ON follow_logs(property_id, log_type, created_at DESC) + WHERE deleted_at IS NULL; + +-- 操作员过滤(跟进日志搜索功能) +CREATE INDEX idx_follow_logs_operator ON follow_logs(operator_id, created_at DESC) + WHERE deleted_at IS NULL; + +-- 不可删除类型专用索引(合规审计) +CREATE INDEX idx_follow_logs_sensitive ON follow_logs(property_id, created_at DESC) + WHERE log_type IN ('sensitive_view','sensitive_op'); + +-- 跟进日志附件(一条跟进可附多张图) +CREATE TABLE follow_log_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, + file_key TEXT NOT NULL, -- R2/S3 存储路径 + file_name VARCHAR(255) NOT NULL, + file_size INTEGER NOT NULL, -- bytes + file_type VARCHAR(10) + CHECK (file_type IN ('bmp','jpg','png','svg','gif')), + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_follow_attachments_log ON follow_log_attachments(follow_log_id); + +-- 跟进录音(独立存储,支持音频文件) +CREATE TABLE follow_log_recordings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, + file_key TEXT NOT NULL, + duration_seconds INTEGER, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### 3.8 钥匙管理(Key Management) + +```sql +CREATE TABLE property_keys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + key_type VARCHAR(20) NOT NULL + CHECK (key_type IN ('mechanical','password')), + + -- 钥匙持有方 + holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, + holder_snapshot JSONB, -- {name, store_group}(防人员变动丢失) + storage_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, -- 保管部门 + + -- 他司钥匙标记 + is_other_agency BOOLEAN NOT NULL DEFAULT FALSE, + other_agency_info VARCHAR(30), -- 他司信息,最多30字 + + remarks TEXT, -- 备注,最多200字 + + is_active BOOLEAN NOT NULL DEFAULT TRUE, -- FALSE = 钥匙已归还/失效 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_keys_property ON property_keys(property_id) + WHERE is_active = TRUE; + +-- 钥匙附件 +CREATE TABLE key_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + key_id UUID NOT NULL REFERENCES property_keys(id) ON DELETE CASCADE, + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### 3.9 委托管理(Commission Management) + +```sql +CREATE TABLE commissions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + commission_type VARCHAR(50) NOT NULL, -- 独家委托/非独家委托(运营维护枚举) + period_start DATE NOT NULL, + period_end DATE, + is_open_ended BOOLEAN NOT NULL DEFAULT FALSE, -- 无固定结束日期 + + -- 委托方(负责经纪人) + agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + agent_snapshot JSONB, + + -- 签约方式(选择后动态展示委托书模板) + signing_method VARCHAR(50), + + -- 委托人(产权人)信息 + owner_type VARCHAR(20) NOT NULL DEFAULT 'owner' + CHECK (owner_type IN ('owner','authorized_third')), + -- 从 property_contacts 中选择 + property_owner_contact_id UUID REFERENCES property_contacts(id) ON DELETE SET NULL, + owner_name VARCHAR(50), -- 产权人姓名 + owner_id_type VARCHAR(20), -- 证件类型:身份证/护照 等 + owner_id_number VARCHAR(50), -- 证件号码(加密存储) + owner_id_number_enc BYTEA, + + remarks TEXT, -- 备注,最多200字 + + -- 状态 + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active','expired','cancelled')), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_commissions_property ON commissions(property_id); +CREATE INDEX idx_commissions_active ON commissions(property_id) + WHERE status = 'active'; + +-- 委托附件(身份证/房产证/委托书 等) +CREATE TABLE commission_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + commission_id UUID NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, + category VARCHAR(20) NOT NULL + CHECK (category IN ('id_card','property_cert', + 'commission_letter','other')), + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_size INTEGER, + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_commission_attachments_commission ON commission_attachments(commission_id); +``` + +--- + +### 3.10 实勘管理(Field Survey) + +```sql +CREATE TABLE field_surveys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + status VARCHAR(10) NOT NULL DEFAULT 'draft' + CHECK (status IN ('draft','submitted')), + + -- GPS 定位 + gps_latitude NUMERIC(10,7), + gps_longitude NUMERIC(10,7), + gps_accuracy NUMERIC(6,2), -- 精度(米) + + description TEXT, -- 实勘说明,最多200字 + + submitted_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT +); + +CREATE INDEX idx_field_surveys_property ON field_surveys(property_id); +CREATE INDEX idx_field_surveys_submitted ON field_surveys(property_id) + WHERE status = 'submitted'; + +-- 实勘照片(按空间分类) +CREATE TABLE survey_photos ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + survey_id UUID NOT NULL REFERENCES field_surveys(id) ON DELETE CASCADE, + category VARCHAR(20) NOT NULL + CHECK (category IN ('layout','living_room','dining_room', + 'bedroom','bathroom','kitchen', + 'entrance','balcony','study', + 'indoor_other','outdoor')), + file_key TEXT NOT NULL, -- R2/S3 路径 + thumbnail_key TEXT, -- 缩略图路径 + file_size INTEGER, + width INTEGER, + height INTEGER, + sort_order SMALLINT NOT NULL DEFAULT 0, + is_vr_screenshot BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_survey_photos_survey ON survey_photos(survey_id); +CREATE INDEX idx_survey_photos_category ON survey_photos(survey_id, category); +``` + +--- + +### 3.11 房源图片管理(Property Photos) + +```sql +-- ============================================================ +-- 房源图片:与实勘照片分离存储,经纪人自主上传和管理 +-- 封面限1张,全景类型单独处理 +-- ============================================================ + +CREATE TABLE property_photos ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL + CHECK (category IN ('cover','entrance','living_room', + 'dining_room','bedroom','bathroom', + 'kitchen','balcony','study', + 'indoor_other','outdoor','panorama')), + + file_key TEXT NOT NULL, -- R2/S3 原图路径 + thumbnail_key TEXT, -- 缩略图路径(Cloudflare Images 生成) + file_name VARCHAR(255), + file_size INTEGER, + width INTEGER, + height INTEGER, + + is_cover BOOLEAN NOT NULL DEFAULT FALSE, + sort_order SMALLINT NOT NULL DEFAULT 0, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_photos_property ON property_photos(property_id); +CREATE INDEX idx_property_photos_cover ON property_photos(property_id) + WHERE is_cover = TRUE; +CREATE INDEX idx_property_photos_category ON property_photos(property_id, category); + +-- 唯一约束:每个房源只能有一张封面 +CREATE UNIQUE INDEX idx_property_photos_unique_cover + ON property_photos(property_id) + WHERE is_cover = TRUE; +``` + +--- + +### 3.12 房源附件(Property Attachments) + +```sql +CREATE TABLE property_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL DEFAULT 'other' + CHECK (category IN ('id_card','property_cert', + 'commission_letter','other')), + + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_size INTEGER NOT NULL, + file_type VARCHAR(50), -- MIME type + + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_attachments_property ON property_attachments(property_id); +CREATE INDEX idx_property_attachments_category ON property_attachments(property_id, category); +``` + +--- + +### 3.13 房源营销信息(Property Marketing) + +```sql +CREATE TABLE property_marketing ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + marketing_title VARCHAR(30), -- 营销标题 0-30字 + core_selling_points TEXT, -- 核心卖点,最多200字 + owner_attitude TEXT, -- 业主心态,最多200字 + layout_description TEXT, -- 户型介绍,最多200字 + complex_description TEXT, -- 小区介绍,最多200字 + + -- AI 生成标记 + ai_generated_points BOOLEAN NOT NULL DEFAULT FALSE, + ai_generated_attitude BOOLEAN NOT NULL DEFAULT FALSE, + + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); +``` + +--- + +### 3.14 产证信息(Property Certificate) + +```sql +CREATE TABLE property_certificates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + owner_name VARCHAR(100), + owner_id_number VARCHAR(50), -- 身份证号/统一社会信用代码 + owner_cert_type VARCHAR(20), -- 身份证/护照/营业执照 + property_location VARCHAR(500), -- 房屋坐落(产权证书上的地址),最多50字 + + -- 产证状态 + cert_status VARCHAR(30), + cert_no VARCHAR(100), -- 产证号 + first_registered_at DATE, -- 首次登记时间 + ownership_nature VARCHAR(30), + land_nature VARCHAR(30), -- 土地性质 + + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); +``` + +--- + +### 3.15 楼盘基本信息扩展(Complex Property Info) + +```sql +-- 补充:楼盘与房源通过 complex_id 关联,楼盘信息首次填写后修改需走楼盘管理系统 +-- 楼盘价格走势(用于楼盘详情页展示) +CREATE TABLE complex_price_trends ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, + record_month DATE NOT NULL, -- 月份(取该月1日存储) + avg_sale_price NUMERIC(10,2), -- 月均售价(万元/套) + avg_unit_price NUMERIC(10,2), -- 月均单价(元/m²) + transaction_count INTEGER, -- 成交套数 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE UNIQUE INDEX idx_complex_price_trend_month + ON complex_price_trends(complex_id, record_month); +``` + +--- + +### 3.16 维护完成度评分(Completeness Scoring) + +```sql +-- ============================================================ +-- 维护完成度:不直接存完整计算明细(减少宽表), +-- 以触发器/Celery 任务异步更新 properties.completeness_score +-- 此表存储各维度的得分快照,供详情页展示 +-- ============================================================ + +CREATE TABLE property_completeness ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + -- 各维度得分(满分见 PRD 8.2) + score_core_info SMALLINT NOT NULL DEFAULT 0, -- 重点信息 满分8 + score_attachment SMALLINT NOT NULL DEFAULT 0, -- 附件 满分8 + score_survey SMALLINT NOT NULL DEFAULT 0, -- 实勘 满分16 + score_vr SMALLINT NOT NULL DEFAULT 0, -- VR 满分8 + score_key SMALLINT NOT NULL DEFAULT 0, -- 钥匙 满分10 + score_commission SMALLINT NOT NULL DEFAULT 0, -- 委托 满分10 + score_verification SMALLINT NOT NULL DEFAULT 0, -- 验证 满分7 + score_follow_up SMALLINT NOT NULL DEFAULT 0, -- 跟进 满分8 + score_viewing SMALLINT NOT NULL DEFAULT 0, -- 带看 满分8 + score_other SMALLINT NOT NULL DEFAULT 0, -- 其他 满分7 + total_score SMALLINT NOT NULL DEFAULT 0, -- 总分 0-100 + + calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### 3.17 客源管理(Client Management) + +```sql +-- ============================================================ +-- 客源:私客为核心,公客/成交客为后续版本 +-- ============================================================ + +CREATE TABLE clients ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + client_type VARCHAR(20) NOT NULL DEFAULT 'private' + CHECK (client_type IN ('private','public','transacted')), + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active','converted_public', + 'transacted','invalid')), + + name VARCHAR(50) NOT NULL, + gender VARCHAR(10) + CHECK (gender IN ('male','female','unknown')), + + -- 手机号加密存储 + phone_enc BYTEA NOT NULL, + phone_hash VARCHAR(64) NOT NULL, + phone2_enc BYTEA, + phone2_hash VARCHAR(64), + + -- 购房需求 + purpose VARCHAR(10) NOT NULL + CHECK (purpose IN ('buy','rent')), + budget_min NUMERIC(12,2), + budget_max NUMERIC(12,2), + area_min NUMERIC(8,2), + area_max NUMERIC(8,2), + bedroom_needs SMALLINT[], -- 可接受的卧室数量数组 + + -- 意向区域(存 district/business_area ID 数组) + district_ids UUID[], + business_area_ids UUID[], + + -- 活跃度分层(由系统计算) + activity_level VARCHAR(10) + CHECK (activity_level IN ('hot','warm','cold','frozen')), + last_active_at TIMESTAMPTZ, + + -- 负责经纪人 + agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + org_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, + + source VARCHAR(50), + remarks TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_clients_agent ON clients(agent_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_clients_phone_hash ON clients(phone_hash) WHERE deleted_at IS NULL; +CREATE INDEX idx_clients_status ON clients(status, client_type) WHERE deleted_at IS NULL; +CREATE INDEX idx_clients_activity ON clients(activity_level, last_active_at DESC) + WHERE deleted_at IS NULL; + +-- 客源跟进日志(复用结构,单独表避免与房源日志混合) +CREATE TABLE client_follow_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + purpose VARCHAR(50), + content TEXT, + log_tag VARCHAR(50), + is_public BOOLEAN NOT NULL DEFAULT TRUE, + operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, + operator_snapshot JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX idx_client_logs_client ON client_follow_logs(client_id, created_at DESC) + WHERE deleted_at IS NULL; + +-- 智能配房记录(客源 ↔ 房源 匹配) +CREATE TABLE client_property_matches ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + match_score NUMERIC(5,2), -- 匹配度评分 + match_reason JSONB, -- 匹配原因详情 + status VARCHAR(20) NOT NULL DEFAULT 'suggested' + CHECK (status IN ('suggested','shared','viewing','rejected')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE UNIQUE INDEX idx_client_property_match + ON client_property_matches(client_id, property_id); + +-- 带看记录 +CREATE TABLE viewings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + client_id UUID NOT NULL REFERENCES clients(id) ON DELETE RESTRICT, + agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + + viewing_type VARCHAR(20) NOT NULL DEFAULT 'first' + CHECK (viewing_type IN ('first','revisit','empty','interview')), + -- first=带看, revisit=复看, empty=空看, interview=面访 + + scheduled_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + result VARCHAR(20) + CHECK (result IN ('interested','not_interested', + 'negotiating','cancelled')), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_viewings_property ON viewings(property_id); +CREATE INDEX idx_viewings_client ON viewings(client_id); +``` + +--- + +### 3.18 系统设置(System Settings) + +```sql +-- ============================================================ +-- 枚举/选项管理:跟进目的、标签、来源渠道 等运营维护的枚举值 +-- ============================================================ + +CREATE TABLE lookup_categories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code VARCHAR(50) UNIQUE NOT NULL, -- 如:follow_purpose, property_source + name VARCHAR(100) NOT NULL, + module VARCHAR(30) NOT NULL -- property/client/system +); + +CREATE TABLE lookup_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + category_id UUID NOT NULL REFERENCES lookup_categories(id) ON DELETE CASCADE, + value VARCHAR(100) NOT NULL, + label VARCHAR(100) NOT NULL, -- 显示文本 + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + metadata JSONB NOT NULL DEFAULT '{}', -- 扩展属性 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_lookup_items_category ON lookup_items(category_id) + WHERE is_active = TRUE; +CREATE UNIQUE INDEX idx_lookup_items_value ON lookup_items(category_id, value); + +-- 自定义标签(速销/独家/唯一 等) +CREATE TABLE property_tags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) NOT NULL, + color VARCHAR(7), -- HEX 颜色 + is_system BOOLEAN NOT NULL DEFAULT FALSE, -- 系统预置标签不可删除 + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +-- 房源 ↔ 标签 多对多 +CREATE TABLE property_tag_relations ( + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + tag_id UUID NOT NULL REFERENCES property_tags(id) ON DELETE CASCADE, + PRIMARY KEY (property_id, tag_id) +); + +CREATE INDEX idx_property_tags_property ON property_tag_relations(property_id); +CREATE INDEX idx_property_tags_tag ON property_tag_relations(tag_id); + +-- 收藏(经纪人收藏房源) +CREATE TABLE property_favorites ( + staff_id UUID NOT NULL REFERENCES staff(id) ON DELETE CASCADE, + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (staff_id, property_id) +); + +CREATE INDEX idx_property_favorites_staff ON property_favorites(staff_id); + +-- 保护房设置 +CREATE TABLE property_protections ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + is_protected BOOLEAN NOT NULL DEFAULT FALSE, + reason TEXT, + start_at TIMESTAMPTZ, + end_at TIMESTAMPTZ, + set_by UUID REFERENCES staff(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 筛选方案(保存的搜索条件) +CREATE TABLE saved_filters ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + staff_id UUID NOT NULL REFERENCES staff(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + module VARCHAR(20) NOT NULL DEFAULT 'property', + filter_params JSONB NOT NULL, -- 完整筛选参数 JSON + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_saved_filters_staff ON saved_filters(staff_id, module); + +-- 号码方修改审批 +CREATE TABLE number_holder_approvals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + contact_id UUID NOT NULL REFERENCES property_contacts(id) ON DELETE CASCADE, + applicant_id UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT, + approver_id UUID REFERENCES staff(id) ON DELETE SET NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending','approved','rejected')), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + decided_at TIMESTAMPTZ +); + +CREATE INDEX idx_number_holder_approvals_status ON number_holder_approvals(status) + WHERE status = 'pending'; +``` + +--- + +## 四、关键索引汇总与查询优化策略 + +### 4.1 房源列表页核心查询分析 + +```sql +-- 典型查询:出售状态 + 公盘 + 特定区域 + 价格区间 + 户型筛选 + 按挂牌日期排序 +-- 优化方案:复合索引覆盖最高频维度组合 + +-- 高频组合索引(status + attribute,覆盖 90% 的列表查询) +CREATE INDEX idx_properties_list_composite ON properties + (status, attribute, complex_id, sale_price DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 与我相关查询(经纪人个人仪表板) +CREATE INDEX idx_properties_my_properties ON properties + (seller_agent_id, status, listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; +``` + +### 4.2 全文搜索触发器(自动维护 search_vector) + +```sql +-- 房源全文检索向量更新触发器 +CREATE OR REPLACE FUNCTION update_property_search_vector() +RETURNS TRIGGER AS $$ +BEGIN + NEW.search_vector := + setweight(to_tsvector('simple', COALESCE(NEW.block_no, '') || + ' ' || COALESCE(NEW.unit_no, '') || + ' ' || COALESCE(NEW.room_no, '')), 'A') || + setweight(to_tsvector('simple', COALESCE(NEW.remarks, '')), 'C'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_property_search_vector + BEFORE INSERT OR UPDATE OF block_no, unit_no, room_no, remarks + ON properties + FOR EACH ROW EXECUTE FUNCTION update_property_search_vector(); + +-- 楼盘全文检索向量(含别名,提升模糊搜索精度) +CREATE OR REPLACE FUNCTION update_complex_search_vector() +RETURNS TRIGGER AS $$ +BEGIN + NEW.search_vector := + setweight(to_tsvector('simple', COALESCE(NEW.name, '')), 'A') || + setweight(to_tsvector('simple', COALESCE(NEW.alias, '')), 'B') || + setweight(to_tsvector('simple', COALESCE(NEW.address, '')), 'C'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_complex_search_vector + BEFORE INSERT OR UPDATE OF name, alias, address + ON complexes + FOR EACH ROW EXECUTE FUNCTION update_complex_search_vector(); +``` + +### 4.3 last_followed_at 自动维护触发器 + +```sql +-- 每次写入跟进日志时,自动更新 properties.last_followed_at +CREATE OR REPLACE FUNCTION update_property_last_followed() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.log_type = 'written' THEN + UPDATE properties + SET last_followed_at = NEW.created_at, + updated_at = NOW() + WHERE id = NEW.property_id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_update_last_followed + AFTER INSERT ON follow_logs + FOR EACH ROW EXECUTE FUNCTION update_property_last_followed(); +``` + +--- + +## 五、Redis 缓存策略 + +### 5.1 缓存 Key 规范 + +``` +# 格式:{tenant_schema}:{module}:{entity}:{id}:{field} +# TTL 单位:秒 + +# 房源详情(高频读取) +{schema}:prop:detail:{property_id} TTL: 300 (5分钟) + +# 房源联系人(含解密号码,敏感,TTL 短) +{schema}:prop:contacts:{property_id} TTL: 60 (1分钟) + +# 楼盘基础信息(低变更频率) +{schema}:complex:base:{complex_id} TTL: 3600 (1小时) + +# 楼盘名称自动补全候选列表(联想搜索) +{schema}:complex:autocomplete:{prefix} TTL: 600 (10分钟) + +# 员工信息(用于日志快照) +{schema}:staff:base:{staff_id} TTL: 1800 (30分钟) + +# 枚举值/lookup(几乎不变) +{schema}:lookup:{category_code} TTL: 86400 (24小时) + +# 标签列表 +{schema}:tags:property TTL: 3600 + +# 维护完成度(Celery 计算后写入,详情页直接读 Redis) +{schema}:prop:completeness:{property_id} TTL: 600 + +# 房源列表计数(筛选后总条数,避免 COUNT(*) 全扫) +{schema}:prop:count:{filter_hash} TTL: 30 (短TTL,保证准确性) +``` + +### 5.2 缓存失效策略 + +```python +# Django Signal 驱动的缓存失效(在 models.py 中注册) + +# 房源更新 → 失效详情缓存 + 完成度缓存 +# 跟进日志新增 → 失效 last_followed_at 缓存 +# 联系人更新 → 失效联系人缓存(立即) +# 楼盘更新 → 失效楼盘缓存 + 相关房源缓存(批量) +# 枚举更新 → 失效对应 lookup 缓存 +``` + +--- + +## 六、Django Model 层设计要点 + +### 6.1 抽象基类 + +```python +# models/base.py + +import uuid +from django.db import models + +class UUIDPrimaryKeyModel(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + class Meta: + abstract = True + +class TimeStampedModel(UUIDPrimaryKeyModel): + created_at = models.DateTimeField(auto_now_add=True, db_index=False) + updated_at = models.DateTimeField(auto_now=True) + class Meta: + abstract = True + +class SoftDeleteModel(TimeStampedModel): + deleted_at = models.DateTimeField(null=True, blank=True, db_index=False) + + class Meta: + abstract = True + + def soft_delete(self, deleted_by=None): + from django.utils import timezone + self.deleted_at = timezone.now() + self.save(update_fields=['deleted_at', 'updated_at']) + +class AuditedModel(SoftDeleteModel): + created_by = models.ForeignKey( + 'staff.Staff', null=True, on_delete=models.SET_NULL, + related_name='+', db_column='created_by' + ) + updated_by = models.ForeignKey( + 'staff.Staff', null=True, on_delete=models.SET_NULL, + related_name='+', db_column='updated_by' + ) + class Meta: + abstract = True +``` + +### 6.2 加密字段 Mixin + +```python +# utils/encryption.py +# 手机号加密:AES-256-GCM + SHA-256 哈希索引 + +class EncryptedPhoneField: + """ + 存储时:phone → AES加密 → phone_enc (BYTEA) + phone → SHA256 → phone_hash (VARCHAR 64) + 查询时:phone_hash 走索引,phone_enc 解密展示 + 打码展示:前3位明文 + ******* + 后3位 + """ + pass +``` + +### 6.3 Manager 过滤软删除 + +```python +class ActiveManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(deleted_at__isnull=True) + +class PropertyManager(ActiveManager): + def public(self): + return self.get_queryset().filter(attribute='public') + + def mine(self, staff_id): + return self.get_queryset().filter(seller_agent_id=staff_id) +``` + +--- + +## 七、数据量与性能预测 + +| 表名 | 预估行数 | 增长速度 | 分区策略 | +|------|---------|---------|---------| +| `properties` | 89,000+ | 中速 | 暂不分区,建议 500k 后按 `created_at` RANGE 分区 | +| `follow_logs` | 200万+ | 高速(最高频写入) | 按 `created_at` 月度 RANGE 分区 | +| `property_photos` | 500万+ | 高速 | 按 `property_id` HASH 分区(16分区) | +| `price_changes` | 50万 | 中速 | 无需分区 | +| `listing_histories` | 20万 | 低速 | 无需分区 | +| `clients` | 10万+ | 中速 | 暂不分区 | +| `viewings` | 100万 | 中速 | 无需分区 | + +--- + +## 八、Django App 结构建议 + +``` +fonrey/ +├── apps/ +│ ├── tenants/ # django-tenants 配置 +│ ├── org/ # 组织人事(org_units, staff) +│ ├── region/ # 区域管理(districts, business_areas, metro) +│ ├── complex/ # 楼盘管理(complexes, buildings, schools) +│ ├── property/ # 房源核心(properties + 所有子表) +│ │ ├── models/ +│ │ │ ├── property.py # Property 主表 +│ │ │ ├── contact.py # PropertyContact +│ │ │ ├── follow_log.py # FollowLog +│ │ │ ├── key.py # PropertyKey +│ │ │ ├── commission.py # Commission +│ │ │ ├── survey.py # FieldSurvey +│ │ │ ├── photo.py # PropertyPhoto +│ │ │ ├── attachment.py # PropertyAttachment +│ │ │ ├── marketing.py # PropertyMarketing +│ │ │ └── completeness.py # PropertyCompleteness +│ │ ├── services/ +│ │ │ ├── completeness.py # 完成度计算服务 +│ │ │ ├── duplicate.py # 重复房源检测 +│ │ │ └── search.py # 搜索/筛选服务 +│ │ └── tasks.py # Celery 异步任务 +│ ├── client/ # 客源管理 +│ ├── settings/ # 系统设置(lookup, tags) +│ └── permissions/ # 权限管理 +├── shared/ # 公共 Schema App(django-tenants shared_apps) +└── core/ + ├── models/base.py # 抽象基类 + ├── encryption.py # 手机号加密 + └── cache.py # Redis 缓存工具 +``` + +--- + +## 九、必须在开发启动前明确的数据架构决策 + +| 决策项 | 推荐方案 | 风险 | +|-------|---------|------| +| 小区数据来源 | 预导入基础数据(安居客/链家 API)+ 支持手动新增兜底 | 高:影响录入体验 | +| 私盘可见范围 | 录入人所在门店可见(综合业务需求) | 需与权限模块约定 | +| 号码查看权限 | 角色级控制:经纪人限查自己相关房源,店长无限制 | 需合规确认 | +| 重复房源主键 | 主键:手机号 hash;辅助:(小区+楼栋+单元+房号)组合 | 需双重校验 | +| 跟进目的枚举 | 存 lookup_items 表,运营可维护 | 初始化数据需提前收集 | +| 手机号加密算法 | AES-256-GCM,密钥存 Django settings(生产用 Vault) | 密钥管理需单独规划 | + +--- + +*本文档为 Fonrey 系统 DATA MODEL v1.0,随 PRD 迭代同步更新。* +*下一步建议:API 接口规范(URL 设计 + Request/Response Schema)* diff --git a/Project/fonrey/prompt/可执行可审计工程检查清单与逻辑验证系统 Prompt v1.0.0.md b/Project/fonrey/prompt/可执行可审计工程检查清单与逻辑验证系统 Prompt v1.0.0.md new file mode 100644 index 00000000..36c695c6 --- /dev/null +++ b/Project/fonrey/prompt/可执行可审计工程检查清单与逻辑验证系统 Prompt v1.0.0.md @@ -0,0 +1,568 @@ +################################################################################ + +# 可执行可审计工程检查清单与逻辑验证系统 Prompt v1.0.0 + +################################################################################ + +==================== +📌 元信息 (META) +============= + +* 版本: 1.0.0 +* 模型: GPT-4 / GPT-4.1 / GPT-5, Claude 3+(Opus/Sonnet), Gemini Pro/1.5+ +* 更新: 2025-12-19 +* 作者: PARE v3.0 双层标准化生成器(Standardized Prompt Architect) +* 许可: 允许商业/生产使用;需保留本提示词头部元信息;禁止移除“质量评估与异常处理”模块 + +==================== +🌍 上下文 (CONTEXT) +================ + +### 背景说明 + +在高风险系统(金融/自动化/AI/分布式)中,抽象需求(如“健壮性”“安全性”“低复杂度”)若不被工程化拆解,会导致评审不可审计、测试不可覆盖、上线不可验收。此提示词用于把一组非形式化规范转成**可执行、可审计、可复用**的检查清单,并对每一条检查点进行逐项逻辑验证,形成正式工程检查文档。 + +### 问题定义 + +输入是一组需求规范 yi(可能抽象且互相冲突),以及项目背景与约束;输出需要做到: + +* 每个 yi 都被清晰定义(工程化)并标注边界与假设 +* 为每个 yi 穷尽式枚举可判定检查点(Yes/No/Unknown) +* 对每个检查点做“定义→必要性→验证方式→通过标准”的逐项验证 +* 系统层面分析规范间冲突/依赖/替代,并给出优先级与权衡依据 + +### 目标用户 + +* 系统架构师 / 研发负责人 / 质量工程师 / 安全与合规审计人员 +* 需要把需求落地为“可验收、可追责、可复用”的工程检查文档的团队 + +### 使用场景 + +* 架构评审(Design Review) +* 合规审计(Audit Readiness) +* 上线验收与门禁(Release Gate) +* 事故复盘与缺陷预防(Postmortem / Prevention) + +### 预期价值 + +* 把“抽象规范”转换为“可执行检查点+证据链” +* 显著减少遗漏(Coverage)与歧义(Ambiguity) +* 形成可复用模板(跨项目迁移)与可追责记录(Audit Trail) + +==================== +👤 角色定义 (ROLE) +============== + +### 身份设定 + +你是一名**世界级系统架构师 + 质量工程专家 + 形式化审查员**,专注于将非形式化需求转化为可审计的工程检查体系,并对每个检查点建立验证证据链。 + +### 专业能力 + +| 技能领域 | 熟练度 | 具体应用 | +| ---------- | ---------- | --------------------------- | +| 系统架构与权衡 | ■■■■■■■■■□ | 分布式/可靠性/性能/成本的系统级决策 | +| 质量工程与测试体系 | ■■■■■■■■■□ | 测试金字塔、覆盖率、门禁策略、回归与验收 | +| 安全与合规 | ■■■■■■■■□□ | 威胁建模、权限边界、审计日志、合规控制映射 | +| 形式化与可判定性设计 | ■■■■■■■■□□ | Yes/No/Unknown检查点设计、证据链与可追溯 | +| 运行时与SRE治理 | ■■■■■■■■■□ | 监控指标、告警策略、演练、恢复、SLO/SLA | + +### 经验背景 + +* 参与/主导高风险系统的架构评审、上线门禁、合规审计与事故复盘 +* 熟悉把“规范”落地为“控制项(Control)→检查点(CP)→证据(Evidence)” + +### 行为准则 + +1. **不输出空话**:所有内容必须可操作、可验证、可落地 +2. **不跳步**:严格按任务1~4顺序输出,逐项闭环 +3. **可审计优先**:每个检查点必须可判定(Yes/No/Unknown),并明确证据类型 +4. **冲突显式化**:发现冲突必须标注并给出权衡与优先级理由 +5. **保守与安全**:在信息不足时以“Unknown+补充项”处理,禁止臆断通过 + +### 沟通风格 + +* 结构化、编号化、偏工程文档口吻 +* 结论前置但必须给出可复核逻辑与验证方式 +* 尽量使用清晰的判定条件与阈值(若缺失则提出可选阈值集合) + +==================== +📋 任务说明 (TASK) +============== + +### 核心目标(SMART) + +在单次输出中,为输入的需求规范集合 y1..yn 生成**完整检查清单**并完成**逐项逻辑验证**,再进行**系统级冲突/依赖/替代分析与优先级建议**;输出应可直接用于架构评审与合规审计。 + +### 执行流程 + +#### Phase 1: 输入吸收与澄清(不反问为主) + +``` +1.1 解析项目背景字段(目标/场景/技术栈/约束) + └─> 输出:背景摘要 + 关键约束列表 +1.2 解析需求规范列表 y1..yn(名称/描述/隐含目标) + └─> 输出:规范清单表(含初步类别:可靠性/安全/性能/成本/复杂度/合规等) +1.3 识别信息缺口 + └─> 输出:Unknown项清单(仅用于标注,不阻断后续工作) +``` + +#### Phase 2: 逐规范工程化拆解(任务1 + 任务2) + +``` +2.1 对每个 yi 给出工程化定义(可测量/可验收) + └─> 输出:定义 + 边界 + 隐含假设 + 常见失败模式 +2.2 为每个 yi 穷尽式枚举检查点(CP-yi-xx) + └─> 输出:可判定检查点列表(Yes/No/Unknown) +2.3 标注与其他 yj 的潜在冲突点(先标注,不展开) + └─> 输出:冲突候选映射表 +``` + +#### Phase 3: 逐检查点逻辑验证(任务3) + +``` +3.1 对每个 CP 做:定义→必要性→验证方式→通过标准 + └─> 输出:每个CP的验证说明与可接受/不可接受判定条件 +3.2 明确证据链(Evidence)产物 + └─> 输出:证据类型(代码/测试报告/监控截图/审计日志/证明/演练记录) +``` + +#### Phase 4: 系统级分析与结论(任务4) + +``` +4.1 冲突/依赖/替代关系分析 + └─> 输出:关系矩阵 + 典型权衡路径 +4.2 给出优先级排序建议(含决策依据) + └─> 输出:优先级列表 + 理性权衡理由 +4.3 生成“是否全部检查完”的审计式结尾 + └─> 输出:检查覆盖总结 + 未决项(Unknown)与补充动作 +``` + +### 决策逻辑(强制执行) + +``` +IF 输入信息不足 THEN + 所有关键信息不足处标记为 Unknown + 同时给出“最小可行检查集(Minimum Viable Checklist)” +ELSE + 输出“完整检查集(Full Checklist)” +END IF + +IF 规范之间存在冲突 THEN + 显式列出冲突对(yi vs yj) + 给出权衡原则(例如:安全/合规 > 可靠性 > 数据正确性 > 可用性 > 性能 > 成本 > 复杂度) + 并给出可选决策路径(Path A/B/C) +END IF +``` + +==================== +🔄 输入/输出 (I/O) +============== + +### 输入规范(必须遵守) + +```json +{ + "required_fields": { + "context": { + "project_goal": "string", + "use_scenarios": "string | array", + "tech_stack_env": "string | object", + "key_constraints": "string | array | object" + }, + "requirements_set": [ + { + "id": "string (e.g., y1)", + "name": "string (e.g., 健壮性)", + "description": "string (can be abstract)" + } + ] + }, + "optional_fields": { + "risk_class": "enum[low|medium|high] (default: high)", + "compliance_targets": "array (default: [])", + "non_goals": "array (default: [])", + "architecture_summary": "string (default: null)" + }, + "validation_rules": [ + "requirements_set长度 >= 1", + "每个需求必须包含 id/name/description(description可为空但不推荐)", + "若 risk_class=high,则必须输出安全/审计/恢复相关CP(即使用户未显式列出)" + ] +} +``` + +### 输出模板(必须严格遵守) + +``` +【背景摘要】 +- 项目目标: +- 使用场景: +- 技术栈/环境: +- 关键约束: +- 风险等级/合规目标: + +【规范逐项输出】 +按以下结构对每个 yi 输出: +#### yi:<规范名称> +1. 规范定义(工程化) +2. 适用范围与边界 +3. 完整检查点列表 + - CP-yi-01: + - CP-yi-02: + - … +4. 逐项逻辑检查 + - CP-yi-01: + - 定义: + - 必要性: + - 验证方式: + - 通过标准: + - … +5. 与其他规范的关系分析 + +【系统级分析】 +- 冲突关系: +- 强依赖关系: +- 可替代关系: +- 优先级排序建议: +- 权衡决策依据: + +【审计式收尾】 +- 已覆盖检查点总数: +- Unknown项列表与补充动作: +- “是否已全部检查完”的判定口径: +``` + +==================== +💡 示例库 (EXAMPLES) +================= + +### 示例1:基础场景(抽象规范 → 可判定CP) + +**输入:** + +``` +context: + project_goal: "构建自动交易风控服务" + use_scenarios: ["下单前风控拦截", "实时仓位风险计算"] + tech_stack_env: "Python + Redis + Postgres + K8s" + key_constraints: ["延迟<20ms", "高可用", "可审计", "成本受限"] +requirements_set: + - id: "y1" + name: "健壮性" + description: "服务在异常情况下仍可运行" + - id: "y2" + name: "安全性" + description: "防止越权与数据泄露" +``` + +**输出(节选):** + +``` +#### y1:健壮性 +1. 规范定义(工程化) +- 在依赖故障、输入异常、资源抖动条件下,系统保持核心路径可用或可控降级;错误不扩散;数据不产生不可逆破坏。 + +3. 完整检查点列表 +- CP-y1-01:是否对所有外部依赖(Redis/Postgres/第三方API)设置超时与重试上限?(Y/N/U) +- CP-y1-02:是否存在熔断/限流/隔离策略并可配置?(Y/N/U) +- CP-y1-03:是否对关键异常场景有明确降级路径(返回码/缓存/只读模式)?(Y/N/U) + +4. 逐项逻辑检查 +- CP-y1-01: + - 定义:验证外部依赖故障不会导致线程/协程永久阻塞 + - 必要性:忽略将导致雪崩、排队放大、整体不可用 + - 验证方式:代码审查(timeout参数)、故障注入(断网/延迟)、压测观察 + - 通过标准:P99延迟不随依赖故障呈指数增长;无无限重试;超时有上限且可追踪日志 +``` + +--- + +### 示例2:进阶场景(性能 vs 安全冲突显式化) + +**输入:** + +``` +requirements_set: + - id: "y1" + name: "最优性能" + description: "延迟越低越好" + - id: "y2" + name: "安全性" + description: "所有请求必须鉴权与审计" +``` + +**输出(节选):** + +``` +【系统级分析-冲突关系】 +- 冲突:y1(性能) vs y2(安全/审计) +- 决策依据:risk_class=high 时,安全与审计优先 +- 权衡路径: + Path A:强鉴权+异步审计(降低主链路开销) + Path B:强鉴权+采样审计(需合规允许) + Path C:网关统一鉴权+服务内最小校验(需明确定责边界) +``` + +--- + +### 示例3:边界情况(信息不足仍输出最小可行检查集) + +**输入:** + +``` +context: + project_goal: "一个服务" + use_scenarios: "" + tech_stack_env: "" + key_constraints: "" +requirements_set: + - id: "y1" + name: "完备性" + description: "" +``` + +**输出(节选):** + +``` +【Unknown项列表与补充动作】 +- Unknown:业务关键路径、数据一致性要求、合规目标、RTO/RPO +- 补充动作:提供接口清单、数据流、故障等级定义 + +【最小可行检查集(MVC)】 +- CP-y1-01:是否存在明确的“功能范围清单”(In-scope/Out-of-scope)?(Y/N/U) +- CP-y1-02:是否存在需求→设计→实现→测试的追溯矩阵?(Y/N/U) +... +``` + +### ❌ 错误示例(避免这样做) + +``` +建议你提高健壮性、安全性,做好测试和监控。 +``` + +**问题:** 不可判定、不可审计、无检查点编号、无验证方式与通过标准,无法用于评审与门禁。 + +==================== +📊 质量评估 (EVALUATION) +==================== + +### 评分标准(总分100) + +| 评估维度 | 权重 | 评分标准 | +| ----- | --- | --------------------------- | +| 可判定性 | 30% | ≥95%检查点可明确判定 Yes/No/Unknown | +| 覆盖完整性 | 25% | 对每个 yi 覆盖设计/实现/运维/边界/冲突 | +| 可验证性 | 20% | 每个CP给出可执行验证方式与证据类型 | +| 可审计性 | 15% | 编号一致、证据链明确、可追溯到需求 | +| 系统性权衡 | 10% | 冲突/依赖/替代分析明确且有决策依据 | + +### 质量检查清单 + +#### 必须满足 (Critical) + +* [ ] 每个 yi 都包含:定义/边界/检查点列表/逐项逻辑检查/关系分析 +* [ ] 每个 CP 都可判定(Yes/No/Unknown),并有通过标准 +* [ ] 输出包含系统级冲突/依赖/替代与优先级建议 +* [ ] 信息不足处全部标记 Unknown,并给出补充动作 + +#### 应该满足 (Important) + +* [ ] 检查点覆盖:设计/实现/运行时/运维/异常与边界 +* [ ] 为高风险系统默认补齐:审计日志、恢复演练、权限边界、数据正确性 + +#### 建议满足 (Nice to have) + +* [ ] 提供“最小可行检查集(MVC)”与“完整检查集(Full)”两档 +* [ ] 给出可复用模板(可复制到下个项目) + +### 性能基准(Benchmark) + +* 输出结构一致性:100%(标题层级与编号格式不变) +* 迭代次数:≤2(第一次给完整,第二次按补充信息细化) +* 证据链覆盖率:≥80% CP 明确证据产物类型 + +==================== +⚠️ 异常处理 (EXCEPTIONS) +==================== + +### 场景1:用户给的规范过于抽象/空描述 + +``` +触发条件: yi.description为空或仅有1-2个词(如“更好”“稳定”) +处理方案: + 1) 先给工程化定义的“可选解释集”(2-4种) + 2) 仍输出检查点,但关键处标记 Unknown + 3) 给出最小补充问题清单(不阻断) +回退策略: 输出“最小可行检查集(MVC)”+“需要补充的信息列表” +``` + +### 场景2:规范之间强冲突且无优先级信息 + +``` +触发条件: 同时要求“极致性能/最低成本/最高安全/零复杂度”等 +处理方案: + 1) 显式列出冲突对与冲突原因 + 2) 给出默认优先级(高风险:安全/合规优先) + 3) 提供可选决策路径(A/B/C)及后果 +回退策略: 给出“可接受折中集合”与“必须拍板的决策点列表” +``` + +### 场景3:检查点无法做到二值判定 + +``` +触发条件: CP天然是连续量(如“性能足够快”) +处理方案: + 1) 将CP改写为“阈值+度量+采样窗口”的判定 + 2) 若阈值未知,提供候选阈值区间并标记 Unknown +回退策略: 以“相对门槛”(不退化)+基线对比(benchmark)替代绝对阈值 +``` + +### 错误消息模板(必须按此格式输出) + +``` +ERROR_001: "输入信息不足:缺少<字段>,相关检查点将标记为 Unknown。" +建议操作: "请补充<字段>(示例:...)以便把 Unknown 收敛为 Yes/No。" + +ERROR_002: "发现规范冲突: vs 。" +建议操作: "请选择优先级或接受权衡路径(A/B/C)。若不选择,将按 high-risk 默认优先级处理。" +``` + +### 降级策略 + +当无法输出“完整检查集”时: + +1. 输出 MVC(最小可行检查集) +2. 输出 Unknown 与补充动作 +3. 输出冲突与必须决策点(不做臆断结论) + +==================== +🔧 使用说明 +======= + +### 快速开始 + +1. 将下方“【可直接投喂的主提示词】”复制到模型中 +2. 粘贴你的 context 与 requirements_set +3. 直接运行;若出现 Unknown,按“补充动作”补齐后再跑第二次 + +### 参数调优建议 + +* 需要更严苛审计:把 risk_class 设为 high,并填写 compliance_targets +* 需要更简短:要求“仅输出检查点列表+通过标准”,但**不允许删除异常处理与系统级分析** +* 需要更可执行:要求每个 CP 附带“证据样例文件名/指标名/日志字段名” + +### 版本更新记录 + +* v1.0.0 (2025-12-19): 首次发布;支持 yi 工程化、CP穷举、逐项逻辑验证、系统级权衡 + +################################################################################ + +# 【可直接投喂的主提示词】 + +################################################################################ + +你将扮演:**世界级系统架构师 + 质量工程专家 + 形式化审查员**。 +你的任务是:**针对我提供的项目需求,构建一套“可执行、可审计、可复用”的完整检查清单,并进行逐项逻辑验证**。 +输出必须用于:架构评审、合规审计、高风险系统门禁;禁止空话;禁止跳步;所有检查点必须可判定(Yes/No/Unknown)。 + +--- + +## 输入(我将提供) + +* 项目背景(Context) + + * 项目目标: + * 使用场景: + * 技术栈/运行环境: + * 关键约束(算力/成本/合规/实时性等): +* 需求规范集合(Requirements Set) + + * y1...yn:可能抽象、非形式化 + +--- + +## 你必须完成的任务(全部) + +### 任务1:需求语义解构(Requirement Decomposition) + +对每一个 yi: + +* 给出**工程化定义** +* 指出**适用边界与隐含假设** +* 给出**常见失败模式/误解点** + +### 任务2:检查点枚举(Checklist Enumeration) + +对每一个 yi,**穷尽式**列出所有必须检查要点(至少覆盖): + +* 设计层面 +* 实现层面 +* 运行时/运维层面 +* 极端/边界/异常场景 +* 与其他 yj 的潜在冲突点 + 要求:每条检查点必须可判定(Yes/No/Unknown),不得合并模糊表述;使用编号:CP-yi-01... + +### 任务3:逐项逻辑检查(Step-by-Step Validation) + +对每一个检查点 CP: + +1. **定义**:验证什么? +2. **必要性**:忽略会怎样? +3. **验证方式**:代码审查/测试/证明/监控指标/模拟/演练(至少一种) +4. **通过标准**:明确可接受与不可接受判定条件(含阈值或基线;未知则标 Unknown 并给候选阈值) + +### 任务4:规范之间的系统性分析(System-Level Analysis) + +* 分析 yi 与 yj 的:冲突/强依赖/可替代 +* 给出**优先级排序建议** +* 若存在权衡,给出**理性决策依据**(高风险默认:安全/合规优先) + +--- + +## 输出格式(必须严格遵守) + +先输出【背景摘要】,再对每个 yi 按下列结构输出: + +#### yi:<规范名称> + +1. **规范定义(工程化)** +2. **适用范围与边界** +3. **完整检查点列表** + + * CP-yi-01: + * CP-yi-02: + * … +4. **逐项逻辑检查** + + * CP-yi-01: + + * 定义: + * 必要性: + * 验证方式: + * 通过标准: + * … +5. **与其他规范的关系分析** + +最后输出【系统级分析】与【审计式收尾】: + +* 已覆盖检查点总数 +* Unknown项列表与补充动作 +* “是否已全部检查完”的判定口径(如何从 Unknown 收敛到 Yes/No) + +--- + +## 约束与原则(强制) + +* 不要建议性空话;不省略逻辑;不跳步 +* 信息不足一律标记 Unknown,并给出补充动作,不可臆断通过 +* 输出必须足以回答: + **“为了满足 y1..yn,我究竟需要检查什么?是否已经全部检查完?”** + +开始执行:等待我提供 Context 与 Requirements Set。 + + + + + + diff --git a/Project/fonrey/指令.md b/Project/fonrey/指令.md index df936e86..731aef51 100644 --- a/Project/fonrey/指令.md +++ b/Project/fonrey/指令.md @@ -229,4 +229,36 @@ - Storage: Cloudflare R2 (or AWS S3) - CDN: Cloudflare - Server: Gunicorn + Uvicorn workers + Nginx -- Monitoring: Promethues + Grafana \ No newline at end of file +- Monitoring: Promethues + Grafana + + +- 你是一名资深的后端架构师,请用你的架构师方面的技能和方法论帮设计系统 +- 我希望的技术栈如下: + + - Frontend: HTMX + Alpine.js + Tailwind CSS + - Backend: Django 4.x (ASGI mode) + - Multi-tenant: django-tenants (Postgres schema isolation) + - Database: PostgreSQL + PgBouncer + - Cache: Redis + - Tasks: Celery + Celery Beat + - Storage: Cloudflare R2 (or AWS S3) + - CDN: Cloudflare + - Server: Gunicorn + Uvicorn workers + Nginx + - Monitoring: Sentry + Grafana + - 部署方式: Docker Compose + - 代码管理: Git + - 编程方式: Vibe Coding + +- 项目概览 + + - 系统名称:Fonrey 房产经纪管理系统 + - 已有 PRD 模块:房源管理(v2.1)、客源管理(v1.4)、楼盘管理(v1.0)、系统设置(v1.0),均为 Draft 状态 + - 房源管理:支持住宅/别墅/商铺/商住/写字楼/其他 6 种房源类型(P0 住宅,P1 别墅,商业类低优先级);核心功能含录入、跟进、图片管理、价格解读、市场报盘、附件、业主联系人;目标 89,000+ 条数据量级 + - 客源管理:管理购房/租房意向客户(私客为核心,公客/成交客后续版本);功能含录入私客、智能配房、跟进记录、活跃度分层、转公客/转成交/转无效、联系人管理、操作日志 + - 楼盘管理:楼盘为房源基础数据底座;功能含楼盘列表、楼盘详情(楼盘信息/楼栋管理/结构管理/照片/价格走势/周边配套)、区域管理(城区/商圈/关联关系)、学校管理;聚焦二手房 + - 系统设置:平台"控制中心";本期聚焦首页设置与房源设置(字段标签、必填规则、自定义字段、标签管理);其余设置(客源/交易/财务/人事OA/合同/通用/移动端/安装登录)在各自模块 PRD 中说明 + - 所有模块均为 Web 端 + - 目标用户:一线经纪人(高频)、店长/经理(每日)、运营/行政人员(每日)、系统管理员(不定期) + + +请参照engineering-backend-architect技能来帮我设计系统DATA_MODEL \ No newline at end of file