Compare commits

..

204 Commits

Author SHA1 Message Date
454e71fb3f 新增Leo学习文件夹 2026-06-19 06:29:29 +08:00
91f9e29167 公司信息 2026-06-17 09:53:39 +08:00
992fb88ff4 更新tag 2026-06-04 15:43:50 +08:00
b6e3e859e1 合并文档 2026-06-04 15:42:44 +08:00
c4f76d9c2a 详细更新 2026-06-04 15:22:18 +08:00
4141e18896 add tag 2026-06-04 15:13:56 +08:00
836bbdd90d 合并文档并更新 2026-06-04 15:12:25 +08:00
b147c5730f remove csd-wiki 2026-06-04 14:37:20 +08:00
b305f2ee0c delete 2026-06-04 14:36:25 +08:00
06dde3173a remove wiki 2026-06-04 14:35:22 +08:00
e01ca79bdc 文档更新 2026-06-04 14:34:32 +08:00
7b91c310eb Auto-sync 2026-05-23 21:47:47 +08:00
369c9e90ea Add MP3 metadata automation guide 2026-05-20 13:09:50 +08:00
17e67f7bd6 Auto-sync 2026-05-16 21:49:09 +08:00
b40abbcd47 Auto-sync 2026-05-10 02:00:58 +08:00
111bc65b7b Update nexus wiki content 2026-05-03 05:42:12 +08:00
Shen Wei
90f3811b83 review 结果 2026-05-02 16:21:46 +08:00
Shen Wei
ca33cc141f 文档更新 2026-05-02 11:35:20 +08:00
Shen Wei
464c5fce51 系统管理模块更新 2026-05-02 05:10:16 +08:00
804954c367 ingest: marketing-agentic-search-optimizer.md (Agentic Search Optimizer Agent)
- Add wiki/sources/marketing-agentic-search-optimizer.md (Source Page)
- Update wiki/index.md: fix missing source marker → full entry
- Append wiki/log.md entry

Ingest steps 1-9 completed:
1. Read raw/Agent/agency-agents/marketing/marketing-agentic-search-optimizer.md
2. Read wiki/index.md + wiki/overview.md
3. Generate source page (slug: marketing-agentic-search-optimizer)
4. Update index.md
5. overview.md already has [[Marketing Agentic Search Optimizer]] ref (no dup needed)
6. Entity check: no entities ≥2 mentions (Chrome/Edge/Claude/Perplexity each 1×)
7. Concept check: WebMCP concepts described in source page only (W3C draft, conservative)
8. Contradiction check: none found
9. Append log.md
2026-05-02 02:48:41 +08:00
8525e8e6da ingest: CMS Developer Agent Personality (engineering-cms-developer.md)
- Source: sources/engineering-cms-developer.md
- Entities: WordPress, Drupal
- Concepts: ContentModel-first, CodeOverConfiguration
- Updated: index.md (Sources/Entities/Concepts), overview.md, log.md
2026-05-01 15:21:22 +08:00
f96c445cec ingest: Book Co-Author source + 7 new concepts 2026-04-30 23:22:04 +08:00
Shen Wei
b7469954c5 添加 README 2026-04-30 20:35:45 +08:00
Shen Wei
94d8061eb5 添加变更历史 添加ARD文档 2026-04-30 20:33:51 +08:00
Shen Wei
57600598ac 登录模块审核 2026-04-30 18:40:55 +08:00
Shen Wei
4030a91100 项目骨架搭建实施报告v2 2026-04-30 13:15:00 +08:00
Shen Wei
1da12a27f0 Permission seed 文档 2026-04-30 12:47:35 +08:00
Shen Wei
91fc0b06d6 data model注释补全 2026-04-30 06:57:02 +08:00
Shen Wei
eada3af824 注释补全 2026-04-30 06:33:50 +08:00
c961c6a394 Auto-sync: update nexus workspace 2026-04-29 15:44:42 +08:00
Shen Wei
b2aadf771a 文档修改 2026-04-29 15:43:49 +08:00
c3f9de5f9f ingest: Public Cloud Learning Sessions - EC2 Cost Optimization AWS (2024-05-29)
Source: Cloud & DevOps/Public-Cloud-Learning-Sessions/05_FinOps/public-cloud-learning-sessions-best-practices-for-ec2-cost-optimization-in-aws-2.md

Entities: Mike-Dukes, Steele-Taylor, Spot-Invaders
Concepts: AWS-Nitro, EC2-Spot-Instances, ECS
Concepts updated: Graviton, SpotInstances (added source reference)
2026-04-29 11:38:37 +08:00
070bd42886 Auto-sync: update nexus workspace 2026-04-29 07:09:27 +08:00
Shen Wei
15cd44b2ca 新增笔记 2026-04-29 07:08:27 +08:00
Shen Wei
faf3aa51bb 新增笔记 2026-04-29 07:08:08 +08:00
eedfafcae2 Auto-sync: 2026-04-29 04:03 2026-04-29 04:03:31 +08:00
2c56d5a031 Auto-sync Quartz output: 2026-04-29 00:05 2026-04-29 00:05:47 +08:00
74d02d0df2 Auto-sync: 2026-04-29 00:02 2026-04-29 00:02:51 +08:00
0e548ce5dc Auto-sync post-Quartz: 2026-04-28 20:09 2026-04-28 20:09:30 +08:00
f71229f0c3 Auto-sync: 2026-04-28 20:03 2026-04-28 20:03:11 +08:00
c51cc4c58b ingest: CTP Topic 35 AWS Landing Zone Design Refresher (SaaS Labs)
- Source page: fix broken wikilinks, add Contradictions (bidirectional with ctp-topic-1)
- Entities: create Cloud-Technology-Design-Forum
- Concepts: create Network-Segmentation
- index.md: add date+summary to ctp-topic-35, add new Entity+Concept entries
- log.md: append ingest record
2026-04-28 20:03:11 +08:00
Shen Wei
e4cf7f8485 chore: sync local project changes 2026-04-28 16:39:52 +08:00
365caa800a Auto-sync: 2026-04-28 16:03 2026-04-28 16:03:03 +08:00
f8b421ece6 Auto-sync: 2026-04-28 12:03 2026-04-28 12:03:10 +08:00
c898cc3fb9 Auto-sync: 2026-04-28 08:02 2026-04-28 08:02:54 +08:00
Shen Wei
cf4001c4f8 修改文档 2026-04-28 07:38:30 +08:00
3224ec4787 Auto-sync: update nexus workspace 2026-04-28 07:26:52 +08:00
b83b4e3105 Auto-sync: 2026-04-28 04:02 2026-04-28 04:02:45 +08:00
0d764a0c4a ingest: How to Get the RSS Feed For Any YouTube Channel
- Add source page with Summary, Key Claims, Key Quotes, Key Concepts, Key Entities
- Update index.md Sources section
- Add log entry
2026-04-28 03:46:44 +08:00
b574c99af6 Auto-sync: 2026-04-28 00:02 2026-04-28 00:02:50 +08:00
de7ebe9256 Auto-sync: 2026-04-27 20:02 2026-04-27 20:02:52 +08:00
Shen Wei
5854781fa8 chore: sync local project changes 2026-04-27 16:26:34 +08:00
dfcf7de003 Auto-sync: 2026-04-27 16:02 2026-04-27 16:02:59 +08:00
Shen Wei
712a33fbac docs: 新增系统配置模块PRD及数据模型文档,更新TASK.md
- 新增 PRD/系统配置/系统配置模块PRD.md(v0.1 Draft)
  - MVP 范围:US-SETTING-001-A(Lookup Items)、B(房源字段必填规则)、C(客源录入规则)
- 新增 PRD/系统配置/系统配置数据模型设计说明_for_Atlas.md
- 新增 PRD/系统配置/系统配置参数数据.md(竞品参数数据)
- 删除旧版 PRD/系统配置/系统配置.md(已被新PRD替代)
- 新增 DATA_MODEL/DATA_MODEL_SETTING.md(系统配置数据模型)
- 新增 DATA_MODEL/ENUMS.md(枚举定义与约定)
- 新增 AGENTS.md(AI Agent 开发规范)
- 更新 PRD/TASK.md:US-SETTING-001 拆分为 A/B/C 三个子任务,修正参考文档路径与验收标准
- 新增 VIBE_CODING_开工前缺失清单.md
- 新增 TECH_STACK/房源管理技术方案.md
- 更新 DATA_MODEL/DATA_MODEL.md、DATA_MODEL_CLIENT.md、DATA_MODEL_LOGIN.md
- 更新 PRD/PRD_MVP.md、PRD/权限管理/权限管理模块PRD.md
- 更新 TECH_STACK/TECH_STACK.md、权限管理系统技术方案.md
- 更新 UI_DESIGN/preview.html、UI_SYSTEM/UI_SYSTEM.md
- 新增 prompt/PRD - 为系统设置生成PRD设计文档.md、更新 prompt 模板
2026-04-27 15:33:12 +08:00
4422c0eac8 Sync: add productivity workflow notes 2026-04-27 15:29:47 +08:00
83c6e24e7c Auto-sync: 2026-04-27 12:02 2026-04-27 12:03:03 +08:00
fbd6107be4 Auto-sync: 2026-04-27 08:02 2026-04-27 08:02:55 +08:00
ishenwei
1c7c7d673e docs(fonrey): add web coding startup checklist 2026-04-27 06:05:28 +08:00
1642757cd0 Auto-sync: 2026-04-27 04:02 2026-04-27 04:02:52 +08:00
23bef113dd Auto-sync: 2026-04-27 00:02 2026-04-27 00:02:56 +08:00
997e25aae6 chore: log ingest for NodeWarden 2026-04-26 21:47:33 +08:00
Shen Wei
6dc50b68a0 文档修改 2026-04-26 20:33:29 +08:00
b7d9d0f5d1 Auto-sync: 2026-04-26 20:02 2026-04-26 20:02:45 +08:00
Shen Wei
d42bc16120 文档修改 2026-04-26 19:50:01 +08:00
d2ae5b3948 Auto-sync: 2026-04-26 16:02 2026-04-26 16:02:45 +08:00
Shen Wei
1abf0d56f5 修改文档 2026-04-26 14:03:16 +08:00
Shen Wei
6270ba56ee 修改文档 2026-04-26 12:49:46 +08:00
ecdf295ded Sync: add ai finops and deployment notes 2026-04-26 12:35:59 +08:00
Shen Wei
f09834b5a5 Update nexus: fix conflicts and sync local changes 2026-04-26 12:06:50 +08:00
191797c01b Auto-sync: 2026-04-26 12:02 2026-04-26 12:02:53 +08:00
c073392db8 Auto-sync: 2026-04-26 08:02 2026-04-26 08:02:48 +08:00
22f77e0660 Auto-sync: 2026-04-26 04:02 2026-04-26 04:02:54 +08:00
f1de106298 Auto-sync: 2026-04-26 00:02 2026-04-26 00:02:55 +08:00
8cceccf489 feat(wiki): ingest nexus-spatial-discovery — 8-agent product discovery exercise
Ingest raw/Agent/agency-agents/examples/nexus-spatial-discovery.md
Source: 8 The Agency agents parallel discovery exercise for Nexus Spatial
Products: AI spatial command center (SpatialAIOps category)
Key findings: 2D-first/WebXR strategy, debugging as killer use case

Entities created: Nexus-Spatial, CrewAI
Concepts created: SpatialAIOps, Command-Theater-Interface,
  Debugging-Visualization, Semantic-Zoom, Growth-Loop
2026-04-25 23:39:53 +08:00
82741b1c69 ingest: support-analytics-reporter.md
- Source page: Analytics Reporter Agent Personality
- Concepts: RFM-Analysis, Marketing-Attribution
- Updated: index.md (entry fix), overview.md (Support dept), log.md
2026-04-25 21:06:28 +08:00
c304b2b2e2 修改模板文档 2026-04-25 20:40:14 +08:00
52337cf662 更新文件名 2026-04-25 20:24:32 +08:00
c5a6f4d76e Sync: add testing tool evaluation notes 2026-04-25 20:22:57 +08:00
1e8673c5dd 静态页面 2026-04-25 20:03:40 +08:00
5c6911b44d Auto-sync: 2026-04-25 20:02 2026-04-25 20:02:49 +08:00
a6a0d4435c Sync: add test results analyzer notes 2026-04-25 20:00:10 +08:00
ec3be78d36 Sync: refine ui system documentation 2026-04-25 19:40:47 +08:00
8c909c9c08 Sync: add design and process improvement notes 2026-04-25 19:38:47 +08:00
2613a74c73 Sync: add integration and agent file notes 2026-04-25 18:43:06 +08:00
7cb331a532 压缩图片 2026-04-25 18:06:25 +08:00
6aa1571161 修改为客源列表_UI设计 2026-04-25 17:35:06 +08:00
bd2a4c5331 Auto-sync: 2026-04-25 16:02 2026-04-25 16:02:46 +08:00
9fccf27053 Sync: add automation governance notes 2026-04-25 13:11:48 +08:00
e812681628 Sync: refine ui design notes 2026-04-25 12:14:47 +08:00
f7cd041319 添加图片 2026-04-25 12:06:55 +08:00
158c43e1b1 Auto-sync: 2026-04-25 12:02 2026-04-25 12:02:58 +08:00
c83d6fbae6 文档更新 2026-04-25 11:53:25 +08:00
31fc369fd5 Sync: add knowledge graph and design notes 2026-04-25 11:51:49 +08:00
55d3745bb0 ingest: Blockchain Security Auditor + 4 entities + 2 concepts
- Source: blockchain-security-auditor.md (The Agency Specialized, smart contract security audit agent)
- Entities: The-DAO-2016, Euler-Finance, Nomad-Bridge, Curve-Finance
- Concepts: Reentrancy, Oracle-Manipulation
- Updated: index.md (消除了source missing标记), overview.md, log.md
2026-04-25 10:52:41 +08:00
ac7fdfc316 Sync: expand ui system components notes 2026-04-25 09:55:51 +08:00
36651b38a9 图片路径变化 2026-04-25 09:10:07 +08:00
466273a164 Sync: add semantic index and lsp notes 2026-04-25 09:09:38 +08:00
480d64ae81 调整图片路径 2026-04-25 08:53:20 +08:00
6006601b6b Sync: add model evaluation and training notes 2026-04-25 08:52:32 +08:00
77fae85f60 调整文档路径 2026-04-25 08:41:26 +08:00
df840cc5b8 Sync: add healthcare compliance notes 2026-04-25 08:20:22 +08:00
7d0e10b299 保持提示词模板 2026-04-25 08:03:47 +08:00
3b6697df35 Sync: add workflow registry and review notes 2026-04-25 08:02:41 +08:00
aa980f8da2 Sync: add identity and trust notes 2026-04-25 07:45:07 +08:00
fdb657965e 提示词模板优化 2026-04-25 07:44:21 +08:00
84d27f4210 登录管理添加流程图 2026-04-25 06:27:00 +08:00
20f686ea5f Sync: add project management and xr notes 2026-04-25 06:25:02 +08:00
a26d62bb6d Auto-sync: 2026-04-25 04:02 2026-04-25 04:02:51 +08:00
ef8474f0d2 feat: ingest sales-outbound-strategist agent
- Source: Agent/agency-agents/sales/sales-outbound-strategist.md
- Add wiki/sources/sales-outbound-strategist.md (source page)
- Add 4 new concept pages: Signal-Based-Selling-Framework,
  ICP-Ideal-Customer-Profile, Multi-Channel-Sequence-Architecture,
  Account-Tiering-Model
- Update overview.md: add Sales Outbound Methodology section
- Update index.md: add source entry + concept entries
- Update existing concepts: Challenger-Sales-Model, Land-and-Expand
- Append log.md entry
2026-04-25 02:20:58 +08:00
432174c5e3 Auto-sync: 2026-04-25 00:02 2026-04-25 00:02:50 +08:00
d54fdb2d26 Sync: add prd and ui system notes 2026-04-24 21:43:10 +08:00
31d316b096 Sync: add design ux architecture notes 2026-04-24 21:19:59 +08:00
4b6b2f970c Sync: add agent design notes 2026-04-24 21:06:05 +08:00
e677a87510 添加图片 2026-04-24 20:42:33 +08:00
33afef323c 更新 ER diagram 2026-04-24 20:39:45 +08:00
7903d703b9 Sync: add ses and networking notes 2026-04-24 20:38:20 +08:00
e4f6f463cb Sync: add infrastructure as code notes 2026-04-24 19:58:02 +08:00
cc23df1883 Sync: add cloud learning and model updates 2026-04-24 17:48:36 +08:00
3148216d38 Sync: add aws source identity notes 2026-04-24 17:14:00 +08:00
207d6e8b42 权限管理设计方案 2026-04-24 16:52:36 +08:00
0d6f30a55a 图片更新修改 2026-04-24 16:28:04 +08:00
6bd1759da8 Sync: add rightsizing notes 2026-04-24 16:23:54 +08:00
cac9d11fef Sync: add ec2 optimization notes 2026-04-24 16:13:36 +08:00
2e0b9940ed Auto-sync: 2026-04-24 16:03 2026-04-24 16:03:17 +08:00
81d97ce6c1 Sync: update data model docs 2026-04-24 15:16:42 +08:00
f7e0d2b400 fonrey-data-model.xml 2026-04-24 15:07:15 +08:00
75b9e25e68 Sync: expand data model and gitops notes 2026-04-24 14:49:34 +08:00
7550b4ee18 Sync: add gitops and ci-cd notes 2026-04-24 14:12:20 +08:00
4c2ec85278 添加图片 2026-04-24 14:00:12 +08:00
160a15c1ad 修改图片 2026-04-24 13:56:55 +08:00
1ad4e6dcf5 更新权限图片 2026-04-24 13:47:38 +08:00
3b55f3af4d Sync: add container security notes 2026-04-24 13:16:42 +08:00
761fa71f69 Auto-sync: 2026-04-24 12:02 2026-04-24 12:02:48 +08:00
2db051a399 Sync: update organization management notes 2026-04-24 11:59:26 +08:00
5cf21b65ee 新增图片 2026-04-24 11:46:35 +08:00
989ec86c77 Sync: add kubernetes observability notes 2026-04-24 11:35:21 +08:00
ca96e409be 更新图片 2026-04-24 11:34:06 +08:00
171d4b5d3e 更新权限文档 2026-04-24 10:56:00 +08:00
688a996082 图片添加 2026-04-24 09:56:21 +08:00
756b30e188 Sync: update nexus knowledgebase content 2026-04-24 09:41:37 +08:00
647d446780 新增图片 2026-04-24 09:36:15 +08:00
7cecf10c79 Auto-sync: 2026-04-24 08:02 2026-04-24 08:02:47 +08:00
cc8ebc60e3 DATA_MODEL 第一版 2026-04-24 05:36:42 +08:00
a96baa8fb7 Auto-sync: 2026-04-24 04:02 2026-04-24 04:02:45 +08:00
4e9ee6f51e Auto-sync: 2026-04-24 00:02 2026-04-24 00:03:01 +08:00
bea2c71242 新增prompt 2026-04-23 21:43:30 +08:00
2d82830d47 文档更新 2026-04-23 21:27:31 +08:00
b598057f03 文档核心 2026-04-23 19:25:35 +08:00
9de4d0a9b4 Workspace sync: auto commit 2026-04-23 17:13:11 2026-04-23 17:13:11 +08:00
e907ba8c5f Auto-sync: 2026-04-23 16:02 2026-04-23 16:02:56 +08:00
782df914d9 Workspace sync: auto commit 2026-04-23 14:15:18 2026-04-23 14:15:18 +08:00
a295b739a0 添加图片 2026-04-23 14:04:04 +08:00
feea929082 Workspace sync: auto commit 2026-04-23 13:28:25 2026-04-23 13:28:25 +08:00
fcfe9c7ae5 Workspace sync: auto commit 2026-04-23 13:22:56 2026-04-23 13:22:56 +08:00
980a3e89bf 修改照片 2026-04-23 13:14:18 +08:00
e235a30768 添加楼盘管理照片 2026-04-23 13:09:53 +08:00
28dbb1a23a Workspace sync: auto commit 2026-04-23 12:52:58 2026-04-23 12:52:58 +08:00
e656c04794 添加图片 2026-04-23 12:40:20 +08:00
c59cc07327 Workspace sync: auto commit 2026-04-23 12:02:11 2026-04-23 12:02:11 +08:00
6a8362bb5a 更新图片 2026-04-23 10:44:23 +08:00
059fffb9c2 新增图片 2026-04-23 10:29:41 +08:00
4e43ae0ed3 Workspace sync: auto commit 2026-04-23 10:02:03 2026-04-23 10:02:03 +08:00
0179a6532c 添加图片 2026-04-23 09:45:29 +08:00
42961b7e63 Workspace sync: auto commit 2026-04-23 09:33:08 2026-04-23 09:33:08 +08:00
4652411bf2 添加图片 2026-04-23 09:22:15 +08:00
cb6ec38943 ingest: AI/系统提示词构建原则.md
- 新增 source page: wiki/sources/系统提示词构建原则.md
- index.md: 在 Sources 首部添加条目
- overview.md: Vibe Coding 中文指南段落补充该来源链接
- entities/tukuai.md: sources 字段补充该来源
- concepts/Vibe-Coding.md: sources 字段补充该来源
- log.md: 追加 ingest 日志

来源: vibe-coding-cn (tukuai)
2026-04-23 08:48:18 +08:00
b15569f319 Workspace sync: auto commit 2026-04-23 08:25:18 2026-04-23 08:25:18 +08:00
73c54c1c41 添加图片 2026-04-23 08:12:09 +08:00
e4463f12e1 Auto-sync: 2026-04-23 08:03 2026-04-23 08:03:15 +08:00
a66a882a41 Workspace sync: auto commit 2026-04-23 07:31:53 2026-04-23 07:31:53 +08:00
ec58afd9f0 Workspace sync: auto commit 2026-04-23 07:14:49 2026-04-23 07:14:49 +08:00
7e86320c6d Add new notes and fetched article(s) (intent-ux-jakobnielsenphd) 2026-04-23 06:28:24 +08:00
7742098715 Merge branch 'main' of ssh://192.168.3.17:2222/ishenwei/nexus 2026-04-23 06:21:33 +08:00
2971706866 新增图片 2026-04-23 06:21:27 +08:00
5c5732418b Auto-sync: 2026-04-23 06:14 2026-04-23 06:14:22 +08:00
5a63b6dc72 新增图片 2026-04-23 06:05:38 +08:00
b91831a5fd Merge branch 'main' of ssh://192.168.3.17:2222/ishenwei/nexus 2026-04-23 05:56:00 +08:00
c8599198a0 手动更新 2026-04-23 05:51:04 +08:00
e462f96d61 更改图片说明 2026-04-23 05:48:54 +08:00
b0cdd19bfc 提供新截图 2026-04-23 05:43:12 +08:00
6f44ff76a2 Auto-sync: 2026-04-23 04:02 2026-04-23 04:02:48 +08:00
d1e7e4344b Auto-sync iCloud: 2026-04-23 00:03 2026-04-23 00:03:01 +08:00
e823c78a9b Auto-sync: 2026-04-23 00:02 2026-04-23 00:02:55 +08:00
377d32cd39 ingest: aionui-cowork-desktop - AionUi desktop cowork hub for OpenClaw
- Source page: sources/aionui-cowork-desktop.md
- Entity: AionUi (Multi-Agent Hub with Cowork workspace + Remote rescue)
- Concepts: CoworkWorkspace, RemoteRescuePattern, Multi-AgentHub, MCPOnceAllAgents
- Updated: index.md, overview.md, log.md
2026-04-22 23:35:32 +08:00
fd3f24ba27 新增截图 2026-04-22 21:50:31 +08:00
28830a5393 Merge branch 'main' of ssh://192.168.3.17:2222/ishenwei/nexus 2026-04-22 21:14:03 +08:00
201e165f36 拆分图片 2026-04-22 21:13:56 +08:00
087e05cf73 Auto-sync: 2026-04-22 20:55 2026-04-22 20:55:52 +08:00
e8ad058cdd Merge branch 'main' of ssh://192.168.3.17:2222/ishenwei/nexus 2026-04-22 20:19:29 +08:00
fa0a6fa92c 新增fonrey项目前期研究 2026-04-22 20:19:21 +08:00
f2c14bcce1 Auto-sync: 2026-04-22 20:02 2026-04-22 20:02:57 +08:00
3d9d5c8ca7 删除 wiki/sources/dataview-让我从"笔记黑洞"里逃出来的-obsidian-神器-1.md 2026-04-22 11:27:51 +00:00
772cbf2051 Auto-sync: 2026-04-22 19:20 2026-04-22 19:20:32 +08:00
72f3673978 Auto-sync: 2026-04-22 16:03 2026-04-22 16:03:25 +08:00
b1e6af2458 Auto-sync: 2026-04-22 12:02 2026-04-22 12:02:55 +08:00
143d1fd105 Auto-sync: 2026-04-22 08:02 2026-04-22 08:02:59 +08:00
de096f2f88 Auto-sync: 2026-04-22 04:02 2026-04-22 04:03:04 +08:00
24218550d2 Auto-sync: 2026-04-21 20:03 2026-04-21 20:03:06 +08:00
c4a04cbcee change folder 2026-04-21 19:24:48 +08:00
0fe7ba237f Auto-sync: 2026-04-21 17:12 2026-04-21 17:12:45 +08:00
914c8f6925 Auto-sync: 2026-04-21 16:03 2026-04-21 16:03:27 +08:00
b3b6be6114 ingest: 如何传输 Docker images 并且在另一个 Docker 安装
- Source: raw/Home Office/如何传输Docker images 并且在另一个Docker安装.md
- Update Docker concepts (Docker-Save, Docker-Load, Docker-Image) with new source
- Update Synology entity with new source
- Create Xiaoya entity for xiaoyaliu/alist Docker image
- Update wiki/index.md and wiki/log.md
2026-04-21 14:05:39 +08:00
ca7a910543 Auto-sync: 2026-04-21 12:03 2026-04-21 12:03:17 +08:00
8df9990f15 Auto-sync: 2026-04-21 08:02 2026-04-21 08:02:52 +08:00
3347 changed files with 237586 additions and 225636 deletions

BIN
.DS_Store vendored

Binary file not shown.

5
.gitignore vendored
View File

@@ -1 +1,4 @@
.obsidian/
.obsidian/
.DS_Store
**/.DS_Store

6
.opencode/opencode.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"oh-my-openagent"
]
}

View File

@@ -1,96 +1,96 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
kanban-plugin: board
---
## Backlog
- [ ] 🔼 Email to Nina about Achmea Pen Testing on EU3, WAF result reported
- [ ] 🔼 Prepare JATO UX update notification to impacted customers, send to PM for review first
- [ ] 🔺 Initial an email about WAF rules for OP
- [ ] ⏫ send email to Alex and Philipp about convince TechM to migrate their FinOps to OP
- [ ] ⏫ Onspring new evidence for TLS certificate
- [ ] ⏫ Talk to Ellar about ITOM work utilization data, increase the project percentage for some senior person
- [ ] 🔺 Check with Remi and Brindusa to add Time spent field in PCS for both Support and Service Request
- [ ] 🔺 Change people % in Ellar shared spreadsheet
- [ ] ⏫ Reply to Ken to provide more evidence and also check Onspring system the pending tasks
- [ ] ⏫ Reply email about new EU farm kick off plan after comments from Mark Peter
- [ ] Update, please attached the updated evidence to close Onspring requests Master33541 and Master33560 based on what we gave last September 2024.
- [ ] 🔼 Check EU28 Evonik customer license
- [ ] ⏫ Check OpsB/NOM customer license expiration date in CT
- [ ] 🔼 Raise ticket to request Power BI Pro license:
 Hello Wei, I have moved the ITOM Cloud Service Workspace to Fabric. You access should be restored.  View access is free. Content creators can request a Power BI Pro license through the software request workflow [https://go.opentext.com/softwarerequest](https://go.opentext.com/softwarerequest%20for%20$100) $100 USD annually.
-Garrett
- [ ] 🔼 Ask team to consolidate all important runbook and check who is the new owership of these runbooks
- [ ] ⏫ ADAM Q4 Project List
- [ ] ⏫ create unique DB user instead of smax-admin which can be cross used by OP/OpsB/NOM as tenant admin role
## WIP
- [ ] 🔽 Prepare a wiki page to describe 'Troubleshooting as a service'
- [ ] ⏫ Track incident: IM3660374
- [ ] 🔺 Offline pods incident follow up
- [ ] 🔽 Review the Incident Management Meeting (SM9) record to understand detail process on top of it.
- [ ] 🔺 Prepare SD draft for Premium DR service for SocGen
- [ ] ⏫ Give a mapping table about time spent on each SaaS offerings so that we can give a consolidated team effort on customer tickets
## Done
- [ ] 🔺 Refine the Premium DR service slides
- [ ] ⏫ Initial discussion about US24 unplanned maintenance which might have 2 hours downtime.
- [ ] 🔼 Give Wenjun a go command once confirmed Indra is ok with current proposals
- [ ] ⏫ Prepare the answers to Sales team about how we're going to perform the yearly DR testing
- [ ] ⏫ Provide Feb PCS, X4X data to Sajith
- [ ] 🔺 Update 25.1.2 customer notification with official doc link
- [ ] 🔼 Review ITOM AWS Cost Breakdown detail and reply to Samar and Melissa
- [ ] ⏫ Check with Wenjun about BI data stop sent since Feb 24
- [ ] 🔼 Update slides for SMAX/AC VDC readiness meeting
- [ ] 🔼 Schedule a meeting to discuss OT IT use case about direct access Vertica DB to fetch FinOps data
- [ ] 🔽 Prepare tomorrow's ESM Cloud Service weekly meeting
- [ ] ⏫ Prepare upgrade hyper care plan and detail explain what happened for US steel case
- [ ] 🔼 Check with Danny about how to submit security review request for OT IT vertica DB direct access case
- [ ] 🔼 Reply Ken's questions in team's chat
- [ ] ⏫ Send ESM RI plan to OT FinOps team
- [ ] 🔺 Send email notification to NOM customers for 3/9, 3/16 maintenance window about EKS upgrade
## Tracking
- [ ] ⏫ Review patch /upgrade notification content with Dean - Boglarka to drive this
- [ ] 🔼 Assign task about create Jenkins job to check AWS Supperession list
- [ ] ⏫ Ask wenjun to follow up Dean's request about include ITOM Aviator in SMAX premium trial tenant provision
## Archive
- [ ] ⏫ Update cost estimation about SG
- [ ] ⏫ Send ITOM ESM Monthly Report - Feb 2025
- [ ] ⏫ Schedule a meeting with team about Indra switch to OP
- [ ] 🔼 Update OT SM9 Assignment Group for Ops-ADM_ESM and Ops-ADM_OpsB to add new team members
- [ ] 🔼 Refine the on call list in Everbridge
- [ ] 🔺 Prepare ESM 25.1.2 Patch Notifications
- [ ] ⏫ Reply Florin's proposal
***
## Archive
- [ ] 🔼 Reply to Lihi about non-commercial cloud related efforts
%% kanban:settings
```
{"kanban-plugin":"board","list-collapse":[false,false,false,false,false],"full-list-lane-width":true}
```
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
kanban-plugin: board
---
## Backlog
- [ ] 🔼 Email to Nina about Achmea Pen Testing on EU3, WAF result reported
- [ ] 🔼 Prepare JATO UX update notification to impacted customers, send to PM for review first
- [ ] 🔺 Initial an email about WAF rules for OP
- [ ] ⏫ send email to Alex and Philipp about convince TechM to migrate their FinOps to OP
- [ ] ⏫ Onspring new evidence for TLS certificate
- [ ] ⏫ Talk to Ellar about ITOM work utilization data, increase the project percentage for some senior person
- [ ] 🔺 Check with Remi and Brindusa to add Time spent field in PCS for both Support and Service Request
- [ ] 🔺 Change people % in Ellar shared spreadsheet
- [ ] ⏫ Reply to Ken to provide more evidence and also check Onspring system the pending tasks
- [ ] ⏫ Reply email about new EU farm kick off plan after comments from Mark Peter
- [ ] Update, please attached the updated evidence to close Onspring requests Master33541 and Master33560 based on what we gave last September 2024.
- [ ] 🔼 Check EU28 Evonik customer license
- [ ] ⏫ Check OpsB/NOM customer license expiration date in CT
- [ ] 🔼 Raise ticket to request Power BI Pro license:
 Hello Wei, I have moved the ITOM Cloud Service Workspace to Fabric. You access should be restored.  View access is free. Content creators can request a Power BI Pro license through the software request workflow [https://go.opentext.com/softwarerequest](https://go.opentext.com/softwarerequest%20for%20$100) $100 USD annually.
-Garrett
- [ ] 🔼 Ask team to consolidate all important runbook and check who is the new owership of these runbooks
- [ ] ⏫ ADAM Q4 Project List
- [ ] ⏫ create unique DB user instead of smax-admin which can be cross used by OP/OpsB/NOM as tenant admin role
## WIP
- [ ] 🔽 Prepare a wiki page to describe 'Troubleshooting as a service'
- [ ] ⏫ Track incident: IM3660374
- [ ] 🔺 Offline pods incident follow up
- [ ] 🔽 Review the Incident Management Meeting (SM9) record to understand detail process on top of it.
- [ ] 🔺 Prepare SD draft for Premium DR service for SocGen
- [ ] ⏫ Give a mapping table about time spent on each SaaS offerings so that we can give a consolidated team effort on customer tickets
## Done
- [ ] 🔺 Refine the Premium DR service slides
- [ ] ⏫ Initial discussion about US24 unplanned maintenance which might have 2 hours downtime.
- [ ] 🔼 Give Wenjun a go command once confirmed Indra is ok with current proposals
- [ ] ⏫ Prepare the answers to Sales team about how we're going to perform the yearly DR testing
- [ ] ⏫ Provide Feb PCS, X4X data to Sajith
- [ ] 🔺 Update 25.1.2 customer notification with official doc link
- [ ] 🔼 Review ITOM AWS Cost Breakdown detail and reply to Samar and Melissa
- [ ] ⏫ Check with Wenjun about BI data stop sent since Feb 24
- [ ] 🔼 Update slides for SMAX/AC VDC readiness meeting
- [ ] 🔼 Schedule a meeting to discuss OT IT use case about direct access Vertica DB to fetch FinOps data
- [ ] 🔽 Prepare tomorrow's ESM Cloud Service weekly meeting
- [ ] ⏫ Prepare upgrade hyper care plan and detail explain what happened for US steel case
- [ ] 🔼 Check with Danny about how to submit security review request for OT IT vertica DB direct access case
- [ ] 🔼 Reply Ken's questions in team's chat
- [ ] ⏫ Send ESM RI plan to OT FinOps team
- [ ] 🔺 Send email notification to NOM customers for 3/9, 3/16 maintenance window about EKS upgrade
## Tracking
- [ ] ⏫ Review patch /upgrade notification content with Dean - Boglarka to drive this
- [ ] 🔼 Assign task about create Jenkins job to check AWS Supperession list
- [ ] ⏫ Ask wenjun to follow up Dean's request about include ITOM Aviator in SMAX premium trial tenant provision
## Archive
- [ ] ⏫ Update cost estimation about SG
- [ ] ⏫ Send ITOM ESM Monthly Report - Feb 2025
- [ ] ⏫ Schedule a meeting with team about Indra switch to OP
- [ ] 🔼 Update OT SM9 Assignment Group for Ops-ADM_ESM and Ops-ADM_OpsB to add new team members
- [ ] 🔼 Refine the on call list in Everbridge
- [ ] 🔺 Prepare ESM 25.1.2 Patch Notifications
- [ ] ⏫ Reply Florin's proposal
***
## Archive
- [ ] 🔼 Reply to Lihi about non-commercial cloud related efforts
%% kanban:settings
```
{"kanban-plugin":"board","list-collapse":[false,false,false,false,false],"full-list-lane-width":true}
```
%%

View File

@@ -1,74 +1,74 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
kanban-plugin: board
---
## TikTok Shop Tasks
- [ ] ⏫ 等营业执照下来注册TikTok Shop
- [ ] ⏫ 学习Ushop使用方法了解整个订单流程
- [ ] 🔼 了解一下AMZ123网站
## Backlog
- [ ] ⏫ 学习并掌握Scrapy 爬虫工具的使用方法并结合n8n实现自动化
- [ ] ⏫ 尝试在本地搭建text to speech 的模型 并且通过API被n8n调用
- [ ] ⏫ 用pgAdmin连接NAS上postgres数据库
- [ ] ⏫ 尝试在本地使用n8n来调用comfyUI实现图生图自动化
- [ ] 🔼 Learn Google Trends Tutorials
- [ ] 🔼 学习如何使用Google趋势来查看目标国家的热门产品销售数据
- [ ] 🔼 升级Ubuntu1 Portainer 版本
- [ ] 🔼 有空时可以搞一下 爬虫爬 OdayDown.com的数据
- [ ] 🔽 利用ZBook Laptop搭建第二台Ubuntu Server
- [ ] 🔽 读原子习惯,掌握好习惯 中文版先读, 再读英文版
- [ ] 🔽 注册并试用kie.ai
- [ ] N8n调用第三方ApI 进行图片编辑
- [ ] 了解一下SerpAPI
## WIP
- [ ] ⏫ 尝试使用硅基流的 API来实现文生图并被n8n调用
## Done
- [ ] ⏫ 用n8n创建一个workflow可以把internet的图片转存到zipline,并返回图片公共链接
- [ ] 🔼 了解一下Homarr的具体用法
- [ ] 🔽 逐步淘汰Cpolar的使用并删除相关软件
## Tracking
- [ ] ⏫ 利用Qwan3-code来生成n8n代码
## Archive
- [ ] ⏫ 配置Obsidian使用ishenwei.online 域名的webdav
- [ ] ⏬ 在购买的RackNerd的VPS上安装n8n (需要额外考虑)
- [ ] ⏫ 在NAS上搭建一个图床应用
- [ ] ⏫ 在NAS上部署https://github.com/tt-rss/tt-rss
## Idea
- [ ] 🔼 利用Postgres里的RSS article数据来实现 n8n调用并通过AI来分析最新得到的RSS article给一个简报并通过邮件发送
%% kanban:settings
```
{"kanban-plugin":"board","list-collapse":[false,false,false,false,false,false,false]}
```
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
kanban-plugin: board
---
## TikTok Shop Tasks
- [ ] ⏫ 等营业执照下来注册TikTok Shop
- [ ] ⏫ 学习Ushop使用方法了解整个订单流程
- [ ] 🔼 了解一下AMZ123网站
## Backlog
- [ ] ⏫ 学习并掌握Scrapy 爬虫工具的使用方法并结合n8n实现自动化
- [ ] ⏫ 尝试在本地搭建text to speech 的模型 并且通过API被n8n调用
- [ ] ⏫ 用pgAdmin连接NAS上postgres数据库
- [ ] ⏫ 尝试在本地使用n8n来调用comfyUI实现图生图自动化
- [ ] 🔼 Learn Google Trends Tutorials
- [ ] 🔼 学习如何使用Google趋势来查看目标国家的热门产品销售数据
- [ ] 🔼 升级Ubuntu1 Portainer 版本
- [ ] 🔼 有空时可以搞一下 爬虫爬 OdayDown.com的数据
- [ ] 🔽 利用ZBook Laptop搭建第二台Ubuntu Server
- [ ] 🔽 读原子习惯,掌握好习惯 中文版先读, 再读英文版
- [ ] 🔽 注册并试用kie.ai
- [ ] N8n调用第三方ApI 进行图片编辑
- [ ] 了解一下SerpAPI
## WIP
- [ ] ⏫ 尝试使用硅基流的 API来实现文生图并被n8n调用
## Done
- [ ] ⏫ 用n8n创建一个workflow可以把internet的图片转存到zipline,并返回图片公共链接
- [ ] 🔼 了解一下Homarr的具体用法
- [ ] 🔽 逐步淘汰Cpolar的使用并删除相关软件
## Tracking
- [ ] ⏫ 利用Qwan3-code来生成n8n代码
## Archive
- [ ] ⏫ 配置Obsidian使用ishenwei.online 域名的webdav
- [ ] ⏬ 在购买的RackNerd的VPS上安装n8n (需要额外考虑)
- [ ] ⏫ 在NAS上搭建一个图床应用
- [ ] ⏫ 在NAS上部署https://github.com/tt-rss/tt-rss
## Idea
- [ ] 🔼 利用Postgres里的RSS article数据来实现 n8n调用并通过AI来分析最新得到的RSS article给一个简报并通过邮件发送
%% kanban:settings
```
{"kanban-plugin":"board","list-collapse":[false,false,false,false,false,false,false]}
```
%%

View File

@@ -1,25 +1,25 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## 1. Review Upgrade Procedures Document with R&D team
## 2. Follow the Upgrade Procedures to perform Dev Farm upgrade validation
## 3. Send Notification to ESM Cloud Farm Customer about upcoming maintenance window
## 4. Maintenance Window Procedures
### 1. Set downtime of APM monitoring
### 2. Perform the upgrade change
### 3. Send notification to customer once all the change was done
### 4. Update Wiki Page about Version Tracking
### 5. Update System Health Page - Complete the Maintenance Window
### 6. Update PCS Product Version and Environment Version
### 7. Restore the APM monitoring and ensure all checks are good
## 5. Monitoring the farm metrics to ensure everything is working as expected
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## 1. Review Upgrade Procedures Document with R&D team
## 2. Follow the Upgrade Procedures to perform Dev Farm upgrade validation
## 3. Send Notification to ESM Cloud Farm Customer about upcoming maintenance window
## 4. Maintenance Window Procedures
### 1. Set downtime of APM monitoring
### 2. Perform the upgrade change
### 3. Send notification to customer once all the change was done
### 4. Update Wiki Page about Version Tracking
### 5. Update System Health Page - Complete the Maintenance Window
### 6. Update PCS Product Version and Environment Version
### 7. Restore the APM monitoring and ensure all checks are good
## 5. Monitoring the farm metrics to ensure everything is working as expected

View File

@@ -1,26 +1,26 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Control Tower Link: https://backoffice.saas.microfocus.com/home/bl/desktop.html?TENANTID=1#/customers
- Request Access to Control Tower
-
- Customer Order Filter
- ESM Product Filter:
![Image](http://zipline.ishenwei.online/u/cu2uo8.png)
- APM/OpsB/NOM Product Filter
![Image](http://zipline.ishenwei.online/u/QPUhmO.png)
- SaaS Order In Control Tower
- CS Ops Fulfill the order and generate license
- SaaS Ops team download/allocate license and close the deal
- Control Tower order status change to "Provisioned", close the deal
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Control Tower Link: https://backoffice.saas.microfocus.com/home/bl/desktop.html?TENANTID=1#/customers
- Request Access to Control Tower
-
- Customer Order Filter
- ESM Product Filter:
![Image](http://zipline.ishenwei.online/u/cu2uo8.png)
- APM/OpsB/NOM Product Filter
![Image](http://zipline.ishenwei.online/u/QPUhmO.png)
- SaaS Order In Control Tower
- CS Ops Fulfill the order and generate license
- SaaS Ops team download/allocate license and close the deal
- Control Tower order status change to "Provisioned", close the deal

View File

@@ -1,25 +1,25 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Wiki Page:
https://confluence.opentext.com/display/ICSD/Request+Unplanned+Change+in+Cloud+Production+Environment+Process
R&D SA Approver
- Gong Yi (SMAX)
- Danny Tian (SMAX)
- Spinu Corneliu (SMAX)
- Moldovan Vlad
- Diana Pop (CMS)
- Bianca Voina (CMS)
CSD Approver
- Shen Wei
- Ting Ye
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Wiki Page:
https://confluence.opentext.com/display/ICSD/Request+Unplanned+Change+in+Cloud+Production+Environment+Process
R&D SA Approver
- Gong Yi (SMAX)
- Danny Tian (SMAX)
- Spinu Corneliu (SMAX)
- Moldovan Vlad
- Diana Pop (CMS)
- Bianca Voina (CMS)
CSD Approver
- Shen Wei
- Ting Ye

View File

@@ -1,42 +1,42 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Review R&D Major Release Plan & Patch Plan
- ESM RTE: Mihaela Claudia Chis <mchis@opentext.com>
- PI Planning Readout Slides
- ESM Patch Release Owner: Tanuj Raja Vunnava <tvunnava@opentext.com>
- Patch Release Kick Off email
- Other requirement about upgrade strategy
- Demo/PoC Request
- Customer commitment etc.
## Prepare Cloud Upgrade Plan
### Design Tool
### Plan Strategy
- US2 Dev Farm Upgrade Validation - 1~2 days prior MR release date
- Shared Service - ITOM Aviator US30 staging, EU30 production, EU32 production need to be upgraded first before other consume farm upgrade
- EU3/US7 Trial/PoC Farm Upgrade - 1~ 2 weeks after GA release date, Upgrade on Monday (working day)
- US2/US24 Opentext Internal Customer Production Farm - 1st Wave Production Farm Upgrade (Maintenance Window)
- US26 - SalesForce customer need alternative upgrade date this can be negotiated with CSM and customer
- US26/US6/AP10/CA16 External Customer Production Farm - 2nd Wave Production Farm Upgrade (Maintenance Window)
- EU8/EU18/EU28/BR14/JP12 External Customer Production Farm - 3nd Wave Production Farm Upgrade (Maintenance Window)
- If ESM farm enable Operation Platform, need to upgrade Operation Platform first before upgrade ESM farm
- Considering the 1st patch release, we can consider to adopt patch upgrade direct in the upgrade maintenance window (Need to clarify the dependencies)
- Try to avoid upgrade window before key teams public holiday. Usually some critical issues will be reported on Monday/Tuesday after version upgrade. Need people standby to support troubleshooting
### Publish and Notify the ESM Cloud Upgrade Plan
- ESM Cloud Upgrade Plan Wiki Page: https://confluence.opentext.com/display/ICSD/ESM+Cloud+Ops+Change+Calendar
- ESM Cloud Ops Change Calendar: https://opentextcorporation.sharepoint.com/sites/MFI-SMAXSaaSDevOps/Lists/ESM%20Cloud%20Calendar/calendar.aspx
- Internal Communication About ESM Cloud Upgrade Plan (Sample Email)
### Continuous to adjust the plan according to the changes
- Cancel/Postpone the upgrade according to critical defects
### Rollback the upgrade
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Review R&D Major Release Plan & Patch Plan
- ESM RTE: Mihaela Claudia Chis <mchis@opentext.com>
- PI Planning Readout Slides
- ESM Patch Release Owner: Tanuj Raja Vunnava <tvunnava@opentext.com>
- Patch Release Kick Off email
- Other requirement about upgrade strategy
- Demo/PoC Request
- Customer commitment etc.
## Prepare Cloud Upgrade Plan
### Design Tool
### Plan Strategy
- US2 Dev Farm Upgrade Validation - 1~2 days prior MR release date
- Shared Service - ITOM Aviator US30 staging, EU30 production, EU32 production need to be upgraded first before other consume farm upgrade
- EU3/US7 Trial/PoC Farm Upgrade - 1~ 2 weeks after GA release date, Upgrade on Monday (working day)
- US2/US24 Opentext Internal Customer Production Farm - 1st Wave Production Farm Upgrade (Maintenance Window)
- US26 - SalesForce customer need alternative upgrade date this can be negotiated with CSM and customer
- US26/US6/AP10/CA16 External Customer Production Farm - 2nd Wave Production Farm Upgrade (Maintenance Window)
- EU8/EU18/EU28/BR14/JP12 External Customer Production Farm - 3nd Wave Production Farm Upgrade (Maintenance Window)
- If ESM farm enable Operation Platform, need to upgrade Operation Platform first before upgrade ESM farm
- Considering the 1st patch release, we can consider to adopt patch upgrade direct in the upgrade maintenance window (Need to clarify the dependencies)
- Try to avoid upgrade window before key teams public holiday. Usually some critical issues will be reported on Monday/Tuesday after version upgrade. Need people standby to support troubleshooting
### Publish and Notify the ESM Cloud Upgrade Plan
- ESM Cloud Upgrade Plan Wiki Page: https://confluence.opentext.com/display/ICSD/ESM+Cloud+Ops+Change+Calendar
- ESM Cloud Ops Change Calendar: https://opentextcorporation.sharepoint.com/sites/MFI-SMAXSaaSDevOps/Lists/ESM%20Cloud%20Calendar/calendar.aspx
- Internal Communication About ESM Cloud Upgrade Plan (Sample Email)
### Continuous to adjust the plan according to the changes
- Cancel/Postpone the upgrade according to critical defects
### Rollback the upgrade

View File

@@ -1,32 +1,32 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## ESM Cloud
- ESM Farm Information: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- ESM Capability Introduction
- SMAX
- UCMDB, Native SACM, SAM
- HCMX/DnD
- OO
- AC
- FinOps Classic
- FinOps OP
- Operation Platform/Optic Data Lake (ODL)
- ITOM Aviator
- ESM Farm Version Tracking: https://confluence.opentext.com/display/ICSD/ITOM+Cloud+Applications+Version+Tracking
- ESM Customer Tenant Capabilities Enablement BI Report: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3a054e35d20b9d533d81?experience=power-bi
## OpsB/NOM Cloud
- OpsB/NOM Cloud Deployments & Version Tracking:https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
## APM Cloud
- APM Farm Information: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## ESM Cloud
- ESM Farm Information: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- ESM Capability Introduction
- SMAX
- UCMDB, Native SACM, SAM
- HCMX/DnD
- OO
- AC
- FinOps Classic
- FinOps OP
- Operation Platform/Optic Data Lake (ODL)
- ITOM Aviator
- ESM Farm Version Tracking: https://confluence.opentext.com/display/ICSD/ITOM+Cloud+Applications+Version+Tracking
- ESM Customer Tenant Capabilities Enablement BI Report: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3a054e35d20b9d533d81?experience=power-bi
## OpsB/NOM Cloud
- OpsB/NOM Cloud Deployments & Version Tracking:https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
## APM Cloud
- APM Farm Information: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information

View File

@@ -1,29 +1,29 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
- Major Incident Definition: https://confluence.opentext.com/display/ICSD/Major+Incident+Definition
- Major Incident Management & Best Practice:
- Identification and Detection
- Initial Assessment
- Incident Logging
- Incident in OT SM9
- Internal Practice: Create incident in PCS
- Communication
- Identify Incident Manager
- Create team chat group and involve all stakeholders
- Keeping update status
- Resolution
---Break---
- Oncall/Response
- Post Incident Review
- Continuous Improvement (CAPA)
- Monitoring & Alerting Enhancements
- Documentation & Knowledge base:
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
- Major Incident Definition: https://confluence.opentext.com/display/ICSD/Major+Incident+Definition
- Major Incident Management & Best Practice:
- Identification and Detection
- Initial Assessment
- Incident Logging
- Incident in OT SM9
- Internal Practice: Create incident in PCS
- Communication
- Identify Incident Manager
- Create team chat group and involve all stakeholders
- Keeping update status
- Resolution
---Break---
- Oncall/Response
- Post Incident Review
- Continuous Improvement (CAPA)
- Monitoring & Alerting Enhancements
- Documentation & Knowledge base:

View File

@@ -1,16 +1,16 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
BI report:
https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1f4989a9-0127-4c6d-9375-f9dd9bda5d84/ReportSection?experience=power-bi
PCS Dahsboard:
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
BI report:
https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1f4989a9-0127-4c6d-9375-f9dd9bda5d84/ReportSection?experience=power-bi
PCS Dahsboard:
https://pcs.saas.microfocus.com/dashboard

View File

@@ -1,32 +1,32 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
PCS: https://pcs.saas.microfocus.com/homepage?AUTH=SAML
ITOM Cloud Ops Assignment Group:
- SD: ESM SaaS Ops
- SD: OpsB SaaS Ops
- SD: NOM SaaS Ops
- SD: DCA SaaS Ops
- ITOM Cloud Service Offerings
- Service Request vs Support Request
- Entitlement/Environment/Tenant/Product
- Service/Support Request triage & workflow
- Request -> Incident -> Change
- Escalations
BI report:
https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1f4989a9-0127-4c6d-9375-f9dd9bda5d84/ReportSection?experience=power-bi
PCS Dahsboard:
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
PCS: https://pcs.saas.microfocus.com/homepage?AUTH=SAML
ITOM Cloud Ops Assignment Group:
- SD: ESM SaaS Ops
- SD: OpsB SaaS Ops
- SD: NOM SaaS Ops
- SD: DCA SaaS Ops
- ITOM Cloud Service Offerings
- Service Request vs Support Request
- Entitlement/Environment/Tenant/Product
- Service/Support Request triage & workflow
- Request -> Incident -> Change
- Escalations
BI report:
https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1f4989a9-0127-4c6d-9375-f9dd9bda5d84/ReportSection?experience=power-bi
PCS Dahsboard:
https://pcs.saas.microfocus.com/dashboard

View File

@@ -1,60 +1,60 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Role and Responsibility
#### Strategic Responsibilities
- Own the reliability and performance of multiple SaaS Application Services. (APM, BPM, OpsB, NOM, ESM, DCA, SBM, ESM FedRAMP)
- Drive cloud modernization initiatives (e.g., containerization, EKS, CI/CD automation).
- Align cloud service delivery with customer SLAs, business growth, and compliance frameworks (e.g., FedRAMP, SBM).
- Interface with Sales, Product, Security, and Compliance to support new customer onboarding and cloud architecture reviews.
#### Operational Responsibilities
- Oversee 24x7 operation and monitoring of cloud platforms (AWS-based).
- Manage escalations, incidents, and root cause analysis.
- Coordinate patching, upgrades, hotfixes, and maintenance windows.
- Own service onboarding/offboarding workflows, including tenant provisioning and decommissioning.
#### People Management
- Performance management and coaching of global team members.
- Run weekly team syncs, monthly reviews, and ad-hoc cross-regional escalations.
## Cloud Applications and Cloud Services KS Sessions
### Session 1
- ITOM Cloud Application AWS Account Owner
- https://confluence.opentext.com/display/ICSD/ITOM+Cloud+AWS+Account+Overview
- AWS Account Admin ownership
- Responsibility of AWS Account Admin
- ITOM Cloud Application List
- ESM/ITOM Aviator/DCA/SBM: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- OpsB/NOM Cloud Application List: https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
- APM Cloud Farm List: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information
- ITOM Cloud FinOps
- BI Reporting: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1a3fceca-6563-4cc6-8218-d1d27f15e2f1/ReportSection?experience=power-bi
- Opentext FinOps Team AWS FinOps Dashboard
### Session 2
- ITOM Cloud Application Version Currency
- Upgrade Plan
- ESM/FebRAMP Ops Change Calendar
- OpsB/NOM Ops Change Calendar
- Upgrade Plan Timeline
### Session 3
### Session 4
### Session 5
### Session 6
### Session 7
### Session 8
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Role and Responsibility
#### Strategic Responsibilities
- Own the reliability and performance of multiple SaaS Application Services. (APM, BPM, OpsB, NOM, ESM, DCA, SBM, ESM FedRAMP)
- Drive cloud modernization initiatives (e.g., containerization, EKS, CI/CD automation).
- Align cloud service delivery with customer SLAs, business growth, and compliance frameworks (e.g., FedRAMP, SBM).
- Interface with Sales, Product, Security, and Compliance to support new customer onboarding and cloud architecture reviews.
#### Operational Responsibilities
- Oversee 24x7 operation and monitoring of cloud platforms (AWS-based).
- Manage escalations, incidents, and root cause analysis.
- Coordinate patching, upgrades, hotfixes, and maintenance windows.
- Own service onboarding/offboarding workflows, including tenant provisioning and decommissioning.
#### People Management
- Performance management and coaching of global team members.
- Run weekly team syncs, monthly reviews, and ad-hoc cross-regional escalations.
## Cloud Applications and Cloud Services KS Sessions
### Session 1
- ITOM Cloud Application AWS Account Owner
- https://confluence.opentext.com/display/ICSD/ITOM+Cloud+AWS+Account+Overview
- AWS Account Admin ownership
- Responsibility of AWS Account Admin
- ITOM Cloud Application List
- ESM/ITOM Aviator/DCA/SBM: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- OpsB/NOM Cloud Application List: https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
- APM Cloud Farm List: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information
- ITOM Cloud FinOps
- BI Reporting: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1a3fceca-6563-4cc6-8218-d1d27f15e2f1/ReportSection?experience=power-bi
- Opentext FinOps Team AWS FinOps Dashboard
### Session 2
- ITOM Cloud Application Version Currency
- Upgrade Plan
- ESM/FebRAMP Ops Change Calendar
- OpsB/NOM Ops Change Calendar
- Upgrade Plan Timeline
### Session 3
### Session 4
### Session 5
### Session 6
### Session 7
### Session 8

View File

@@ -1,75 +1,75 @@
---
title: AWS → GCP
source:
author: shenwei
published:
created:
description:
tags: []
---
# AWS → GCP
To migrate your enterprise-level SaaS application from AWS to Google Cloud, youll need to find equivalent Google Cloud services for the AWS services you currently use, while ensuring your architecture remains compatible. Here's a service-by-service breakdown for smooth development:
### 1. **AWS EKS (Elastic Kubernetes Service) → Google Kubernetes Engine (GKE)**
**Google Kubernetes Engine (GKE)** is Google Clouds equivalent to AWS EKS. Both manage Kubernetes clusters, offering similar features like autoscaling, security, and networking.
- **Migration Notes**: Kubernetes manifests and Helm charts will be reusable with minimal modification, but youll need to handle network and security configurations specific to Google Cloud.
### 2. **AWS RDS (Relational Database Service) → Cloud SQL / Cloud Spanner**
- **Cloud SQL**: Supports MySQL, PostgreSQL, and SQL Server, making it a direct equivalent for most RDS instances.
- **Cloud Spanner**: If you need horizontally scalable, globally distributed databases with strong consistency, consider Cloud Spanner.
- **Migration Notes**: Database migration tools like **Database Migration Service** can help with the data migration, ensuring minimal downtime and compatibility.
### 3. **AWS EFS (Elastic File System) → Filestore**
**Google Cloud Filestore** is a fully managed NFS (Network File System) service similar to AWS EFS.
- **Migration Notes**: Ensure your applications are configured to use the Filestore APIs and access paths properly. Performance tiers and network configuration will need review for alignment with your AWS EFS setup.
### 4. **AWS S3 (Simple Storage Service) → Google Cloud Storage**
**Google Cloud Storage** is a direct equivalent to AWS S3 for object storage, supporting multiple classes of storage with similar durability and availability guarantees.
- **Migration Notes**: Google Clouds storage APIs differ slightly, so you may need to refactor your code to accommodate the differences. However, bucket management and object lifecycle policies are similar.
### 5. **AWS Lambda (Serverless Compute) → Google Cloud Functions / Cloud Run**
- **Google Cloud Functions**: Equivalent to AWS Lambda for small, event-driven serverless tasks.
- **Cloud Run**: If you need to run containerized microservices, **Cloud Run** (based on Knative) is a good choice for scaling container workloads.
- **Migration Notes**: Code migration is generally straightforward, though GCP uses slightly different event triggers and permission models.
### 6. **AWS Parameter Store → Secret Manager / Cloud Key Management Service (KMS)**
- **Secret Manager**: For storing secrets such as passwords, API keys, and tokens.
- **Cloud KMS**: For managing encryption keys and ensuring data security.
- **Migration Notes**: Similar management principles apply, but authentication and access policies will need revision to comply with Google Cloud's identity model.
### 7. **AWS SES (Simple Email Service) → SendGrid / Mailgun (Third-party service)**
Google Cloud doesn't have a native email service comparable to SES, so you can leverage third-party services like **SendGrid** or **Mailgun**, which integrate well with Google Cloud.
- **Migration Notes**: Integration through APIs will require code changes, but the overall functionality will remain similar.
### 8. **AWS CloudWatch → Google Cloud Monitoring (formerly Stackdriver)**
**Google Cloud Monitoring** is the equivalent of AWS CloudWatch, providing observability, logging, and alerting for your infrastructure and applications.
- **Migration Notes**: If you are heavily using AWS CloudWatch Logs, consider migrating to **Google Cloud Logging** to keep a similar flow of data.
### Additional Considerations:
- **IAM and Security**: AWS Identity and Access Management (IAM) maps well to Google Cloud IAM, but you'll need to refactor policies and roles.
- **Networking**: Google Clouds VPC is similar to AWS VPC, but you'll need to translate networking configurations (firewalls, routing, etc.) and possibly update load balancing setups, using **Google Cloud Load Balancer** in place of AWS Elastic Load Balancing (ELB).
### Tools for Migration:
- **Googles Database Migration Service**: To migrate RDS databases to Cloud SQL.
- **Migrate for Compute Engine**: For VM migrations if you use EC2 instances.
- **Velostrata**: For handling live migrations of workloads.
Would you like to dive deeper into any particular service, or need assistance on a specific migration tool or process?
---
title: AWS → GCP
source:
author: shenwei
published:
created:
description:
tags: []
---
# AWS → GCP
To migrate your enterprise-level SaaS application from AWS to Google Cloud, youll need to find equivalent Google Cloud services for the AWS services you currently use, while ensuring your architecture remains compatible. Here's a service-by-service breakdown for smooth development:
### 1. **AWS EKS (Elastic Kubernetes Service) → Google Kubernetes Engine (GKE)**
**Google Kubernetes Engine (GKE)** is Google Clouds equivalent to AWS EKS. Both manage Kubernetes clusters, offering similar features like autoscaling, security, and networking.
- **Migration Notes**: Kubernetes manifests and Helm charts will be reusable with minimal modification, but youll need to handle network and security configurations specific to Google Cloud.
### 2. **AWS RDS (Relational Database Service) → Cloud SQL / Cloud Spanner**
- **Cloud SQL**: Supports MySQL, PostgreSQL, and SQL Server, making it a direct equivalent for most RDS instances.
- **Cloud Spanner**: If you need horizontally scalable, globally distributed databases with strong consistency, consider Cloud Spanner.
- **Migration Notes**: Database migration tools like **Database Migration Service** can help with the data migration, ensuring minimal downtime and compatibility.
### 3. **AWS EFS (Elastic File System) → Filestore**
**Google Cloud Filestore** is a fully managed NFS (Network File System) service similar to AWS EFS.
- **Migration Notes**: Ensure your applications are configured to use the Filestore APIs and access paths properly. Performance tiers and network configuration will need review for alignment with your AWS EFS setup.
### 4. **AWS S3 (Simple Storage Service) → Google Cloud Storage**
**Google Cloud Storage** is a direct equivalent to AWS S3 for object storage, supporting multiple classes of storage with similar durability and availability guarantees.
- **Migration Notes**: Google Clouds storage APIs differ slightly, so you may need to refactor your code to accommodate the differences. However, bucket management and object lifecycle policies are similar.
### 5. **AWS Lambda (Serverless Compute) → Google Cloud Functions / Cloud Run**
- **Google Cloud Functions**: Equivalent to AWS Lambda for small, event-driven serverless tasks.
- **Cloud Run**: If you need to run containerized microservices, **Cloud Run** (based on Knative) is a good choice for scaling container workloads.
- **Migration Notes**: Code migration is generally straightforward, though GCP uses slightly different event triggers and permission models.
### 6. **AWS Parameter Store → Secret Manager / Cloud Key Management Service (KMS)**
- **Secret Manager**: For storing secrets such as passwords, API keys, and tokens.
- **Cloud KMS**: For managing encryption keys and ensuring data security.
- **Migration Notes**: Similar management principles apply, but authentication and access policies will need revision to comply with Google Cloud's identity model.
### 7. **AWS SES (Simple Email Service) → SendGrid / Mailgun (Third-party service)**
Google Cloud doesn't have a native email service comparable to SES, so you can leverage third-party services like **SendGrid** or **Mailgun**, which integrate well with Google Cloud.
- **Migration Notes**: Integration through APIs will require code changes, but the overall functionality will remain similar.
### 8. **AWS CloudWatch → Google Cloud Monitoring (formerly Stackdriver)**
**Google Cloud Monitoring** is the equivalent of AWS CloudWatch, providing observability, logging, and alerting for your infrastructure and applications.
- **Migration Notes**: If you are heavily using AWS CloudWatch Logs, consider migrating to **Google Cloud Logging** to keep a similar flow of data.
### Additional Considerations:
- **IAM and Security**: AWS Identity and Access Management (IAM) maps well to Google Cloud IAM, but you'll need to refactor policies and roles.
- **Networking**: Google Clouds VPC is similar to AWS VPC, but you'll need to translate networking configurations (firewalls, routing, etc.) and possibly update load balancing setups, using **Google Cloud Load Balancer** in place of AWS Elastic Load Balancing (ELB).
### Tools for Migration:
- **Googles Database Migration Service**: To migrate RDS databases to Cloud SQL.
- **Migrate for Compute Engine**: For VM migrations if you use EC2 instances.
- **Velostrata**: For handling live migrations of workloads.
Would you like to dive deeper into any particular service, or need assistance on a specific migration tool or process?

View File

@@ -1,210 +1,210 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
#### Major Incident Management Process
Certainly! Designing a comprehensive Major Incident Management process is crucial for maintaining the reliability and availability of enterprise SaaS applications. Below is a detailed outline of the major incident management process, including procedures for handling service outages:
### Major Incident Management Process
#### 1. **Identification and Detection:**
- **Automated Monitoring:** Utilize robust monitoring tools to detect anomalies, performance issues, and potential outages.
- **User Reports:** Encourage users to report issues promptly via designated channels.
#### 2. **Incident Logging:**
- **Centralized Logging:** Maintain a centralized incident log that captures all relevant details, timestamps, and initial impact assessment.
- **Severity Classification:** Categorize incidents based on severity to prioritize response efforts.
#### 3. **Initial Assessment:**
- **Incident Triage:** Quickly assemble a cross-functional incident response team, including representatives from development, operations, and support.
- **Impact Analysis:** Evaluate the scope and impact of the incident on users, systems, and business operations.
#### 4. **Communication:**
- **Internal Communication:** Establish communication channels for the incident response team, ensuring timely updates and coordination.
- **External Communication:** Prepare predefined messages for customers and stakeholders, providing transparency about the incident.
#### 5. **Resolution:**
- **Runbooks and Playbooks:** Develop detailed runbooks and playbooks for common incident scenarios, outlining step-by-step resolution procedures.
- **Escalation Procedures:** Define clear escalation paths for issues that require higher-level expertise or management attention.
#### 6. **Post-Incident Review (PIR):**
- **Root Cause Analysis (RCA):** Conduct a thorough RCA to identify the underlying cause of the incident.
- **Documentation:** Document the incident resolution process, lessons learned, and preventive measures for future incidents.
#### 7. **Continuous Improvement:**
- **Iterative Updates:** Regularly update incident response procedures based on lessons learned from past incidents.
- **Training and Drills:** Conduct regular training sessions and simulated drills to ensure the incident response team is well-prepared.
#### 8. **Monitoring and Alerting Enhancements:**
- **Continuous Monitoring:** Implement ongoing improvements to monitoring and alerting systems to proactively detect potential issues.
- **Automated Remediation:** Integrate automated remediation tools to address common incidents swiftly.
#### 9. **Documentation and Knowledge Sharing:**
- **Knowledge Base:** Maintain a comprehensive knowledge base with troubleshooting guides, FAQs, and resolutions for known issues.
- **Documentation Accessibility:** Ensure that incident response documentation is easily accessible to all team members.
#### 10. **Review and Audit:**
- **Periodic Audits:** Conduct periodic reviews and audits of the major incident management process to identify areas for improvement.
- **Compliance Checks:** Ensure that the process aligns with industry best practices and regulatory requirements.
This Major Incident Management process should be regularly reviewed and updated to adapt to evolving technologies and organizational needs. Regular training and communication exercises will help maintain a proactive and efficient incident response capability.
#### Whats the definition of RACI
RACI is an acronym that stands for Responsible, Accountable, Consulted, and Informed. It is a project management and organizational tool used to clarify roles and responsibilities within a team or across different stakeholders for specific tasks, processes, or projects. Each letter in RACI represents a different level of involvement and accountability:
1. Responsible (R): This person or role is responsible for completing the task or carrying out the work. They are the individuals who perform the actual work and ensure it gets done.
2. Accountable (A): This person is ultimately accountable for the task's success or failure. They are the decision-makers who oversee the work and ensure it aligns with the project's objectives. There should be only one "A" assigned to each task or activity.
3. Consulted (C): These are individuals or roles that provide input, expertise, or advice on the task. They are consulted for their knowledge or perspective, but they are not responsible for the task's completion.
4. Informed (I): These individuals or roles need to be kept informed about the task's progress or outcome but do not have active participation in its execution.
RACI matrices and charts are commonly used to document and communicate these roles and responsibilities, helping teams and organizations to reduce confusion, improve accountability, and ensure that work is completed efficiently and effectively.
#### Handling Data Return in Enterprise SaaS Business
Handling data returns in an enterprise SaaS business requires a well-defined process to ensure customer satisfaction and compliance with data privacy regulations. Here's a step-by-step guide to help you prepare a process for when a client requests to return their data:
1. **Understand Data Ownership and Privacy Policies:**
- Clearly define data ownership in your terms of service and privacy policy.
- Understand and comply with data protection regulations relevant to your industry and geographic location (e.g., GDPR, CCPA).
2. **Provide Clear Terms in Contracts:**
- Include provisions in your contracts that outline the conditions under which clients can request the return of their data.
- Specify the format and timeline for data return.
3. **Implement Data Export Features:**
- Build data export features into your SaaS platform to allow clients to easily retrieve their data in a standard and commonly used format (e.g., CSV, JSON).
- Ensure that exported data includes all relevant information and maintains data integrity.
4. **Establish a Request Process:**
- Create a formalized process for clients to request the return of their data.
- This process could include a dedicated support channel, a web portal, or a specific form.
5. **Authenticate and Verify Requests:**
- Implement a robust authentication process to ensure that only authorized individuals can request data returns.
- Verify the identity of the requester through multi-factor authentication or other secure means.
6. **Document and Track Requests:**
- Keep a centralized record of all data return requests.
- Track the status of each request, including when it was received, processed, and completed.
7. **Review and Cleanse Data:**
- Before returning data, review it to ensure it doesnt contain any sensitive information from other users.
- Implement a data cleansing process to remove any irrelevant or unnecessary information.
8. **Secure Data Transmission:**
- Use secure channels and encryption protocols to transmit the data back to the client.
- Provide the client with instructions on how to securely receive the data.
9. **Notify Client of Completion:**
- Notify the client when their data return request has been processed and the data is available for retrieval.
- Provide any relevant documentation or instructions.
10. **Follow Up for Feedback:**
```other
- Follow up with the client after the data return to gather feedback on the process and ensure their satisfaction.
- Use feedback to continuously improve the data return process.
```
11. **Train Support and Compliance Teams:**
```other
- Ensure that your support and compliance teams are well-trained on the data return process.
- Keep them updated on any changes to regulations or internal policies.
```
12. **Regularly Review and Update Process:**
```other
- Periodically review and update the data return process to incorporate any changes in regulations, technology, or customer needs.
```
By implementing a well-structured process, you can efficiently handle data return requests, maintain customer trust, and comply with data protection laws.
#### Routine DR Validation Process
Routine disaster recovery (DR) validation reviews are crucial for ensuring the resilience of your enterprise SaaS business. Here's a step-by-step guide to help you prepare a process for routine disaster recovery validation reviews:
1. **Define Objectives and Scope:**
- Clearly define the objectives of the routine disaster recovery validation review.
- Specify the scope, including the systems, applications, and data that will be included in the review.
2. **Establish a Schedule:**
- Set a regular schedule for conducting disaster recovery validation reviews. This could be quarterly, semi-annually, or annually based on the criticality of your systems.
3. **Document the Disaster Recovery Plan (DRP):**
- Ensure that you have a comprehensive and up-to-date disaster recovery plan in place.
- Document the step-by-step procedures for recovering systems and data in the event of a disaster.
4. **Identify Key Stakeholders:**
- Identify the key stakeholders involved in the disaster recovery validation process.
- This may include IT administrators, security personnel, and relevant business unit representatives.
5. **Select Validation Criteria:**
- Define the criteria that will be used to validate the effectiveness of the disaster recovery plan.
- Criteria may include recovery time objectives (RTO), recovery point objectives (RPO), and data integrity.
6. **Simulate Disaster Scenarios:**
- Develop a set of realistic disaster scenarios that could impact your systems and data.
- Simulate these scenarios to test the effectiveness of your disaster recovery plan.
7. **Coordinate with Third-Party Vendors:**
- If your SaaS business relies on third-party vendors or cloud service providers, coordinate with them to ensure that their disaster recovery plans align with yours.
- Validate their processes and capabilities as part of your review.
8. **Perform Tabletop Exercises:**
- Conduct tabletop exercises with key stakeholders to walk through various disaster recovery scenarios.
- Use these exercises to identify weaknesses, gaps, and areas for improvement in the plan.
9. **Automate Testing Where Possible:**
- Implement automation tools to simulate and test disaster recovery procedures.
- Automation can help streamline the testing process and provide more accurate results.
10. **Measure and Analyze Results:**
- Measure the performance against the defined criteria during each validation review.
- Analyze the results to identify trends, patterns, and areas that need improvement.
11. **Document Findings and Recommendations:**
- Document the findings of each disaster recovery validation review.
- Provide clear recommendations for improvements and corrective actions.
12. **Implement Improvements:**
- Act on the recommendations and implement improvements to enhance the effectiveness of your disaster recovery plan.
13. **Review and Update DRP:**
- Regularly review and update the disaster recovery plan based on the lessons learned from validation reviews and changes in your IT infrastructure.
14. **Communicate Results:**
- Communicate the results of the disaster recovery validation reviews to relevant stakeholders.
- Ensure transparency and provide information on the steps being taken to address any identified issues.
15. **Continuous Training and Awareness:**
- Conduct regular training sessions for your IT and operational teams to ensure they are familiar with the disaster recovery procedures.
- Raise awareness among employees about the importance of disaster recovery and their roles in the process.
By following these steps, you can establish a robust process for routine disaster recovery validation reviews, helping to ensure the resilience and continuity of your enterprise SaaS business in the face of potential disasters.
#### Please help explain what's the SOC2 audit?
Please help me explain what's the SOC2 audit?
SOC 2, which stands for Service Organization Control 2, is a framework for managing and securing data that is crucial for the privacy of user information. It is specifically designed for technology and cloud computing organizations that store customer data in the cloud. Developed by the American Institute of CPAs (AICPA), SOC 2 compliance is not a one-time event but an ongoing process that requires companies to follow certain criteria to ensure the security, availability, processing integrity, confidentiality, and privacy of customer data.
Here are key components of a SOC 2 audit:
1. **Trust Service Criteria:** SOC 2 is built around five "Trust Service Criteria" (TSC) security, availability, processing integrity, confidentiality, and privacy. These criteria serve as the foundation for evaluating an organization's systems and processes.
- **Security:** The system is protected against unauthorized access (both physical and logical).
- **Availability:** The system is available for operation and use as committed or agreed.
- **Processing Integrity:** System processing is complete, valid, accurate, timely, and authorized.
- **Confidentiality:** Information designated as confidential is protected as committed or agreed.
- **Privacy:** Personal information is collected, used, retained, disclosed, and disposed of in conformity with the commitments in the entitys privacy notice.
2. **SOC 2 Type I and Type II Audits:**
- **Type I:** Examines the suitability of the design of controls at a specific point in time.
- **Type II:** Examines the operational effectiveness of these controls over a period of time (typically a minimum of six months).
3. **Scope and Boundary:** The organization defines the scope of the audit, including the systems that are in scope and the boundaries of the environment being evaluated.
4. **Risk Management and Compliance:** SOC 2 requires organizations to identify and manage risks related to the security, availability, processing integrity, confidentiality, and privacy of information.
5. **Written Policies and Procedures:** Organizations need to have documented policies and procedures in place to demonstrate how they achieve and maintain compliance with the Trust Service Criteria.
6. **Third-Party Involvement:** If a company uses third-party service providers, the SOC 2 audit may extend to these providers to ensure that their services also meet the required standards.
Successfully completing a SOC 2 audit provides organizations with a valuable certification that demonstrates their commitment to data security and privacy. It is often seen as a trust-building factor for customers and partners who want assurance that their data is handled with the utmost care and security.
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
#### Major Incident Management Process
Certainly! Designing a comprehensive Major Incident Management process is crucial for maintaining the reliability and availability of enterprise SaaS applications. Below is a detailed outline of the major incident management process, including procedures for handling service outages:
### Major Incident Management Process
#### 1. **Identification and Detection:**
- **Automated Monitoring:** Utilize robust monitoring tools to detect anomalies, performance issues, and potential outages.
- **User Reports:** Encourage users to report issues promptly via designated channels.
#### 2. **Incident Logging:**
- **Centralized Logging:** Maintain a centralized incident log that captures all relevant details, timestamps, and initial impact assessment.
- **Severity Classification:** Categorize incidents based on severity to prioritize response efforts.
#### 3. **Initial Assessment:**
- **Incident Triage:** Quickly assemble a cross-functional incident response team, including representatives from development, operations, and support.
- **Impact Analysis:** Evaluate the scope and impact of the incident on users, systems, and business operations.
#### 4. **Communication:**
- **Internal Communication:** Establish communication channels for the incident response team, ensuring timely updates and coordination.
- **External Communication:** Prepare predefined messages for customers and stakeholders, providing transparency about the incident.
#### 5. **Resolution:**
- **Runbooks and Playbooks:** Develop detailed runbooks and playbooks for common incident scenarios, outlining step-by-step resolution procedures.
- **Escalation Procedures:** Define clear escalation paths for issues that require higher-level expertise or management attention.
#### 6. **Post-Incident Review (PIR):**
- **Root Cause Analysis (RCA):** Conduct a thorough RCA to identify the underlying cause of the incident.
- **Documentation:** Document the incident resolution process, lessons learned, and preventive measures for future incidents.
#### 7. **Continuous Improvement:**
- **Iterative Updates:** Regularly update incident response procedures based on lessons learned from past incidents.
- **Training and Drills:** Conduct regular training sessions and simulated drills to ensure the incident response team is well-prepared.
#### 8. **Monitoring and Alerting Enhancements:**
- **Continuous Monitoring:** Implement ongoing improvements to monitoring and alerting systems to proactively detect potential issues.
- **Automated Remediation:** Integrate automated remediation tools to address common incidents swiftly.
#### 9. **Documentation and Knowledge Sharing:**
- **Knowledge Base:** Maintain a comprehensive knowledge base with troubleshooting guides, FAQs, and resolutions for known issues.
- **Documentation Accessibility:** Ensure that incident response documentation is easily accessible to all team members.
#### 10. **Review and Audit:**
- **Periodic Audits:** Conduct periodic reviews and audits of the major incident management process to identify areas for improvement.
- **Compliance Checks:** Ensure that the process aligns with industry best practices and regulatory requirements.
This Major Incident Management process should be regularly reviewed and updated to adapt to evolving technologies and organizational needs. Regular training and communication exercises will help maintain a proactive and efficient incident response capability.
#### Whats the definition of RACI
RACI is an acronym that stands for Responsible, Accountable, Consulted, and Informed. It is a project management and organizational tool used to clarify roles and responsibilities within a team or across different stakeholders for specific tasks, processes, or projects. Each letter in RACI represents a different level of involvement and accountability:
1. Responsible (R): This person or role is responsible for completing the task or carrying out the work. They are the individuals who perform the actual work and ensure it gets done.
2. Accountable (A): This person is ultimately accountable for the task's success or failure. They are the decision-makers who oversee the work and ensure it aligns with the project's objectives. There should be only one "A" assigned to each task or activity.
3. Consulted (C): These are individuals or roles that provide input, expertise, or advice on the task. They are consulted for their knowledge or perspective, but they are not responsible for the task's completion.
4. Informed (I): These individuals or roles need to be kept informed about the task's progress or outcome but do not have active participation in its execution.
RACI matrices and charts are commonly used to document and communicate these roles and responsibilities, helping teams and organizations to reduce confusion, improve accountability, and ensure that work is completed efficiently and effectively.
#### Handling Data Return in Enterprise SaaS Business
Handling data returns in an enterprise SaaS business requires a well-defined process to ensure customer satisfaction and compliance with data privacy regulations. Here's a step-by-step guide to help you prepare a process for when a client requests to return their data:
1. **Understand Data Ownership and Privacy Policies:**
- Clearly define data ownership in your terms of service and privacy policy.
- Understand and comply with data protection regulations relevant to your industry and geographic location (e.g., GDPR, CCPA).
2. **Provide Clear Terms in Contracts:**
- Include provisions in your contracts that outline the conditions under which clients can request the return of their data.
- Specify the format and timeline for data return.
3. **Implement Data Export Features:**
- Build data export features into your SaaS platform to allow clients to easily retrieve their data in a standard and commonly used format (e.g., CSV, JSON).
- Ensure that exported data includes all relevant information and maintains data integrity.
4. **Establish a Request Process:**
- Create a formalized process for clients to request the return of their data.
- This process could include a dedicated support channel, a web portal, or a specific form.
5. **Authenticate and Verify Requests:**
- Implement a robust authentication process to ensure that only authorized individuals can request data returns.
- Verify the identity of the requester through multi-factor authentication or other secure means.
6. **Document and Track Requests:**
- Keep a centralized record of all data return requests.
- Track the status of each request, including when it was received, processed, and completed.
7. **Review and Cleanse Data:**
- Before returning data, review it to ensure it doesnt contain any sensitive information from other users.
- Implement a data cleansing process to remove any irrelevant or unnecessary information.
8. **Secure Data Transmission:**
- Use secure channels and encryption protocols to transmit the data back to the client.
- Provide the client with instructions on how to securely receive the data.
9. **Notify Client of Completion:**
- Notify the client when their data return request has been processed and the data is available for retrieval.
- Provide any relevant documentation or instructions.
10. **Follow Up for Feedback:**
```other
- Follow up with the client after the data return to gather feedback on the process and ensure their satisfaction.
- Use feedback to continuously improve the data return process.
```
11. **Train Support and Compliance Teams:**
```other
- Ensure that your support and compliance teams are well-trained on the data return process.
- Keep them updated on any changes to regulations or internal policies.
```
12. **Regularly Review and Update Process:**
```other
- Periodically review and update the data return process to incorporate any changes in regulations, technology, or customer needs.
```
By implementing a well-structured process, you can efficiently handle data return requests, maintain customer trust, and comply with data protection laws.
#### Routine DR Validation Process
Routine disaster recovery (DR) validation reviews are crucial for ensuring the resilience of your enterprise SaaS business. Here's a step-by-step guide to help you prepare a process for routine disaster recovery validation reviews:
1. **Define Objectives and Scope:**
- Clearly define the objectives of the routine disaster recovery validation review.
- Specify the scope, including the systems, applications, and data that will be included in the review.
2. **Establish a Schedule:**
- Set a regular schedule for conducting disaster recovery validation reviews. This could be quarterly, semi-annually, or annually based on the criticality of your systems.
3. **Document the Disaster Recovery Plan (DRP):**
- Ensure that you have a comprehensive and up-to-date disaster recovery plan in place.
- Document the step-by-step procedures for recovering systems and data in the event of a disaster.
4. **Identify Key Stakeholders:**
- Identify the key stakeholders involved in the disaster recovery validation process.
- This may include IT administrators, security personnel, and relevant business unit representatives.
5. **Select Validation Criteria:**
- Define the criteria that will be used to validate the effectiveness of the disaster recovery plan.
- Criteria may include recovery time objectives (RTO), recovery point objectives (RPO), and data integrity.
6. **Simulate Disaster Scenarios:**
- Develop a set of realistic disaster scenarios that could impact your systems and data.
- Simulate these scenarios to test the effectiveness of your disaster recovery plan.
7. **Coordinate with Third-Party Vendors:**
- If your SaaS business relies on third-party vendors or cloud service providers, coordinate with them to ensure that their disaster recovery plans align with yours.
- Validate their processes and capabilities as part of your review.
8. **Perform Tabletop Exercises:**
- Conduct tabletop exercises with key stakeholders to walk through various disaster recovery scenarios.
- Use these exercises to identify weaknesses, gaps, and areas for improvement in the plan.
9. **Automate Testing Where Possible:**
- Implement automation tools to simulate and test disaster recovery procedures.
- Automation can help streamline the testing process and provide more accurate results.
10. **Measure and Analyze Results:**
- Measure the performance against the defined criteria during each validation review.
- Analyze the results to identify trends, patterns, and areas that need improvement.
11. **Document Findings and Recommendations:**
- Document the findings of each disaster recovery validation review.
- Provide clear recommendations for improvements and corrective actions.
12. **Implement Improvements:**
- Act on the recommendations and implement improvements to enhance the effectiveness of your disaster recovery plan.
13. **Review and Update DRP:**
- Regularly review and update the disaster recovery plan based on the lessons learned from validation reviews and changes in your IT infrastructure.
14. **Communicate Results:**
- Communicate the results of the disaster recovery validation reviews to relevant stakeholders.
- Ensure transparency and provide information on the steps being taken to address any identified issues.
15. **Continuous Training and Awareness:**
- Conduct regular training sessions for your IT and operational teams to ensure they are familiar with the disaster recovery procedures.
- Raise awareness among employees about the importance of disaster recovery and their roles in the process.
By following these steps, you can establish a robust process for routine disaster recovery validation reviews, helping to ensure the resilience and continuity of your enterprise SaaS business in the face of potential disasters.
#### Please help explain what's the SOC2 audit?
Please help me explain what's the SOC2 audit?
SOC 2, which stands for Service Organization Control 2, is a framework for managing and securing data that is crucial for the privacy of user information. It is specifically designed for technology and cloud computing organizations that store customer data in the cloud. Developed by the American Institute of CPAs (AICPA), SOC 2 compliance is not a one-time event but an ongoing process that requires companies to follow certain criteria to ensure the security, availability, processing integrity, confidentiality, and privacy of customer data.
Here are key components of a SOC 2 audit:
1. **Trust Service Criteria:** SOC 2 is built around five "Trust Service Criteria" (TSC) security, availability, processing integrity, confidentiality, and privacy. These criteria serve as the foundation for evaluating an organization's systems and processes.
- **Security:** The system is protected against unauthorized access (both physical and logical).
- **Availability:** The system is available for operation and use as committed or agreed.
- **Processing Integrity:** System processing is complete, valid, accurate, timely, and authorized.
- **Confidentiality:** Information designated as confidential is protected as committed or agreed.
- **Privacy:** Personal information is collected, used, retained, disclosed, and disposed of in conformity with the commitments in the entitys privacy notice.
2. **SOC 2 Type I and Type II Audits:**
- **Type I:** Examines the suitability of the design of controls at a specific point in time.
- **Type II:** Examines the operational effectiveness of these controls over a period of time (typically a minimum of six months).
3. **Scope and Boundary:** The organization defines the scope of the audit, including the systems that are in scope and the boundaries of the environment being evaluated.
4. **Risk Management and Compliance:** SOC 2 requires organizations to identify and manage risks related to the security, availability, processing integrity, confidentiality, and privacy of information.
5. **Written Policies and Procedures:** Organizations need to have documented policies and procedures in place to demonstrate how they achieve and maintain compliance with the Trust Service Criteria.
6. **Third-Party Involvement:** If a company uses third-party service providers, the SOC 2 audit may extend to these providers to ensure that their services also meet the required standards.
Successfully completing a SOC 2 audit provides organizations with a valuable certification that demonstrates their commitment to data security and privacy. It is often seen as a trust-building factor for customers and partners who want assurance that their data is handled with the utmost care and security.

View File

@@ -1,55 +1,55 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Introduction
The main purpose of this document is to help non-Cloud Ops team members better understand the various services and tools currently provided for Cloud Application troubleshooting, so that they can be used flexibly in different scenarios and reduce dependence on Cloud Ops engineers.
Our goal is also very clear. We hope to provide a more efficient DevOps ecosystem to provide better services to our customers.
**Please note that the various services and tools mentioned below require approval and authorization, and are currently limited to members of the Cloud Ops and R&D CPE teams**
## Troubleshooting as a Service
### Access Environment as a Service
#### Access to Customer Tenant
We provide a method to enter the customer's tenant so that when doing troubleshooting, you can directly access the customer's environment to check the problem and understand the symptoms of the problem at the first time, so as to make the right judgment.
#### Access to ESM Farm BO, IDM, UCMDB JMX console
We provide a method to apply for temporary user access to each farm management console
- BO Suite Admin
- ESM IDM Admin
- UCMDB Super Admin to UCMDB JMX Console
### Log Collection as a Service
We provide a very comprehensive log collection automation tool.
Collect log information of a specific module within a specific time period. Users can select appropriate filtering conditions to collect logs according to different scenarios, so as to locate problems more accurately and reduce extra effort caused by excessive log size.
### Check Configuration
### Monitoring as a Service
#### Unified Monitoring via pre-defined Grafana Dashboard
We provide a lot of rich implementation monitoring data for various troubleshooting. Currently we use Grafana as the monitoring UI to reflect the monitoring data of farm implementation:
- AWS Cloud Watch Data Source - Able to have real-time infrastructure monitoring (AWS EKS/EFS/RDS)
- Prometheus Data Source - Able to check real-time application level metrics exposed by Prometheus
- Database query Data Source - Get some key indicators of the application through database query
- Containerize/K8S - Able to monitor the key monitoring data of the containerize product, container/node/pod etc.
#### Service Availability Health Page
### Log Analysis as a Service
### BI Reporting as a Service
### Unplanned Change Request as a Service
### Other Services
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Introduction
The main purpose of this document is to help non-Cloud Ops team members better understand the various services and tools currently provided for Cloud Application troubleshooting, so that they can be used flexibly in different scenarios and reduce dependence on Cloud Ops engineers.
Our goal is also very clear. We hope to provide a more efficient DevOps ecosystem to provide better services to our customers.
**Please note that the various services and tools mentioned below require approval and authorization, and are currently limited to members of the Cloud Ops and R&D CPE teams**
## Troubleshooting as a Service
### Access Environment as a Service
#### Access to Customer Tenant
We provide a method to enter the customer's tenant so that when doing troubleshooting, you can directly access the customer's environment to check the problem and understand the symptoms of the problem at the first time, so as to make the right judgment.
#### Access to ESM Farm BO, IDM, UCMDB JMX console
We provide a method to apply for temporary user access to each farm management console
- BO Suite Admin
- ESM IDM Admin
- UCMDB Super Admin to UCMDB JMX Console
### Log Collection as a Service
We provide a very comprehensive log collection automation tool.
Collect log information of a specific module within a specific time period. Users can select appropriate filtering conditions to collect logs according to different scenarios, so as to locate problems more accurately and reduce extra effort caused by excessive log size.
### Check Configuration
### Monitoring as a Service
#### Unified Monitoring via pre-defined Grafana Dashboard
We provide a lot of rich implementation monitoring data for various troubleshooting. Currently we use Grafana as the monitoring UI to reflect the monitoring data of farm implementation:
- AWS Cloud Watch Data Source - Able to have real-time infrastructure monitoring (AWS EKS/EFS/RDS)
- Prometheus Data Source - Able to check real-time application level metrics exposed by Prometheus
- Database query Data Source - Get some key indicators of the application through database query
- Containerize/K8S - Able to monitor the key monitoring data of the containerize product, container/node/pod etc.
#### Service Availability Health Page
### Log Analysis as a Service
### BI Reporting as a Service
### Unplanned Change Request as a Service
### Other Services

View File

@@ -1,32 +1,32 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Product Service
### ESM Product
### ESM Cloud Trial
## Customer Service
### SaaS Customer Support Model
### Customer Service Offering Runbook
- Configure SAML authentication
- Configure custom domain for customer
## DevOps/SRE
### ESM Cloud GitLab
### ESM Cloud Operation Automation/Jenkins
### ESM Cloud Monitoring
### ESM Cloud System Health Page
### ESM Cloud Disaster Recovery
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Product Service
### ESM Product
### ESM Cloud Trial
## Customer Service
### SaaS Customer Support Model
### Customer Service Offering Runbook
- Configure SAML authentication
- Configure custom domain for customer
## DevOps/SRE
### ESM Cloud GitLab
### ESM Cloud Operation Automation/Jenkins
### ESM Cloud Monitoring
### ESM Cloud System Health Page
### ESM Cloud Disaster Recovery

View File

@@ -1,60 +1,60 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Role and Responsibility
#### Strategic Responsibilities
- Own the reliability and performance of multiple SaaS Application Services. (APM, BPM, OpsB, NOM, ESM, DCA, SBM, ESM FedRAMP)
- Drive cloud modernization initiatives (e.g., containerization, EKS, CI/CD automation).
- Align cloud service delivery with customer SLAs, business growth, and compliance frameworks (e.g., FedRAMP, SBM).
- Interface with Sales, Product, Security, and Compliance to support new customer onboarding and cloud architecture reviews.
#### Operational Responsibilities
- Oversee 24x7 operation and monitoring of cloud platforms (AWS-based).
- Manage escalations, incidents, and root cause analysis.
- Coordinate patching, upgrades, hotfixes, and maintenance windows.
- Own service onboarding/offboarding workflows, including tenant provisioning and decommissioning.
#### People Management
- Performance management and coaching of global team members.
- Run weekly team syncs, monthly reviews, and ad-hoc cross-regional escalations.
## Cloud Applications and Cloud Services KS Sessions
### Session 1
- ITOM Cloud Application AWS Account Owner
- https://confluence.opentext.com/display/ICSD/ITOM+Cloud+AWS+Account+Overview
- AWS Account Admin ownership
- Responsibility of AWS Account Admin
- ITOM Cloud Application List
- ESM/ITOM Aviator/DCA/SBM: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- OpsB/NOM Cloud Application List: https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
- APM Cloud Farm List: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information
- ITOM Cloud FinOps
- BI Reporting: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1a3fceca-6563-4cc6-8218-d1d27f15e2f1/ReportSection?experience=power-bi
- Opentext FinOps Team AWS FinOps Dashboard
### Session 2
- ITOM Cloud Application Version Currency
- Upgrade Plan
- ESM/FebRAMP Ops Change Calendar
- OpsB/NOM Ops Change Calendar
- Upgrade Plan Timeline
### Session 3
### Session 4
### Session 5
### Session 6
### Session 7
### Session 8
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## Role and Responsibility
#### Strategic Responsibilities
- Own the reliability and performance of multiple SaaS Application Services. (APM, BPM, OpsB, NOM, ESM, DCA, SBM, ESM FedRAMP)
- Drive cloud modernization initiatives (e.g., containerization, EKS, CI/CD automation).
- Align cloud service delivery with customer SLAs, business growth, and compliance frameworks (e.g., FedRAMP, SBM).
- Interface with Sales, Product, Security, and Compliance to support new customer onboarding and cloud architecture reviews.
#### Operational Responsibilities
- Oversee 24x7 operation and monitoring of cloud platforms (AWS-based).
- Manage escalations, incidents, and root cause analysis.
- Coordinate patching, upgrades, hotfixes, and maintenance windows.
- Own service onboarding/offboarding workflows, including tenant provisioning and decommissioning.
#### People Management
- Performance management and coaching of global team members.
- Run weekly team syncs, monthly reviews, and ad-hoc cross-regional escalations.
## Cloud Applications and Cloud Services KS Sessions
### Session 1
- ITOM Cloud Application AWS Account Owner
- https://confluence.opentext.com/display/ICSD/ITOM+Cloud+AWS+Account+Overview
- AWS Account Admin ownership
- Responsibility of AWS Account Admin
- ITOM Cloud Application List
- ESM/ITOM Aviator/DCA/SBM: https://confluence.opentext.com/display/ICSD/ITOM+ESM+Cloud+Farm+Information
- OpsB/NOM Cloud Application List: https://confluence.opentext.com/display/ICSD/OpsB+and+NOM+Cloud+Deployments+Version+Tracking
- APM Cloud Farm List: https://confluence.opentext.com/display/ICSD/ITOM+APM+AppPluse+Cloud+Farm+Information
- ITOM Cloud FinOps
- BI Reporting: https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/1a3fceca-6563-4cc6-8218-d1d27f15e2f1/ReportSection?experience=power-bi
- Opentext FinOps Team AWS FinOps Dashboard
### Session 2
- ITOM Cloud Application Version Currency
- Upgrade Plan
- ESM/FebRAMP Ops Change Calendar
- OpsB/NOM Ops Change Calendar
- Upgrade Plan Timeline
### Session 3
### Session 4
### Session 5
### Session 6
### Session 7
### Session 8

View File

@@ -1,66 +1,66 @@
---
title: Cloud Service “Bilities”
source:
author: shenwei
published: 2025-03-01
created: 2025-03-01
description:
tags: []
link:
---
# Cloud Service “Bilities”
**The "bilities"**
In heritage OpenText Architecture we are constantly chasing how to meet what we lovingly call the "bilities".  Here is a list of the "bilities"
Below are the primary capability requirements of any application to be operated by OT Commercial Cloud Service Delivery.  Some are absolute requirements, others add to the stability, performance and customer experience of the service.
These define the “What” not the “How” of either the application or the infrastructure.
**Recoverability** Capability of an application to recover to a normal processing disposition as soon as a deviation from normal processing is detected either internal to the application or through external monitoring.  Recoverability includes not only restarting but restarting processing where it was last interrupted.
**Usability** All applications in a processing community are interacted with in a common and predictable manner, both from the administration side and the consumer side.  Standards in usability across applications supports efficient usage across those applications.
**Operability** Capability of an application component to be started, stopped, updated, diagnosed and deployed in a standard and predictable way.
**Maintainability** Capability of an application component to be updated, patched or functions changed in a standard and predictable manner.  Maintainability requires backward compatibility through 2 or more releases to enable maintenance activities to occur online.
**Securability** Capability of an application component to protect its assets and customer payload from unauthorized access.    Additionally, a capability to enforce access control rules based upon approved role.
**Persistability** Capability of an application to always persist payload data once it has entered the OT processing environment.  That data must exist and be accessible through maintenance, defect, application outage and normal processing.  Persistence should last the entirety of the payloads expected processing lifecycle from entrance to the environment through historical archive expiration. Persistence will accommodate global processing and Disaster Recovery requirements where applicable.
**Mobility** Capability of an application to survive infrastructure actions to support moving service components through In-center High Availability, Intra-center Geographic relocation and Intra-service Private to Public Cloud relocation.
**Throttleability** Capability of an application to control the processing rate through each component or service.  This key capability of each application component enables the operators of the service to isolate and maintain control of recovery to normal processing flow.
**Deployability** Capability of an application component to be maintained without downtime to the solution as a whole.  Maintenance, wherever possible, needs to take place without externally identifiable service interruption.
**Reliability** Capability of an application component or collection of components to with a high degree of consistency perform its defined function through both normal and abnormal operating conditions.  Reliability requires that the application component be able to perform its defined work through outage.
**Reusability** Capability of application service to perform for more than one service consumer.  Build once, use many in a common and consistent way.
**Accountability/Billability** Capability of an application to accurately report its usage by customer tenant for financial accounting purposes.
**Durability**Capability of an application to survive deviation from normal operating conditions.
**Troubleshootability** Capability to provide clear output to logging systems about all application components health and disposition during both normal and abnormal operating conditions.
**Defensibility** Capability and awareness of the application to defend itself against incorrect usage.  Both accidental and purposeful.
**Extensibility** An upfront design capability that takes into consideration the applications ability to expand its functions automatically in response to dynamic demand prompts.  Extensibility promotes expandability and elasticity.
**Auditability** An application needs to be deployed with capabilities and structures, and within an infrastructure design, that meet applicable external security and audit standards.
**Application configurability** All required feature and functional configuration management should be provided through a Web/API enabled interface.  No customer should need administrative access to underlying systems or infrastructure to perform customer available administrative tasks.
**Observability** Capability of an application to be deployed as an active part of an ecosystem that provides and accurate, timely, and complete indication of functional status and capacity level.
**Visibility** Provide capability to see detailed monitoring data describing the operating condition or health of the application.
**Affordability** An application needs to implement and use components and software that achieve P&L objectives and retain that position when scaled.  This includes the full range of administrative, support, and operational costs.
**Adaptability** An advanced capability of an application to be aware of processing going on around both upstream and downstream.  AI and machine learning are key to this capability.
---
title: Cloud Service “Bilities”
source:
author: shenwei
published: 2025-03-01
created: 2025-03-01
description:
tags: []
link:
---
# Cloud Service “Bilities”
**The "bilities"**
In heritage OpenText Architecture we are constantly chasing how to meet what we lovingly call the "bilities".  Here is a list of the "bilities"
Below are the primary capability requirements of any application to be operated by OT Commercial Cloud Service Delivery.  Some are absolute requirements, others add to the stability, performance and customer experience of the service.
These define the “What” not the “How” of either the application or the infrastructure.
**Recoverability** Capability of an application to recover to a normal processing disposition as soon as a deviation from normal processing is detected either internal to the application or through external monitoring.  Recoverability includes not only restarting but restarting processing where it was last interrupted.
**Usability** All applications in a processing community are interacted with in a common and predictable manner, both from the administration side and the consumer side.  Standards in usability across applications supports efficient usage across those applications.
**Operability** Capability of an application component to be started, stopped, updated, diagnosed and deployed in a standard and predictable way.
**Maintainability** Capability of an application component to be updated, patched or functions changed in a standard and predictable manner.  Maintainability requires backward compatibility through 2 or more releases to enable maintenance activities to occur online.
**Securability** Capability of an application component to protect its assets and customer payload from unauthorized access.    Additionally, a capability to enforce access control rules based upon approved role.
**Persistability** Capability of an application to always persist payload data once it has entered the OT processing environment.  That data must exist and be accessible through maintenance, defect, application outage and normal processing.  Persistence should last the entirety of the payloads expected processing lifecycle from entrance to the environment through historical archive expiration. Persistence will accommodate global processing and Disaster Recovery requirements where applicable.
**Mobility** Capability of an application to survive infrastructure actions to support moving service components through In-center High Availability, Intra-center Geographic relocation and Intra-service Private to Public Cloud relocation.
**Throttleability** Capability of an application to control the processing rate through each component or service.  This key capability of each application component enables the operators of the service to isolate and maintain control of recovery to normal processing flow.
**Deployability** Capability of an application component to be maintained without downtime to the solution as a whole.  Maintenance, wherever possible, needs to take place without externally identifiable service interruption.
**Reliability** Capability of an application component or collection of components to with a high degree of consistency perform its defined function through both normal and abnormal operating conditions.  Reliability requires that the application component be able to perform its defined work through outage.
**Reusability** Capability of application service to perform for more than one service consumer.  Build once, use many in a common and consistent way.
**Accountability/Billability** Capability of an application to accurately report its usage by customer tenant for financial accounting purposes.
**Durability**Capability of an application to survive deviation from normal operating conditions.
**Troubleshootability** Capability to provide clear output to logging systems about all application components health and disposition during both normal and abnormal operating conditions.
**Defensibility** Capability and awareness of the application to defend itself against incorrect usage.  Both accidental and purposeful.
**Extensibility** An upfront design capability that takes into consideration the applications ability to expand its functions automatically in response to dynamic demand prompts.  Extensibility promotes expandability and elasticity.
**Auditability** An application needs to be deployed with capabilities and structures, and within an infrastructure design, that meet applicable external security and audit standards.
**Application configurability** All required feature and functional configuration management should be provided through a Web/API enabled interface.  No customer should need administrative access to underlying systems or infrastructure to perform customer available administrative tasks.
**Observability** Capability of an application to be deployed as an active part of an ecosystem that provides and accurate, timely, and complete indication of functional status and capacity level.
**Visibility** Provide capability to see detailed monitoring data describing the operating condition or health of the application.
**Affordability** An application needs to implement and use components and software that achieve P&L objectives and retain that position when scaled.  This includes the full range of administrative, support, and operational costs.
**Adaptability** An advanced capability of an application to be aware of processing going on around both upstream and downstream.  AI and machine learning are key to this capability.

View File

@@ -1,56 +1,56 @@
---
title: ITOM Cloud Service Review Meeting
source:
author: shenwei
published:
created:
description:
tags: []
---
# ITOM Cloud Service Review Meeting
### ESM
- Current Status
- Farm #, region,
- EU managed
- Team size
- Major workload
- Upgrade maintenance
- Customer Cloud Service request → Show trend
- Internal Cloud Service - Trial (SMAX, CMS Standalone, HCMX, ITOM Aviator), Unplanned change etc.
- Customer driven project - UPN change,
- Security
- Recent Plan and activity
- Upgrade/Patch, SMAX helm transformation
- New farm plan
- ITOM Aviator productionize
- Issues & Gap
- EU Ops engineer resource gap
- FedRAMP resource gap
- Product quality caused additional operation effort
### Smart Observability
- Current Status
- Instances #, customer #
- Team size
- Major workload
- Cloud deployment automation certification
- Paid customer cloud instance deployment, initial configuration
- Trial Instance deployment
- Maintain upgrade, upgrade validation
- Customer
- Recent Plan and activity
- 24.2 Upgrade/Patch
- Support new product capability - AppO, CNO
- Issues & Gap
- AWS Cost Control
- Trial Instance control
- NOM Ops resource gap
- PCS Support case
- RnD request cloud instance
---
title: ITOM Cloud Service Review Meeting
source:
author: shenwei
published:
created:
description:
tags: []
---
# ITOM Cloud Service Review Meeting
### ESM
- Current Status
- Farm #, region,
- EU managed
- Team size
- Major workload
- Upgrade maintenance
- Customer Cloud Service request → Show trend
- Internal Cloud Service - Trial (SMAX, CMS Standalone, HCMX, ITOM Aviator), Unplanned change etc.
- Customer driven project - UPN change,
- Security
- Recent Plan and activity
- Upgrade/Patch, SMAX helm transformation
- New farm plan
- ITOM Aviator productionize
- Issues & Gap
- EU Ops engineer resource gap
- FedRAMP resource gap
- Product quality caused additional operation effort
### Smart Observability
- Current Status
- Instances #, customer #
- Team size
- Major workload
- Cloud deployment automation certification
- Paid customer cloud instance deployment, initial configuration
- Trial Instance deployment
- Maintain upgrade, upgrade validation
- Customer
- Recent Plan and activity
- 24.2 Upgrade/Patch
- Support new product capability - AppO, CNO
- Issues & Gap
- AWS Cost Control
- Trial Instance control
- NOM Ops resource gap
- PCS Support case
- RnD request cloud instance

View File

@@ -1,72 +1,72 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
### Mar 19
- ESM 25.2 upgrade validation plan - Ting Ye
- ESM Change Management Process Update - Ting Ye
- SG Yearly DR solution to share with team - Shen Wei
- ITOM Aviator EU32 farm construction status - Liu Yu/Adina
- ESM US2 dev farm automation pipeline - Sunny
- Update Incident Management Process -Shen Wei
-
### Mar 13
- Demo: New Jenkins Job to request temp BO admin user -Wenjun Sun
- Customer case update 
- Team Project Update:
- ESM Upgrade/Patch/Hotfix - Ting Ye
- AWS account migration to new SCP OU hierarchy - Yu Liu
- Terraform automation to construct new farm - Sunny Xia
- Round table
### Mar 3
- Patch upgrade rollback strategy update - Shen Wei
- Heads up about time coverage - Shen Wei
- Ops Doc review and approve - Shen Wei
- ESM 25.1.2 + ITOM Aviator 25.1.2 Patch upgrade plan - Shen Wei
- ITOM Aviator (EU managed farm) budget approved, start project  Yu Liu 
- OP BVD ILR license generation and documentation Miroslav Shindarov Yun Zhao
- Operation Excellence Update
- Remove BO admin and use temp suite-admin account
- Grafana - AWS Cognito authentication status
- Terraform for ESM  
- DevSecOps Qualys/Prisma- Yu Liu
- New member training status update 
- Round table
### Feb 25
- FedRAMP farm updates - Jeremy Thunker
- Team project update
- ESM Upgrade Ting Ye
- AWS account migration to new SCP OU hierarchy - Yu Liu
- Grafana to use AWS Cognito - Shen Wei
- CCOE AMI adoption - Ting Ye
- Cost Optimization - Ling-yan Meng
- Introduce the process how to handle security scan found issues - Shen Wei
- Customer exit process
### Feb 17
- Welcome to Mericel to join ESM Cloud Ops team
- ESM 25.1.1 patch upgrade status - Ting Ye
- ITOM Aviator 25.1.1 upgrade status update - Yu Liu
- Mega Audit update - Shen Wei
- New ITOM Aviator farm (EU-managed) preparation - Yu Liu
- SMAX helm hotfix post deployment actions - Ling-yan Meng
- Round table update
### Feb 12
- ESM Cloud Service meeting schedule introduction
- ITOM ESM Cloud Service Catalog introduction and new service approval flow
- New Project:
- New ITOM Aviator Farm (EU managed)
- Adopt CCOE AMI images
- 25.2 upgrade plan
- New ITOM Cloud Farm Architecture
- New member training plan
I will record this meeting to ensure different time zone team member can watch the replay.
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
### Mar 19
- ESM 25.2 upgrade validation plan - Ting Ye
- ESM Change Management Process Update - Ting Ye
- SG Yearly DR solution to share with team - Shen Wei
- ITOM Aviator EU32 farm construction status - Liu Yu/Adina
- ESM US2 dev farm automation pipeline - Sunny
- Update Incident Management Process -Shen Wei
-
### Mar 13
- Demo: New Jenkins Job to request temp BO admin user -Wenjun Sun
- Customer case update 
- Team Project Update:
- ESM Upgrade/Patch/Hotfix - Ting Ye
- AWS account migration to new SCP OU hierarchy - Yu Liu
- Terraform automation to construct new farm - Sunny Xia
- Round table
### Mar 3
- Patch upgrade rollback strategy update - Shen Wei
- Heads up about time coverage - Shen Wei
- Ops Doc review and approve - Shen Wei
- ESM 25.1.2 + ITOM Aviator 25.1.2 Patch upgrade plan - Shen Wei
- ITOM Aviator (EU managed farm) budget approved, start project  Yu Liu 
- OP BVD ILR license generation and documentation Miroslav Shindarov Yun Zhao
- Operation Excellence Update
- Remove BO admin and use temp suite-admin account
- Grafana - AWS Cognito authentication status
- Terraform for ESM  
- DevSecOps Qualys/Prisma- Yu Liu
- New member training status update 
- Round table
### Feb 25
- FedRAMP farm updates - Jeremy Thunker
- Team project update
- ESM Upgrade Ting Ye
- AWS account migration to new SCP OU hierarchy - Yu Liu
- Grafana to use AWS Cognito - Shen Wei
- CCOE AMI adoption - Ting Ye
- Cost Optimization - Ling-yan Meng
- Introduce the process how to handle security scan found issues - Shen Wei
- Customer exit process
### Feb 17
- Welcome to Mericel to join ESM Cloud Ops team
- ESM 25.1.1 patch upgrade status - Ting Ye
- ITOM Aviator 25.1.1 upgrade status update - Yu Liu
- Mega Audit update - Shen Wei
- New ITOM Aviator farm (EU-managed) preparation - Yu Liu
- SMAX helm hotfix post deployment actions - Ling-yan Meng
- Round table update
### Feb 12
- ESM Cloud Service meeting schedule introduction
- ITOM ESM Cloud Service Catalog introduction and new service approval flow
- New Project:
- New ITOM Aviator Farm (EU managed)
- Adopt CCOE AMI images
- 25.2 upgrade plan
- New ITOM Cloud Farm Architecture
- New member training plan
I will record this meeting to ensure different time zone team member can watch the replay.

View File

@@ -1,164 +1,164 @@
# ITOM ESM Cloud Service Monthly Report - Feb 2025
**2025/2/1 ~ 2025/2/28**
This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
---
title: **ITOM ESM Cloud Service Monthly Report - Feb 2025**
author: shenwei
tags: [Cloud, Customer, Product]
---
---
title: **ITOM ESM Cloud Service Monthly Report - Feb 2025**
source:
author: shenwei
published:
created:
description:
tags: [Cloud, Customer, Product]
---
# Table of Content:
- [[#Product Cloud Service|Product Cloud Service]]
- [[#Product Cloud Service#Planned Maintenance Window Changes|Planned Maintenance Window Changes]]
- [[#Product Cloud Service#Upgrade Plan|Upgrade Plan]]
- [[#Product Cloud Service#Unplanned Production Change|Unplanned Production Change]]
- [[#Product Cloud Service#Tenant Provision Services|Tenant Provision Services]]
- [[#Product Cloud Service#Product Trial Service|Product Trial Service]]
- [[#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Major Incident & RCA|Major Incident & RCA]]
- [[#Customer Cloud Service#Customer Order & Fulfillment Highlights|Customer Order & Fulfillment Highlights]]
- [[#Customer Cloud Service#Monthly SLA|Monthly SLA]]
- [[#Cloud DevOps/SRE|Cloud DevOps/SRE]]
- [[#Cloud DevOps/SRE#ITOM Operation Platform 25.1|ITOM Operation Platform 25.1]]
- [[#Cloud DevOps/SRE#ESM Cloud Application WAF Enablement|ESM Cloud Application WAF Enablement]]
- [[#Cloud DevOps/SRE#ESM GCP onboarding|ESM GCP onboarding]]
- [[#Cloud DevOps/SRE#Security & Compliance|Security & Compliance]]
- [[#Cloud DevOps/SRE#Cloud BI Reporting|Cloud BI Reporting]]
---
# Product Cloud Service
## Planned Maintenance Window Changes
- **ESM Standard Planned Changes**
- There were a total of **49** times of (SMX/CMS/OMT/OO, FedRAMP, DCA, ITOM Aviator) Upgrade/Patch/Hotfix deployments to various farms
- All **ESM production farms** (**EU3/US7/US2/US24/US26/US6/EU8/AP10/JP12/BR14/CA16/EU18/EU28**) were upgraded to ESM latest major version **ESM 25.1.1** by the end of Feb, 2025
- **ITOM Operation Platform** **25.1.1** was upgraded on ESM farm (**EU3/US24/EU18**) by the end of Feb, 2025
- **ITOM Aviator Service** (**EU30**) was already upgraded to **25.1.1** with some infra change with language model
- All ESM Farm's **AWS EKS** version were upgraded to **1.30**
- FedRAMP **AMI Rotation + 24.3 FP4** was done successfully in Feb maintenance window
![Image](http://zipline.ishenwei.online/u/9KyDGW.png)
![Image](http://zipline.ishenwei.online/u/u3jqNN.png)
![Image](http://zipline.ishenwei.online/u/UQxkcZ.png)
![Image](http://zipline.ishenwei.online/u/EA5ruD.png)
![Image](http://zipline.ishenwei.online/u/hGHxKw.png)
---
## Upgrade Plan
**ESM 25.1 Upgrade Plan - COMPLETE**
![Image](http://zipline.ishenwei.online/u/d1OO4K.png)
**ESM 25.2 Upgrade Plan - IN PROGRESS**
![Image](http://zipline.ishenwei.online/u/A4bEnW.png)
---
## Unplanned Production Change
- Total **17** Unplanned Production Changes deployed to different ESM farms in this month
- **The number of additional unplanned change requests generated by product quality increased again this month. This needs to be taken seriously by the RnD team and analyzed after the fact to reduce similar problems and additional production changes.::**
![Image](http://zipline.ishenwei.online/u/kGuobh.png)
![Image](http://zipline.ishenwei.online/u/LnBn4Y.png)
![Image](http://zipline.ishenwei.online/u/9B64zk.png)
---
## Tenant Provision Services
- There were a total of **28** tenant provision request fulfilled in this month.
![Image](http://zipline.ishenwei.online/u/EJGHvv.png)
![Image](http://zipline.ishenwei.online/u/mzkyBA.png)
---
## Product Trial Service
- There were a total of **370** product trial related service request fulfilled in this month.
![Image](http://zipline.ishenwei.online/u/1AkGlm.png)
![Image](http://zipline.ishenwei.online/u/UMP0Y5.png)
![Image](http://zipline.ishenwei.online/u/j3Ap39.png)
- CT Trial (External Customer SMAX Only Trial) - **12**
![Image](http://zipline.ishenwei.online/u/NTkk0s.png)
- Internal SMAX Premium Trial - **50**
![Image](http://zipline.ishenwei.online/u/GeXoRV.png)
- HCMX Trial - **1**
![Image](http://zipline.ishenwei.online/u/2sOhvg.png)
- ESM (SMAX/AMX/CMS) Trial - **21**
![Image](http://zipline.ishenwei.online/u/XqdzLt.png)
- **ITOM Aviator Trial - 20**
![Image](http://zipline.ishenwei.online/u/FZN250.png)
# Customer Cloud Service
## Customer Cloud Service
- There were a total of **96** Customer Cloud Service Requests handled by ESM Cloud Service team in PCS
- **Special thanks to Remi's ESM Cloud RnD team for their cooperation in handling the customer's Cloud Service Request with technical support!**
![Image](http://zipline.ishenwei.online/u/ccwoCw.png)
---
## Major Incident & RCA
#### [2025/02/09 - US6/US2/AP10 - SMAX- Major Function Issue](https://confluence.opentext.com/pages/viewpage.action?pageId=689008569)
---
## Customer Order & Fulfillment Highlights
![Image](http://zipline.ishenwei.online/u/Wm4ACS.png)
---
## Monthly SLA
- **ESM SMAX has achieved all 100% SLA in Jan, 2024**
- Data is available through **Jan 2024**, with Feb 2025 SLA data to be released around mid-March.
---
# Cloud DevOps/SRE
## ITOM Aviator (EU-Managed) Farm
- We got final business approval to construct new **ITOM Aviator (EU-managed)** farm.
- The project has already started and is expected to be delivered in 1 to 2 weeks
## ITOM Operation Platform 25.1
- We have successfully upgrade Operation Platform 24.4 to **25.1** on **EU3/US24/EU18** farm.
- We're preparing the Operation Platform 25.2 cloud readiness work include to start support OpsB to use OP 25.2 in ITOM Cloud environment.
- We are now continuing to work on the OP D2 enablement automation and the relevant operation runbook.
## ESM Cloud Application WAF Enablement
- A new DevOps project was initiated to enhance WAF rules management & cloud deployment automation
- After some series of team efforts, it went through several processes of testing, modification, and deployment. We have now reached the criteria of **Enable WAF denied mode** with current WAF version on top of SMAX 25.1.1.
- We have enabled **SMAX WAF** in **Denied Mode** on **US2** farm this Monday(Feb 24th).
- In addition, we have enable  **Observation Mode** on **US24** & **US26** farms to get more WAF log information to help us determine the effectiveness of blockings.
## ESM GCP onboarding
- Based on the business requirements, we started working with Cloud SA group and Product group on GCP onboarding for ESM. Currently, there are two main working threads which require product team support to certify ESM products deployment on GCP, and validation and experimentation for GCP OCF cloud architecture platform led by Cloud SA.
## Operation Excellence
- We are actively enhancing the automation of ESM farm construction, covering all configurations based on AWS services, and using OT compliance automation solutions to build cloud automation deployment for ESM Farm
- We've implemented to use AWS Cognito authentication to control all Cloud Ops tooling. Recently we've enabled AWS Cognito authentication to access **Grafana** monitoring tool
## Security & Compliance
- At the request of Opentext GIS, the ESM Cloud Service Team has installed **Qualys** and **Prisma Defender** on all ESM production farms in order to facilitate security scanning on the Cloud to provide more secure ESM SaaS services.
- We recently launched a new project on how to handle various OS-based security issues discovered by Qualys Scan. In conjunction with the upcoming adopt **CCOE AMI** project, we intend to centrally replace and update the existing Cloud Application's EKS worker node OS to meet higher security standards.
## Cloud Service BI Reporting
- ITOM ESM Farm/Tenant Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3243d84335d863ef318a?experience=power-bi)
- ITOM Cloud Service Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/363a8aba-6746-4468-9d5c-54e0a463b708/ReportSectionc350f5d544676dc460b4?experience=power-bi)
---
# ITOM ESM Cloud Service Monthly Report - Feb 2025
**2025/2/1 ~ 2025/2/28**
This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
---
title: **ITOM ESM Cloud Service Monthly Report - Feb 2025**
author: shenwei
tags: [Cloud, Customer, Product]
---
---
title: **ITOM ESM Cloud Service Monthly Report - Feb 2025**
source:
author: shenwei
published:
created:
description:
tags: [Cloud, Customer, Product]
---
# Table of Content:
- [[#Product Cloud Service|Product Cloud Service]]
- [[#Product Cloud Service#Planned Maintenance Window Changes|Planned Maintenance Window Changes]]
- [[#Product Cloud Service#Upgrade Plan|Upgrade Plan]]
- [[#Product Cloud Service#Unplanned Production Change|Unplanned Production Change]]
- [[#Product Cloud Service#Tenant Provision Services|Tenant Provision Services]]
- [[#Product Cloud Service#Product Trial Service|Product Trial Service]]
- [[#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Major Incident & RCA|Major Incident & RCA]]
- [[#Customer Cloud Service#Customer Order & Fulfillment Highlights|Customer Order & Fulfillment Highlights]]
- [[#Customer Cloud Service#Monthly SLA|Monthly SLA]]
- [[#Cloud DevOps/SRE|Cloud DevOps/SRE]]
- [[#Cloud DevOps/SRE#ITOM Operation Platform 25.1|ITOM Operation Platform 25.1]]
- [[#Cloud DevOps/SRE#ESM Cloud Application WAF Enablement|ESM Cloud Application WAF Enablement]]
- [[#Cloud DevOps/SRE#ESM GCP onboarding|ESM GCP onboarding]]
- [[#Cloud DevOps/SRE#Security & Compliance|Security & Compliance]]
- [[#Cloud DevOps/SRE#Cloud BI Reporting|Cloud BI Reporting]]
---
# Product Cloud Service
## Planned Maintenance Window Changes
- **ESM Standard Planned Changes**
- There were a total of **49** times of (SMX/CMS/OMT/OO, FedRAMP, DCA, ITOM Aviator) Upgrade/Patch/Hotfix deployments to various farms
- All **ESM production farms** (**EU3/US7/US2/US24/US26/US6/EU8/AP10/JP12/BR14/CA16/EU18/EU28**) were upgraded to ESM latest major version **ESM 25.1.1** by the end of Feb, 2025
- **ITOM Operation Platform** **25.1.1** was upgraded on ESM farm (**EU3/US24/EU18**) by the end of Feb, 2025
- **ITOM Aviator Service** (**EU30**) was already upgraded to **25.1.1** with some infra change with language model
- All ESM Farm's **AWS EKS** version were upgraded to **1.30**
- FedRAMP **AMI Rotation + 24.3 FP4** was done successfully in Feb maintenance window
![Image](http://zipline.ishenwei.online/u/9KyDGW.png)
![Image](http://zipline.ishenwei.online/u/u3jqNN.png)
![Image](http://zipline.ishenwei.online/u/UQxkcZ.png)
![Image](http://zipline.ishenwei.online/u/EA5ruD.png)
![Image](http://zipline.ishenwei.online/u/hGHxKw.png)
---
## Upgrade Plan
**ESM 25.1 Upgrade Plan - COMPLETE**
![Image](http://zipline.ishenwei.online/u/d1OO4K.png)
**ESM 25.2 Upgrade Plan - IN PROGRESS**
![Image](http://zipline.ishenwei.online/u/A4bEnW.png)
---
## Unplanned Production Change
- Total **17** Unplanned Production Changes deployed to different ESM farms in this month
- **The number of additional unplanned change requests generated by product quality increased again this month. This needs to be taken seriously by the RnD team and analyzed after the fact to reduce similar problems and additional production changes.::**
![Image](http://zipline.ishenwei.online/u/kGuobh.png)
![Image](http://zipline.ishenwei.online/u/LnBn4Y.png)
![Image](http://zipline.ishenwei.online/u/9B64zk.png)
---
## Tenant Provision Services
- There were a total of **28** tenant provision request fulfilled in this month.
![Image](http://zipline.ishenwei.online/u/EJGHvv.png)
![Image](http://zipline.ishenwei.online/u/mzkyBA.png)
---
## Product Trial Service
- There were a total of **370** product trial related service request fulfilled in this month.
![Image](http://zipline.ishenwei.online/u/1AkGlm.png)
![Image](http://zipline.ishenwei.online/u/UMP0Y5.png)
![Image](http://zipline.ishenwei.online/u/j3Ap39.png)
- CT Trial (External Customer SMAX Only Trial) - **12**
![Image](http://zipline.ishenwei.online/u/NTkk0s.png)
- Internal SMAX Premium Trial - **50**
![Image](http://zipline.ishenwei.online/u/GeXoRV.png)
- HCMX Trial - **1**
![Image](http://zipline.ishenwei.online/u/2sOhvg.png)
- ESM (SMAX/AMX/CMS) Trial - **21**
![Image](http://zipline.ishenwei.online/u/XqdzLt.png)
- **ITOM Aviator Trial - 20**
![Image](http://zipline.ishenwei.online/u/FZN250.png)
# Customer Cloud Service
## Customer Cloud Service
- There were a total of **96** Customer Cloud Service Requests handled by ESM Cloud Service team in PCS
- **Special thanks to Remi's ESM Cloud RnD team for their cooperation in handling the customer's Cloud Service Request with technical support!**
![Image](http://zipline.ishenwei.online/u/ccwoCw.png)
---
## Major Incident & RCA
#### [2025/02/09 - US6/US2/AP10 - SMAX- Major Function Issue](https://confluence.opentext.com/pages/viewpage.action?pageId=689008569)
---
## Customer Order & Fulfillment Highlights
![Image](http://zipline.ishenwei.online/u/Wm4ACS.png)
---
## Monthly SLA
- **ESM SMAX has achieved all 100% SLA in Jan, 2024**
- Data is available through **Jan 2024**, with Feb 2025 SLA data to be released around mid-March.
---
# Cloud DevOps/SRE
## ITOM Aviator (EU-Managed) Farm
- We got final business approval to construct new **ITOM Aviator (EU-managed)** farm.
- The project has already started and is expected to be delivered in 1 to 2 weeks
## ITOM Operation Platform 25.1
- We have successfully upgrade Operation Platform 24.4 to **25.1** on **EU3/US24/EU18** farm.
- We're preparing the Operation Platform 25.2 cloud readiness work include to start support OpsB to use OP 25.2 in ITOM Cloud environment.
- We are now continuing to work on the OP D2 enablement automation and the relevant operation runbook.
## ESM Cloud Application WAF Enablement
- A new DevOps project was initiated to enhance WAF rules management & cloud deployment automation
- After some series of team efforts, it went through several processes of testing, modification, and deployment. We have now reached the criteria of **Enable WAF denied mode** with current WAF version on top of SMAX 25.1.1.
- We have enabled **SMAX WAF** in **Denied Mode** on **US2** farm this Monday(Feb 24th).
- In addition, we have enable  **Observation Mode** on **US24** & **US26** farms to get more WAF log information to help us determine the effectiveness of blockings.
## ESM GCP onboarding
- Based on the business requirements, we started working with Cloud SA group and Product group on GCP onboarding for ESM. Currently, there are two main working threads which require product team support to certify ESM products deployment on GCP, and validation and experimentation for GCP OCF cloud architecture platform led by Cloud SA.
## Operation Excellence
- We are actively enhancing the automation of ESM farm construction, covering all configurations based on AWS services, and using OT compliance automation solutions to build cloud automation deployment for ESM Farm
- We've implemented to use AWS Cognito authentication to control all Cloud Ops tooling. Recently we've enabled AWS Cognito authentication to access **Grafana** monitoring tool
## Security & Compliance
- At the request of Opentext GIS, the ESM Cloud Service Team has installed **Qualys** and **Prisma Defender** on all ESM production farms in order to facilitate security scanning on the Cloud to provide more secure ESM SaaS services.
- We recently launched a new project on how to handle various OS-based security issues discovered by Qualys Scan. In conjunction with the upcoming adopt **CCOE AMI** project, we intend to centrally replace and update the existing Cloud Application's EKS worker node OS to meet higher security standards.
## Cloud Service BI Reporting
- ITOM ESM Farm/Tenant Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3243d84335d863ef318a?experience=power-bi)
- ITOM Cloud Service Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/363a8aba-6746-4468-9d5c-54e0a463b708/ReportSectionc350f5d544676dc460b4?experience=power-bi)
---

View File

@@ -1,200 +1,200 @@
---
title: ITOM ESM Cloud Service Monthly Report - Jan 2025
source:
author: shenwei
published:
created: 2025-03-02
description: This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
tags:
- Cloud
- Customer
- Product
---
# **ITOM ESM Cloud Service Monthly Report - Jan 2025**
**2025/1/1 ~ 2025/1/31**
This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
# Table of Content:
- [[#Product Cloud Service|Product Cloud Service]]
- [[#Product Cloud Service#Planned Maintenance Window Changes|Planned Maintenance Window Changes]]
- [[#Product Cloud Service#Upgrade Plan|Upgrade Plan]]
- [[#Product Cloud Service#Unplanned Production Change|Unplanned Production Change]]
- [[#Product Cloud Service#Tenant Provision Services|Tenant Provision Services]]
- [[#Product Cloud Service#Product Trial Service|Product Trial Service]]
- [[#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Major Incident & RCA|Major Incident & RCA]]
- [[#Customer Cloud Service#Customer Order & Fulfillment Highlights|Customer Order & Fulfillment Highlights]]
- [[#Customer Cloud Service#Monthly SLA|Monthly SLA]]
- [[#Cloud DevOps/SRE|Cloud DevOps/SRE]]
- [[#Cloud DevOps/SRE#ITOM Operation Platform 25.1|ITOM Operation Platform 25.1]]
- [[#Cloud DevOps/SRE#ESM Cloud Application WAF Enablement|ESM Cloud Application WAF Enablement]]
- [[#Cloud DevOps/SRE#ESM GCP onboarding|ESM GCP onboarding]]
- [[#Cloud DevOps/SRE#Security & Compliance|Security & Compliance]]
- [[#Cloud DevOps/SRE#Cloud BI Reporting|Cloud BI Reporting]]
---
# Product Cloud Service
## Planned Maintenance Window Changes
- **ESM Standard Planned Changes**
- There were a total of **::22::** times of (SMX/CMS/OMT/OO, FedRAMP, DCA, ITOM Aviator) Upgrade/Patch/Hotfix deployments to various farms
- **ESM farms** (**::EU3/US7/US2/US24/US26::**) were upgraded to ESM latest major version **::ESM 25.1::** by the end of Jan, 2025
- **ITOM Operation Platform** **::25.1::** was upgraded on ESM farm (**::EU3/US24::**) by the end of Jan, 2025
- **ITOM Aviator Service** (**::EU30::**) was already upgraded to **::25.1::**
- All ESM Farm's **AWS EKS** version were upgraded to **::1.30::**
- FedRAMP **::AMI Rotation::** + **::RDS 15.4 +EKS Upgrade::** was done successfully in Jan maintenance window
![image.png](http://zipline.ishenwei.online/u/TpWM1Z.png)
![image.png](http://zipline.ishenwei.online/u/iJ088Q.png)
![image.png](http://zipline.ishenwei.online/u/WGaKk4.png)
![image.png](http://zipline.ishenwei.online/u/OYABda.png)
---
## Upgrade Plan
**ESM 25.1 Upgrade Plan - ::IN PROGRESS::**
![image.png](http://zipline.ishenwei.online/u/TgTE2M.png)
---
## Unplanned Production Change
- > Total **::26::** Unplanned Production Changes deployed to different ESM farms in this month
- > **::The number of additional unplanned change requests generated by product quality increased again this month. This needs to be taken seriously by the RnD team and analyzed after the fact to reduce similar problems and additional production changes.::**
![image.png](http://zipline.ishenwei.online/u/xXKQR1.png)
![image.png](http://zipline.ishenwei.online/u/WLQ91Z.png)
![image.png](http://zipline.ishenwei.online/u/cDcHDT.png)
---
## Tenant Provision Services
- > There were a total of **::17::** tenant provision request fulfilled in this month.
![image.png](http://zipline.ishenwei.online/u/Tu7f0F.png)
![image.png](http://zipline.ishenwei.online/u/9dqHZ3.png)
---
## Product Trial Service
- > There were a total of **::252::** product trial related service request fulfilled in this month.
![image.png](http://zipline.ishenwei.online/u/S5UkaH.png)
![image.png](http://zipline.ishenwei.online/u/LcnjR3.png)
![image.png](http://zipline.ishenwei.online/u/S0hBGT.png)
- > CT Trial (External Customer SMAX Only Trial) - 12
![image.png](http://zipline.ishenwei.online/u/DiZRD1.png)
- > Internal SMAX Premium Trial - 58
![image.png](http://zipline.ishenwei.online/u/UBvgzB.png)
- > HCMX Trial - 1
![image.png](http://zipline.ishenwei.online/u/VADMwI.png)
- > ESM (SMAX/AMX/CMS) Trial - 8
![image.png](http://zipline.ishenwei.online/u/2SCiiC.png)
- > **::ITOM Aviator Trial - 14::**
![image.png](http://zipline.ishenwei.online/u/BXkHWQ.png)
---
# Customer Cloud Service
## Customer Cloud Service
- > There were a total of **::84::** Customer Cloud Service Requests handled by ESM Cloud Service team in PCS
- > **::Special thanks to Remi's ESM Cloud RnD team for their cooperation in handling the customer's Cloud Service Request with technical support and staff coverage!::**
![image.png](http://zipline.ishenwei.online/u/eYDiqG.png)
![image.png](http://zipline.ishenwei.online/u/PPGtAi.png)
---
## Major Incident & RCA
> No major incident in Jan, 2025
---
## Customer Order & Fulfillment Highlights
![image.png](http://zipline.ishenwei.online/u/AqlcoY.png)
---
## Monthly SLA
- > **::ESM SMAX has achieved all 100% SLA in Dec, 2024::**
- > Data is available through **Dec, 2024**, with Jan, 2025 SLA data to be released around mid-Feb.
![http://zipline.ishenwei.online/u/HYziL8.png](http://zipline.ishenwei.online/u/HYziL8.png)
![image.png](http://zipline.ishenwei.online/u/CxrtGV.png)
---
# Cloud DevOps/SRE
## ITOM Operation Platform 25.1
- > We have successfully upgrade Operation Platform 24.4 to 25.1 on EU3/US24 farm.
- > We're preparing the Operation Platform 25.2 cloud readiness work include to start support OpsB to use OP 25.2 in ITOM Cloud environment.
- > We are now continuing to work on the OP D2 enablement automation and the relevant operation runbook.
## ESM Cloud Application WAF Enablement
- > After some series of team efforts, it went through several processes of testing, modification, and deployment. We have now reached the criteria of Enable WAF denied mode with current WAF version on top of SMAX 24.4.1.
We have enabled SMAX WAF in **Denied Mode** on **::EU3::** and **::US7::** farm by end of Nov.
- > In addition, we are gradually turning on **Observation Mode** in other farms to get more WAF log information to help us determine the effectiveness of blockings.
- > A new DevOps project was initiated to enhance WAF rules management & cloud deployment automation
## ESM GCP onboarding
- > Based on the business requirements, we started working with Cloud SA group and Product group on GCP onboarding for ESM. Currently, there are two main working threads which require product team support to certify ESM products deployment on GCP, and validation and experimentation for GCP OCF cloud architecture platform led by Cloud SA.
## Security & Compliance
- > At the request of Opentext GIS, the ESM Cloud Service Team has installed **Prisma Defender** on all ESM production farms in order to facilitate security scanning on the Cloud to provide more secure ESM SaaS services. - **::Done::**
- > We recently launched a new project on how to handle various OS-based security issues discovered by Qualys Scan. In conjunction with the upcoming adopt CCOE AMI project, we intend to centrally replace and update the existing Cloud Application's EKS worker node OS to meet higher security standards. - **::In Progress::**
## Cloud BI Reporting
- > ITOM ESM Farm/Tenant Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3243d84335d863ef318a?experience=power-bi)
- > ITOM Cloud Service Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/363a8aba-6746-4468-9d5c-54e0a463b708/ReportSectionc350f5d544676dc460b4?experience=power-bi)
##
---
---
title: ITOM ESM Cloud Service Monthly Report - Jan 2025
source:
author: shenwei
published:
created: 2025-03-02
description: This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
tags:
- Cloud
- Customer
- Product
---
# **ITOM ESM Cloud Service Monthly Report - Jan 2025**
**2025/1/1 ~ 2025/1/31**
This report contains the main work of the ESM Cloud Service team and shows the load of the team's work in the form of data, and describes some issues and risks for continuous improvement.
# Table of Content:
- [[#Product Cloud Service|Product Cloud Service]]
- [[#Product Cloud Service#Planned Maintenance Window Changes|Planned Maintenance Window Changes]]
- [[#Product Cloud Service#Upgrade Plan|Upgrade Plan]]
- [[#Product Cloud Service#Unplanned Production Change|Unplanned Production Change]]
- [[#Product Cloud Service#Tenant Provision Services|Tenant Provision Services]]
- [[#Product Cloud Service#Product Trial Service|Product Trial Service]]
- [[#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Customer Cloud Service|Customer Cloud Service]]
- [[#Customer Cloud Service#Major Incident & RCA|Major Incident & RCA]]
- [[#Customer Cloud Service#Customer Order & Fulfillment Highlights|Customer Order & Fulfillment Highlights]]
- [[#Customer Cloud Service#Monthly SLA|Monthly SLA]]
- [[#Cloud DevOps/SRE|Cloud DevOps/SRE]]
- [[#Cloud DevOps/SRE#ITOM Operation Platform 25.1|ITOM Operation Platform 25.1]]
- [[#Cloud DevOps/SRE#ESM Cloud Application WAF Enablement|ESM Cloud Application WAF Enablement]]
- [[#Cloud DevOps/SRE#ESM GCP onboarding|ESM GCP onboarding]]
- [[#Cloud DevOps/SRE#Security & Compliance|Security & Compliance]]
- [[#Cloud DevOps/SRE#Cloud BI Reporting|Cloud BI Reporting]]
---
# Product Cloud Service
## Planned Maintenance Window Changes
- **ESM Standard Planned Changes**
- There were a total of **::22::** times of (SMX/CMS/OMT/OO, FedRAMP, DCA, ITOM Aviator) Upgrade/Patch/Hotfix deployments to various farms
- **ESM farms** (**::EU3/US7/US2/US24/US26::**) were upgraded to ESM latest major version **::ESM 25.1::** by the end of Jan, 2025
- **ITOM Operation Platform** **::25.1::** was upgraded on ESM farm (**::EU3/US24::**) by the end of Jan, 2025
- **ITOM Aviator Service** (**::EU30::**) was already upgraded to **::25.1::**
- All ESM Farm's **AWS EKS** version were upgraded to **::1.30::**
- FedRAMP **::AMI Rotation::** + **::RDS 15.4 +EKS Upgrade::** was done successfully in Jan maintenance window
![image.png](http://zipline.ishenwei.online/u/TpWM1Z.png)
![image.png](http://zipline.ishenwei.online/u/iJ088Q.png)
![image.png](http://zipline.ishenwei.online/u/WGaKk4.png)
![image.png](http://zipline.ishenwei.online/u/OYABda.png)
---
## Upgrade Plan
**ESM 25.1 Upgrade Plan - ::IN PROGRESS::**
![image.png](http://zipline.ishenwei.online/u/TgTE2M.png)
---
## Unplanned Production Change
- > Total **::26::** Unplanned Production Changes deployed to different ESM farms in this month
- > **::The number of additional unplanned change requests generated by product quality increased again this month. This needs to be taken seriously by the RnD team and analyzed after the fact to reduce similar problems and additional production changes.::**
![image.png](http://zipline.ishenwei.online/u/xXKQR1.png)
![image.png](http://zipline.ishenwei.online/u/WLQ91Z.png)
![image.png](http://zipline.ishenwei.online/u/cDcHDT.png)
---
## Tenant Provision Services
- > There were a total of **::17::** tenant provision request fulfilled in this month.
![image.png](http://zipline.ishenwei.online/u/Tu7f0F.png)
![image.png](http://zipline.ishenwei.online/u/9dqHZ3.png)
---
## Product Trial Service
- > There were a total of **::252::** product trial related service request fulfilled in this month.
![image.png](http://zipline.ishenwei.online/u/S5UkaH.png)
![image.png](http://zipline.ishenwei.online/u/LcnjR3.png)
![image.png](http://zipline.ishenwei.online/u/S0hBGT.png)
- > CT Trial (External Customer SMAX Only Trial) - 12
![image.png](http://zipline.ishenwei.online/u/DiZRD1.png)
- > Internal SMAX Premium Trial - 58
![image.png](http://zipline.ishenwei.online/u/UBvgzB.png)
- > HCMX Trial - 1
![image.png](http://zipline.ishenwei.online/u/VADMwI.png)
- > ESM (SMAX/AMX/CMS) Trial - 8
![image.png](http://zipline.ishenwei.online/u/2SCiiC.png)
- > **::ITOM Aviator Trial - 14::**
![image.png](http://zipline.ishenwei.online/u/BXkHWQ.png)
---
# Customer Cloud Service
## Customer Cloud Service
- > There were a total of **::84::** Customer Cloud Service Requests handled by ESM Cloud Service team in PCS
- > **::Special thanks to Remi's ESM Cloud RnD team for their cooperation in handling the customer's Cloud Service Request with technical support and staff coverage!::**
![image.png](http://zipline.ishenwei.online/u/eYDiqG.png)
![image.png](http://zipline.ishenwei.online/u/PPGtAi.png)
---
## Major Incident & RCA
> No major incident in Jan, 2025
---
## Customer Order & Fulfillment Highlights
![image.png](http://zipline.ishenwei.online/u/AqlcoY.png)
---
## Monthly SLA
- > **::ESM SMAX has achieved all 100% SLA in Dec, 2024::**
- > Data is available through **Dec, 2024**, with Jan, 2025 SLA data to be released around mid-Feb.
![http://zipline.ishenwei.online/u/HYziL8.png](http://zipline.ishenwei.online/u/HYziL8.png)
![image.png](http://zipline.ishenwei.online/u/CxrtGV.png)
---
# Cloud DevOps/SRE
## ITOM Operation Platform 25.1
- > We have successfully upgrade Operation Platform 24.4 to 25.1 on EU3/US24 farm.
- > We're preparing the Operation Platform 25.2 cloud readiness work include to start support OpsB to use OP 25.2 in ITOM Cloud environment.
- > We are now continuing to work on the OP D2 enablement automation and the relevant operation runbook.
## ESM Cloud Application WAF Enablement
- > After some series of team efforts, it went through several processes of testing, modification, and deployment. We have now reached the criteria of Enable WAF denied mode with current WAF version on top of SMAX 24.4.1.
We have enabled SMAX WAF in **Denied Mode** on **::EU3::** and **::US7::** farm by end of Nov.
- > In addition, we are gradually turning on **Observation Mode** in other farms to get more WAF log information to help us determine the effectiveness of blockings.
- > A new DevOps project was initiated to enhance WAF rules management & cloud deployment automation
## ESM GCP onboarding
- > Based on the business requirements, we started working with Cloud SA group and Product group on GCP onboarding for ESM. Currently, there are two main working threads which require product team support to certify ESM products deployment on GCP, and validation and experimentation for GCP OCF cloud architecture platform led by Cloud SA.
## Security & Compliance
- > At the request of Opentext GIS, the ESM Cloud Service Team has installed **Prisma Defender** on all ESM production farms in order to facilitate security scanning on the Cloud to provide more secure ESM SaaS services. - **::Done::**
- > We recently launched a new project on how to handle various OS-based security issues discovered by Qualys Scan. In conjunction with the upcoming adopt CCOE AMI project, we intend to centrally replace and update the existing Cloud Application's EKS worker node OS to meet higher security standards. - **::In Progress::**
## Cloud BI Reporting
- > ITOM ESM Farm/Tenant Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/cf509ffe-325f-4c1b-a507-44b93e6d85ca/ReportSection3243d84335d863ef318a?experience=power-bi)
- > ITOM Cloud Service Summary - [BI Report Link](https://app.powerbi.com/groups/fac06a69-6340-4715-b8fe-4bdc0ca9af14/reports/363a8aba-6746-4468-9d5c-54e0a463b708/ReportSectionc350f5d544676dc460b4?experience=power-bi)
##
---

View File

@@ -1,200 +1,200 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Entitlement
- Create new company
- Customer Type: Customer
- Add new domain name (remember to add same in PCS dev as well, for dev2prod)
- Create new entitlement
Add new person
- Create in BO and sync back to SMAX
- Add company
- Add employee type - external
- Assign people to relevant entitlement
- Assign role
- Microfocus SaaS entitlement
- Self-Service portal app
- Self-Service portal user
PCS Administration Training
[[PCS Create New Customer Initial Setup Steps]]
KT Session #1
[https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EemsG7iwZ8xCsPm5QygCoZgBIWubxMNMhbCQU4AIhUoA3A](https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EemsG7iwZ8xCsPm5QygCoZgBIWubxMNMhbCQU4AIhUoA3A)
KT Session #2
[https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EXRCtXeBAXZPisy27-kVeuMBgZ_MdlyZgi-XmuXRIVseOg](https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EXRCtXeBAXZPisy27-kVeuMBgZ_MdlyZgi-XmuXRIVseOg)
**Data Model**
- Data domains
- Company
- Entitlement
- ENTITY_LINK to Company
- ENUM to Data domains
- Person
- ENTITY_LINK to Company
- ENTITY_LINK to Entitlement (default)
- MANY2MANY to Entitlement (full list)
**New Order/customer**
- Validate it is indeed a real customer (have an order ID, can find it in Control Tower)
- Special cases for internal "customers": presales, operations, etc
- Data domain record
- Very likely need to use a new "slot", unless the customer already exists
- Find the list, take the first entry not in use (with **ZZ-To be assigned** label)
- **VERY IMPORTANT: Execute the task in both Dev and Prod tenant. Make sure the Name field (key) is the same in both, otherwise Dev2Prod will have issues**
- The Prod tenant should be locked for customizations, you need to unlock it first. Don't forget to go back and lock it again after
- In rare cases, the customer may exist already if there is an entitlement for another product, in this case skip this step
- Company/Vendor record
- Add a record
- Use the official Display Name from the order/Control Tower
- We started using the names in caps, as this is how Sparks had them, let's keep the rule.
- For Code, use the first 3-4 letters of the name, or, if the company has an acronym, you can use it
- Important: make sure you select **Customer** for the **Company type**.
- In rare cases, the customer may exist already if there is an entitlement for another product, in this case skip this step
- Entitlement record
- Add a record
- Display label
- Is SaaS customer: start by short name of company, followed by product name and SaaS suffix, for example **Decathlon SMAX SaaS**
- If it is a "Powered by" partner, add the "Power By" suffix for the name, example **Rigosis SMAX SaaS Powered by**
- MF internal: Prefix with Micro Focus, no need to add SaaS suffix (it's implicit), for example **Micro Focus IT SMAX** or **Micro Focus Service Hub SMAX**.
- Important to fill in the correct information for the mandatory fields: Entitlement type, Product, Primary Domain, Company. Business rules use this information extensively to filter down what is available in drop down lists, decide what logic to execute
- For specific cases, like Achmea, if a NASE is assigned, fill in the info. Any new ticket linked to this entitlement gets automatically assigned to the person listed here
- Fill in the CSM info, if available. The user listed will be automatically added as follower to all Support requests and a subset of Service Requests
- SAID
- Leave empty for regular SaaS customers
- Fill in with the "Powered By" string for the Powered by partners
**New User**
- Non Micro Focus user (aka DB user)
- Triggered when
- A new customer is onboarded, and an initial list is received from the CSM
- An existing user on an entitlement uses the **Add user to entitlement** offering
- Check the user doesnt already exist
- If yes, go to the **Add user to an entitlement** section
- Create new record from the Person grid
- **VERY IMPORTANT:** Click the **Person type** checkbox, otherwise a contact gets created
- If you forgot, you need access to BO to create the user (with same UPN) and force a sync from the Person grid
- Upn: Use the email address (automatically populated with the email by default)
- Employment type: External
- Company: fill in with the relevant value
- **IMPORTANT:** Do not populate the **Data domains** and **Primary domain** at this point
- If the user is a "regular" customer user, you can fill in the **Default entitlement** value (for example, you may not want to do that if you add a partner)
- Once saved, go back in and populate the **Data domains** and **Primary domain**
- Entitlements M2M widget
- Unlikely to have to use it, unless it is a partner added the first time as part of a customer project. In this case the entitlement doesn't go into the default position.
- Micro Focus users (automatically added through SAML)
- Periodically check if any new users got added. This can happen because someone is supposed to be added to an entitlement or is supposed to work tickets, but in many situations, users get hold of our URL and click on it for curiosity
- R&D or Ops teams, people who logged in because they are supposed to work tickets
- Check their reporting structure and make sure they are who they say they are.
- Configure the following
- Company: Micro Focus
- Primary domain: Micro Focus
- Data domains: Micro Focus
- Employment type: blank
- You can manually add them to the relevant functional group if you know which one. Alternatively, there is an offering that does that, can be called by the authorized audience (anyone with the "PCS Lead" role)
- PS, pre-sales, specific functions
- If instructed, configure profile
- Company: Micro Focus
- Primary domain: Based on the main role
- Data domains: Above, plus potentially others, depending on the person responsibilities
- Employment type: External
- Example, if a PS consultant is asked to work on the MF IT project, the setup will be:
- Default entitlements/Entitlements
- With few exceptions (probably pre-sales), the majority of internal users needing to be added don't have a default entitlement. Add the relevant entitlement to the Entitlements M2M widget, if there is no default entitlement to be configured.
- All other cases, not known why a user connected
- Park the user in a "catch all" profile
- Company: Micro Focus
- Primary domain: Customer
- Data domains: Customer
- Employment type: External
- If later on, the user is identified as belonging to an actual entitlement or is supposed to be an agent, apply the instructions described above
- **IMPORTANT:** just because someone in Micro Focus asked for agent access, do not provide it, unless they are assigned to working tickets.
- Concrete example on when to **NOT** add someone. The user says he/she is a Premier support person or even a CSM and needs to see the tickets for their customers. The answer is to give them the **Account Viewer** role to have the information available from the portal - no need for agent access!
**Modify existing user profile**
- A user already exists in PCS but needs to be added to an additional entitlement
- Typically applies to users working for partners (with multiple customers) or internal Micro Focus people (PS, presales, etc)
- Triggered when
- A new customer is onboarded, and an initial list is received from the CSM
- An existing user on an entitlement uses the **Add user to entitlement** offering
- Data domains: append the customer Data domain (as defined in the Entitlement)
- Entitlements: add the new Entitlement to the M2M widget
- Exception
- An internal user may already exist because they connected before and ended up "parked" in the "Customer" placeholder
- Remove the "Customer" Data domains/Primary domains and follow the instructions listed in the "New user" section
**Remove user**
- Needs to be triggered from BO, which will convert the user into a contact
- Once a contact, change the status from **Inactive** to **Terminated**
- Clear the **Default entitlement** value and any record from the **Entitlements** section
**User Management offerings**
- **Add user to entitlement** offering
- If the email address is not a @microfocus.com domain, follow the instructions for adding a DB user
- **IMPORTANT:** If the email address doesn't appear to be the company email address, it is likely the user is a partner and may need to be treated specially, by first adding a new Company record for the partner. Alternatively, we can take the approach of considering this user as part of the customer company, and only model it as belonging to a new company when there is a need to add a second entitlement linked to the user. But, if we do it at a later date, it is important to identify all the users with this email domain and move them under the newly created Company record (see **New order/customer** section for the detailed procedure)
- If the email address is a @microfocus.com domain, follow the instructions for configuring a SAML user.
- It is possible the user doesn't already exist (never logged in). In this case, ask the requester to notify the user to first login (use the Discussions tab)
- Once the user record got created/updated, move the **Add user** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
- Exceptionally, if the request is not relevant (the user already exists, the offering incorrectly used, or other reasons), process the request as below:
- Edit the Task plan by deleting the **Closure** task, so the request doesn't automatically close when **Add user** task gets actioned
- Move the **Add user** task to **Canceled**
- Manually add a **Solution** (explain why the request is not relevant) and Completion code (usually **Out of scope**)
- **Remove user from entitlement** offering
- Tip: From the **User** user option, navigate to the Person record.
- If the user is part of multiple entitlements (like a partner, PS), just remove the relevant **Entitlement** reference from the Person record
- If the user is part of one entitlement (usually for a regular customer user), follow the procedure described in the **Remove user** section
- Once the relevant action above is completed, move the **Remove user** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
- **Reset user password** offering
- Tip: From the **User** user option, navigate to the Person record.
- Click on the **Reset password** button. Wait for the confirmation message at the top of the screen that the email notification got sent
- Once the relevant action above is completed, move the **Reset password** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
**Process**
- Monitor the "admin" Request queue
- Go to the Active Admin Cases" view. Make sure you don't make any changes to the view definition. If you like to use a different view, create a private one for yourself
- Add more people to the Admin group
- Only **Add user to entitlement**, **Remove user from entitlement, Reset user password** require intervention, the others have automated fulfillment (may require approval)
- See the **User Management offerings** section for instructions for processing each offering
- Monitor the Person grid for newly created SAML users
- Use the **Users Pending Processing** view. Make sure you don't make any changes to the view definition. If you like to use a different view, create a private one for yourself. Also, don't modify the other public views
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
Entitlement
- Create new company
- Customer Type: Customer
- Add new domain name (remember to add same in PCS dev as well, for dev2prod)
- Create new entitlement
Add new person
- Create in BO and sync back to SMAX
- Add company
- Add employee type - external
- Assign people to relevant entitlement
- Assign role
- Microfocus SaaS entitlement
- Self-Service portal app
- Self-Service portal user
PCS Administration Training
[[PCS Create New Customer Initial Setup Steps]]
KT Session #1
[https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EemsG7iwZ8xCsPm5QygCoZgBIWubxMNMhbCQU4AIhUoA3A](https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EemsG7iwZ8xCsPm5QygCoZgBIWubxMNMhbCQU4AIhUoA3A)
KT Session #2
[https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EXRCtXeBAXZPisy27-kVeuMBgZ_MdlyZgi-XmuXRIVseOg](https://microfocusinternational-my.sharepoint.com/:v:/g/personal/brindusa_kevorkian_microfocus_com/EXRCtXeBAXZPisy27-kVeuMBgZ_MdlyZgi-XmuXRIVseOg)
**Data Model**
- Data domains
- Company
- Entitlement
- ENTITY_LINK to Company
- ENUM to Data domains
- Person
- ENTITY_LINK to Company
- ENTITY_LINK to Entitlement (default)
- MANY2MANY to Entitlement (full list)
**New Order/customer**
- Validate it is indeed a real customer (have an order ID, can find it in Control Tower)
- Special cases for internal "customers": presales, operations, etc
- Data domain record
- Very likely need to use a new "slot", unless the customer already exists
- Find the list, take the first entry not in use (with **ZZ-To be assigned** label)
- **VERY IMPORTANT: Execute the task in both Dev and Prod tenant. Make sure the Name field (key) is the same in both, otherwise Dev2Prod will have issues**
- The Prod tenant should be locked for customizations, you need to unlock it first. Don't forget to go back and lock it again after
- In rare cases, the customer may exist already if there is an entitlement for another product, in this case skip this step
- Company/Vendor record
- Add a record
- Use the official Display Name from the order/Control Tower
- We started using the names in caps, as this is how Sparks had them, let's keep the rule.
- For Code, use the first 3-4 letters of the name, or, if the company has an acronym, you can use it
- Important: make sure you select **Customer** for the **Company type**.
- In rare cases, the customer may exist already if there is an entitlement for another product, in this case skip this step
- Entitlement record
- Add a record
- Display label
- Is SaaS customer: start by short name of company, followed by product name and SaaS suffix, for example **Decathlon SMAX SaaS**
- If it is a "Powered by" partner, add the "Power By" suffix for the name, example **Rigosis SMAX SaaS Powered by**
- MF internal: Prefix with Micro Focus, no need to add SaaS suffix (it's implicit), for example **Micro Focus IT SMAX** or **Micro Focus Service Hub SMAX**.
- Important to fill in the correct information for the mandatory fields: Entitlement type, Product, Primary Domain, Company. Business rules use this information extensively to filter down what is available in drop down lists, decide what logic to execute
- For specific cases, like Achmea, if a NASE is assigned, fill in the info. Any new ticket linked to this entitlement gets automatically assigned to the person listed here
- Fill in the CSM info, if available. The user listed will be automatically added as follower to all Support requests and a subset of Service Requests
- SAID
- Leave empty for regular SaaS customers
- Fill in with the "Powered By" string for the Powered by partners
**New User**
- Non Micro Focus user (aka DB user)
- Triggered when
- A new customer is onboarded, and an initial list is received from the CSM
- An existing user on an entitlement uses the **Add user to entitlement** offering
- Check the user doesnt already exist
- If yes, go to the **Add user to an entitlement** section
- Create new record from the Person grid
- **VERY IMPORTANT:** Click the **Person type** checkbox, otherwise a contact gets created
- If you forgot, you need access to BO to create the user (with same UPN) and force a sync from the Person grid
- Upn: Use the email address (automatically populated with the email by default)
- Employment type: External
- Company: fill in with the relevant value
- **IMPORTANT:** Do not populate the **Data domains** and **Primary domain** at this point
- If the user is a "regular" customer user, you can fill in the **Default entitlement** value (for example, you may not want to do that if you add a partner)
- Once saved, go back in and populate the **Data domains** and **Primary domain**
- Entitlements M2M widget
- Unlikely to have to use it, unless it is a partner added the first time as part of a customer project. In this case the entitlement doesn't go into the default position.
- Micro Focus users (automatically added through SAML)
- Periodically check if any new users got added. This can happen because someone is supposed to be added to an entitlement or is supposed to work tickets, but in many situations, users get hold of our URL and click on it for curiosity
- R&D or Ops teams, people who logged in because they are supposed to work tickets
- Check their reporting structure and make sure they are who they say they are.
- Configure the following
- Company: Micro Focus
- Primary domain: Micro Focus
- Data domains: Micro Focus
- Employment type: blank
- You can manually add them to the relevant functional group if you know which one. Alternatively, there is an offering that does that, can be called by the authorized audience (anyone with the "PCS Lead" role)
- PS, pre-sales, specific functions
- If instructed, configure profile
- Company: Micro Focus
- Primary domain: Based on the main role
- Data domains: Above, plus potentially others, depending on the person responsibilities
- Employment type: External
- Example, if a PS consultant is asked to work on the MF IT project, the setup will be:
- Default entitlements/Entitlements
- With few exceptions (probably pre-sales), the majority of internal users needing to be added don't have a default entitlement. Add the relevant entitlement to the Entitlements M2M widget, if there is no default entitlement to be configured.
- All other cases, not known why a user connected
- Park the user in a "catch all" profile
- Company: Micro Focus
- Primary domain: Customer
- Data domains: Customer
- Employment type: External
- If later on, the user is identified as belonging to an actual entitlement or is supposed to be an agent, apply the instructions described above
- **IMPORTANT:** just because someone in Micro Focus asked for agent access, do not provide it, unless they are assigned to working tickets.
- Concrete example on when to **NOT** add someone. The user says he/she is a Premier support person or even a CSM and needs to see the tickets for their customers. The answer is to give them the **Account Viewer** role to have the information available from the portal - no need for agent access!
**Modify existing user profile**
- A user already exists in PCS but needs to be added to an additional entitlement
- Typically applies to users working for partners (with multiple customers) or internal Micro Focus people (PS, presales, etc)
- Triggered when
- A new customer is onboarded, and an initial list is received from the CSM
- An existing user on an entitlement uses the **Add user to entitlement** offering
- Data domains: append the customer Data domain (as defined in the Entitlement)
- Entitlements: add the new Entitlement to the M2M widget
- Exception
- An internal user may already exist because they connected before and ended up "parked" in the "Customer" placeholder
- Remove the "Customer" Data domains/Primary domains and follow the instructions listed in the "New user" section
**Remove user**
- Needs to be triggered from BO, which will convert the user into a contact
- Once a contact, change the status from **Inactive** to **Terminated**
- Clear the **Default entitlement** value and any record from the **Entitlements** section
**User Management offerings**
- **Add user to entitlement** offering
- If the email address is not a @microfocus.com domain, follow the instructions for adding a DB user
- **IMPORTANT:** If the email address doesn't appear to be the company email address, it is likely the user is a partner and may need to be treated specially, by first adding a new Company record for the partner. Alternatively, we can take the approach of considering this user as part of the customer company, and only model it as belonging to a new company when there is a need to add a second entitlement linked to the user. But, if we do it at a later date, it is important to identify all the users with this email domain and move them under the newly created Company record (see **New order/customer** section for the detailed procedure)
- If the email address is a @microfocus.com domain, follow the instructions for configuring a SAML user.
- It is possible the user doesn't already exist (never logged in). In this case, ask the requester to notify the user to first login (use the Discussions tab)
- Once the user record got created/updated, move the **Add user** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
- Exceptionally, if the request is not relevant (the user already exists, the offering incorrectly used, or other reasons), process the request as below:
- Edit the Task plan by deleting the **Closure** task, so the request doesn't automatically close when **Add user** task gets actioned
- Move the **Add user** task to **Canceled**
- Manually add a **Solution** (explain why the request is not relevant) and Completion code (usually **Out of scope**)
- **Remove user from entitlement** offering
- Tip: From the **User** user option, navigate to the Person record.
- If the user is part of multiple entitlements (like a partner, PS), just remove the relevant **Entitlement** reference from the Person record
- If the user is part of one entitlement (usually for a regular customer user), follow the procedure described in the **Remove user** section
- Once the relevant action above is completed, move the **Remove user** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
- **Reset user password** offering
- Tip: From the **User** user option, navigate to the Person record.
- Click on the **Reset password** button. Wait for the confirmation message at the top of the screen that the email notification got sent
- Once the relevant action above is completed, move the **Reset password** task to **Validate** phase. The request should automatically close, there is no need for interaction with the requester.
**Process**
- Monitor the "admin" Request queue
- Go to the Active Admin Cases" view. Make sure you don't make any changes to the view definition. If you like to use a different view, create a private one for yourself
- Add more people to the Admin group
- Only **Add user to entitlement**, **Remove user from entitlement, Reset user password** require intervention, the others have automated fulfillment (may require approval)
- See the **User Management offerings** section for instructions for processing each offering
- Monitor the Person grid for newly created SAML users
- Use the **Users Pending Processing** view. Make sure you don't make any changes to the view definition. If you like to use a different view, create a private one for yourself. Also, don't modify the other public views
- Follow the instructions in the **New User** section

View File

@@ -1,23 +1,23 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
I'm a senior manager responsible for SaaS operations and maintenance for cloud products in a global enterprise software company. I manage a global cloud operations team of 20+ people. The team is a global team covering different time zones. Our team is also responsible for interfacing with the product RnD team to understand requirements and communicate with them. I am designing myself and my team's goals from the below perspective: Leadership Activities, service delivery quality, Process Standardization, Operational Excellence, and Satisfy customers. could you please prepare a draft for me with efforts detail, and measurable KPIs for the above areas.
| | | | | |
|---|---|---|---|---|
|**Leadership Activities**|• Conduct regular team meetings and one-on-one sessions to provide guidance, support, and alignment. <br>• <br>Act as a liaison between the cloud service team and ITOM organization, ensuring effective communication and collaboration. <br>• <br>Participate in the hiring process for new team members, ensuring the selection of qualified candidates who align with the team's goals and culture. <br>• <br>Foster a positive work environment conducive to productivity, innovation, and growth.|• Executive Dashboard: Implement an executive monthly report to track and communicate Cloud Service team key metrics and performance indicators. <br>• <br>100% Participation in Team meetings <br>• <br>100% Compliance in accordance with local legislation|||
|**Service Delivery Quality**|• Drive efficiencies and optimizations to deliver services in the most economical manner. <br>• Manage resource allocations effectively to meet business demands and maintain service levels. <br>• Continue to optimize cost structures across cloud and corporate services while maintaining service quality.|• On-time Project Completion: 90% <br>• <br>Ensure 100% compliance with local legislation and industry regulations relevant to cloud operations <br>• <br>|||
|**Process Standardization**|• Focus on standardization and modernization of processes, adopting best practices and industry standards such as ITIL. <br>• <br>Support initiatives to consolidate/migrate customers to cloud platforms, with a focus on shared environments where feasible. <br>• <br>Minimize process gaps and ensure zero deviations from defined processes. <br>• <br>Conduct quarterly reviews of documentation to ensure accuracy, relevance, and completeness. <br>• <br>|• Zero process gaps  <br>• <br>Quarterly review of process documentation <br> <br>|||
|**Operational Excellence**|• Aim for 90% of projects to be completed on time, ensuring timely delivery of services and solutions. <br>• <br>Continuously identify and implement improvements to existing processes and deliverables, enhancing operational efficiency and effectiveness. <br>• <br>Actively participate in the adoption of new features, technologies, and cloud solutions to improve operational capabilities. <br>• <br>Document and analyze lessons learned from project executions, incorporating feedback to drive continuous improvement. <br>• <br>| <br>• <br>60% of Project Completion within 3 months <br>• <br>100% Security Compliance <br>• <br>Expense Reduction Targets: Meet defined FY targets communicated by FinOps <br>• <br>Adoption of New Technologies: Regularly adopt new features/technologies to enhance operational capabilities <br> <br> <br> <br>|||
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
I'm a senior manager responsible for SaaS operations and maintenance for cloud products in a global enterprise software company. I manage a global cloud operations team of 20+ people. The team is a global team covering different time zones. Our team is also responsible for interfacing with the product RnD team to understand requirements and communicate with them. I am designing myself and my team's goals from the below perspective: Leadership Activities, service delivery quality, Process Standardization, Operational Excellence, and Satisfy customers. could you please prepare a draft for me with efforts detail, and measurable KPIs for the above areas.
| | | | | |
|---|---|---|---|---|
|**Leadership Activities**|• Conduct regular team meetings and one-on-one sessions to provide guidance, support, and alignment. <br>• <br>Act as a liaison between the cloud service team and ITOM organization, ensuring effective communication and collaboration. <br>• <br>Participate in the hiring process for new team members, ensuring the selection of qualified candidates who align with the team's goals and culture. <br>• <br>Foster a positive work environment conducive to productivity, innovation, and growth.|• Executive Dashboard: Implement an executive monthly report to track and communicate Cloud Service team key metrics and performance indicators. <br>• <br>100% Participation in Team meetings <br>• <br>100% Compliance in accordance with local legislation|||
|**Service Delivery Quality**|• Drive efficiencies and optimizations to deliver services in the most economical manner. <br>• Manage resource allocations effectively to meet business demands and maintain service levels. <br>• Continue to optimize cost structures across cloud and corporate services while maintaining service quality.|• On-time Project Completion: 90% <br>• <br>Ensure 100% compliance with local legislation and industry regulations relevant to cloud operations <br>• <br>|||
|**Process Standardization**|• Focus on standardization and modernization of processes, adopting best practices and industry standards such as ITIL. <br>• <br>Support initiatives to consolidate/migrate customers to cloud platforms, with a focus on shared environments where feasible. <br>• <br>Minimize process gaps and ensure zero deviations from defined processes. <br>• <br>Conduct quarterly reviews of documentation to ensure accuracy, relevance, and completeness. <br>• <br>|• Zero process gaps  <br>• <br>Quarterly review of process documentation <br> <br>|||
|**Operational Excellence**|• Aim for 90% of projects to be completed on time, ensuring timely delivery of services and solutions. <br>• <br>Continuously identify and implement improvements to existing processes and deliverables, enhancing operational efficiency and effectiveness. <br>• <br>Actively participate in the adoption of new features, technologies, and cloud solutions to improve operational capabilities. <br>• <br>Document and analyze lessons learned from project executions, incorporating feedback to drive continuous improvement. <br>• <br>| <br>• <br>60% of Project Completion within 3 months <br>• <br>100% Security Compliance <br>• <br>Expense Reduction Targets: Meet defined FY targets communicated by FinOps <br>• <br>Adoption of New Technologies: Regularly adopt new features/technologies to enhance operational capabilities <br> <br> <br> <br>|||
|**Satisfy Customers**|• Be an advocate for customer satisfaction, focusing on delivering value and exceeding customer expectations in every project and interaction. <br>• <br>Collaborate with cross-functional teams and stakeholders to support business goals and initiatives, such as migrations and security compliance efforts. <br>• <br>Ensure documentation of any planned/unplanned change predefined timeframes to maintain transparency and accountability. <br>• <br>Participate in the onboarding of new customers to the cloud business, ensuring smooth transitions and successful deployments. <br>• <br>|• customer escalations due to cloud service errors <3|||

View File

@@ -1,66 +1,66 @@
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## **1. Objective**
Ensure business continuity and data protection by implementing an effective DR strategy for the customer, leveraging EFS replication, RDS PITR, and different failover methods.
## **2. DR Scenarios & Recovery Options**
| | **Method** | **RDS Recovery** | **EFS Recovery** | **Failover Steps** | **Estimated Downtime (RTO)** | **RPO** | **Cost Impact** |
| ------------------ | ------------------------------- | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | ------- | --------------- |
| DR Basic Service | **Cold Backup-Restore** | Snapshot (6h) | Backup Restore (6h) | 1. Restore RDS from snapshot (6h) <br>2. Restore EFS from snapshot (6h) <br>3. Recover EKS (4h) | **24 hours** | 4 hours | **Base Cost** |
| DR Premium Service | **EFS Replica Only (RDS PITR)** | PITR (6h) | EFS Replica + Restore (0.2h) | 1. RDS recovery from PITR (6h) <br>2. Stop EFS sync (0.2h) <br>3. Full EKS recovery | **6 hours** | 15 min | **+30% Cost** |
---
## **3. Downtime Estimation & RTO Considerations**
- **EFS Replica Only (RDS PITR)**
- **6-hour RTO**, significantly reducing downtime compared to cold restore.
- **15-minute RPO** ensures minimal data loss.
---
## **4. DR Execution Plan**
### **4.1 Pre-DR Readiness Checks**
- Ensure **EFS replication** is active and functioning correctly.
- Verify **RDS PITR backups** and retention policies.
- Pre-configure **EKS deployment templates(Velero)** for rapid recovery.
### **4.2 Disaster Recovery Trigger**
- DR activation is **initiated upon a major failure event** in the primary environment.
- Decision criteria include **regional failure, prolonged service outage, or severe data corruption**.
### **4.3 Execution Steps**
#### **EFS Replica Only (RDS PITR)**
1. **Recover RDS** from PITR (**6 hours**).
2. **Stop EFS replication sync** (**0.2 hours**).
3. **Recover EKS cluster** and validate application (**immediate**).
### **4.4 Post-Failover Validation**
- Confirm **data consistency** between DR and primary environments.
- Validate **application services and connectivity**.
- Communicate DR activation and service restoration to stakeholders.
---
## **5. DR Testing & Cost Estimation**
- **Annual DR validation test** is required, adding an **estimated 2 months of AWS costs**.
- **EFS Replica Only (RDS PITR):**
- **$20.8K/month**
---
title:
source:
author: shenwei
published:
created:
description:
tags: []
---
## **1. Objective**
Ensure business continuity and data protection by implementing an effective DR strategy for the customer, leveraging EFS replication, RDS PITR, and different failover methods.
## **2. DR Scenarios & Recovery Options**
| | **Method** | **RDS Recovery** | **EFS Recovery** | **Failover Steps** | **Estimated Downtime (RTO)** | **RPO** | **Cost Impact** |
| ------------------ | ------------------------------- | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | ------- | --------------- |
| DR Basic Service | **Cold Backup-Restore** | Snapshot (6h) | Backup Restore (6h) | 1. Restore RDS from snapshot (6h) <br>2. Restore EFS from snapshot (6h) <br>3. Recover EKS (4h) | **24 hours** | 4 hours | **Base Cost** |
| DR Premium Service | **EFS Replica Only (RDS PITR)** | PITR (6h) | EFS Replica + Restore (0.2h) | 1. RDS recovery from PITR (6h) <br>2. Stop EFS sync (0.2h) <br>3. Full EKS recovery | **6 hours** | 15 min | **+30% Cost** |
---
## **3. Downtime Estimation & RTO Considerations**
- **EFS Replica Only (RDS PITR)**
- **6-hour RTO**, significantly reducing downtime compared to cold restore.
- **15-minute RPO** ensures minimal data loss.
---
## **4. DR Execution Plan**
### **4.1 Pre-DR Readiness Checks**
- Ensure **EFS replication** is active and functioning correctly.
- Verify **RDS PITR backups** and retention policies.
- Pre-configure **EKS deployment templates(Velero)** for rapid recovery.
### **4.2 Disaster Recovery Trigger**
- DR activation is **initiated upon a major failure event** in the primary environment.
- Decision criteria include **regional failure, prolonged service outage, or severe data corruption**.
### **4.3 Execution Steps**
#### **EFS Replica Only (RDS PITR)**
1. **Recover RDS** from PITR (**6 hours**).
2. **Stop EFS replication sync** (**0.2 hours**).
3. **Recover EKS cluster** and validate application (**immediate**).
### **4.4 Post-Failover Validation**
- Confirm **data consistency** between DR and primary environments.
- Validate **application services and connectivity**.
- Communicate DR activation and service restoration to stakeholders.
---
## **5. DR Testing & Cost Estimation**
- **Annual DR validation test** is required, adding an **estimated 2 months of AWS costs**.
- **EFS Replica Only (RDS PITR):**
- **$20.8K/month**

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 KiB

View File

@@ -1,355 +0,0 @@
---
title: "hermes-agent/website/docs/user-guide/features/api-server.md at main"
source: "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/api-server.md"
author:
published:
created: 2026-04-20
description: "The agent that grows with you. Contribute to NousResearch/hermes-agent development by creating an account on GitHub."
tags:
- "clippings"
---
| sidebar\_position | 14 |
| ----------------- | ---------------------------------------------------------------- |
| title | API Server |
| description | Expose hermes-agent as an OpenAI-compatible API for any frontend |
## API Server
The API server exposes hermes-agent as an OpenAI-compatible HTTP endpoint. Any frontend that speaks the OpenAI format — Open WebUI, LobeChat, LibreChat, NextChat, ChatBox, and hundreds more — can connect to hermes-agent and use it as a backend.
Your agent handles requests with its full toolset (terminal, file operations, web search, memory, skills) and returns the final response. When streaming, tool progress indicators appear inline so frontends can show what the agent is doing.
## Quick Start
### 1\. Enable the API server
Add to `~/.hermes/.env`:
```
API_SERVER_ENABLED=true
API_SERVER_KEY=change-me-local-dev
# Optional: only if a browser must call Hermes directly
# API_SERVER_CORS_ORIGINS=http://localhost:3000
```
### 2\. Start the gateway
```
hermes gateway
```
You'll see:
```
[API Server] API server listening on http://127.0.0.1:8642
```
### 3\. Connect a frontend
Point any OpenAI-compatible client at `http://localhost:8642/v1`:
```
# Test with curl
curl http://localhost:8642/v1/chat/completions \
-H "Authorization: Bearer change-me-local-dev" \
-H "Content-Type: application/json" \
-d '{"model": "hermes-agent", "messages": [{"role": "user", "content": "Hello!"}]}'
```
Or connect Open WebUI, LobeChat, or any other frontend — see the [Open WebUI integration guide](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/messaging/open-webui) for step-by-step instructions.
## Endpoints
### POST /v1/chat/completions
Standard OpenAI Chat Completions format. Stateless — the full conversation is included in each request via the `messages` array.
**Request:**
```
{
"model": "hermes-agent",
"messages": [
{"role": "system", "content": "You are a Python expert."},
{"role": "user", "content": "Write a fibonacci function"}
],
"stream": false
}
```
**Response:**
```
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1710000000,
"model": "hermes-agent",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Here's a fibonacci function..."},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 50, "completion_tokens": 200, "total_tokens": 250}
}
```
**Streaming** (`"stream": true`): Returns Server-Sent Events (SSE) with token-by-token response chunks. For **Chat Completions**, the stream uses standard `chat.completion.chunk` events plus Hermes' custom `hermes.tool.progress` event for tool-start UX. For **Responses**, the stream uses OpenAI Responses event types such as `response.created`, `response.output_text.delta`, `response.output_item.added`, `response.output_item.done`, and `response.completed`.
**Tool progress in streams**:
- **Chat Completions**: Hermes emits `event: hermes.tool.progress` for tool-start visibility without polluting persisted assistant text.
- **Responses**: Hermes emits spec-native `function_call` and `function_call_output` output items during the SSE stream, so clients can render structured tool UI in real time.
### POST /v1/responses
OpenAI Responses API format. Supports server-side conversation state via `previous_response_id` — the server stores full conversation history (including tool calls and results) so multi-turn context is preserved without the client managing it.
**Request:**
```
{
"model": "hermes-agent",
"input": "What files are in my project?",
"instructions": "You are a helpful coding assistant.",
"store": true
}
```
**Response:**
```
{
"id": "resp_abc123",
"object": "response",
"status": "completed",
"model": "hermes-agent",
"output": [
{"type": "function_call", "name": "terminal", "arguments": "{\"command\": \"ls\"}", "call_id": "call_1"},
{"type": "function_call_output", "call_id": "call_1", "output": "README.md src/ tests/"},
{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Your project has..."}]}
],
"usage": {"input_tokens": 50, "output_tokens": 200, "total_tokens": 250}
}
```
#### Multi-turn with previous\_response\_id
Chain responses to maintain full context (including tool calls) across turns:
```
{
"input": "Now show me the README",
"previous_response_id": "resp_abc123"
}
```
The server reconstructs the full conversation from the stored response chain — all previous tool calls and results are preserved. Chained requests also share the same session, so multi-turn conversations appear as a single entry in the dashboard and session history.
#### Named conversations
Use the `conversation` parameter instead of tracking response IDs:
```
{"input": "Hello", "conversation": "my-project"}
{"input": "What's in src/?", "conversation": "my-project"}
{"input": "Run the tests", "conversation": "my-project"}
```
The server automatically chains to the latest response in that conversation. Like the `/title` command for gateway sessions.
### GET /v1/responses/{id}
Retrieve a previously stored response by ID.
### DELETE /v1/responses/{id}
Delete a stored response.
### GET /v1/models
Lists the agent as an available model. The advertised model name defaults to the [profile](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/profiles) name (or `hermes-agent` for the default profile). Required by most frontends for model discovery.
### GET /health
Health check. Returns `{"status": "ok"}`. Also available at **GET /v1/health** for OpenAI-compatible clients that expect the `/v1/` prefix.
### GET /health/detailed
Extended health check that also reports active sessions, running agents, and resource usage. Useful for monitoring/observability tooling.
## Runs API (streaming-friendly alternative)
In addition to `/v1/chat/completions` and `/v1/responses`, the server exposes a **runs** API for long-form sessions where the client wants to subscribe to progress events instead of managing streaming themselves.
### POST /v1/runs
Create a new agent run. Returns a `run_id` that can be used to subscribe to progress events.
### GET /v1/runs/{run\_id}/events
Server-Sent Events stream of the run's tool-call progress, token deltas, and lifecycle events. Designed for dashboards and thick clients that want to attach/detach without losing state.
## Jobs API (background scheduled work)
The server exposes a lightweight jobs CRUD surface for managing scheduled / background agent runs from a remote client. All endpoints are gated behind the same bearer auth.
### GET /api/jobs
List all scheduled jobs.
### POST /api/jobs
Create a new scheduled job. Body accepts the same shape as `hermes cron` — prompt, schedule, skills, provider override, delivery target.
### GET /api/jobs/{job\_id}
Fetch a single job's definition and last-run state.
### PATCH /api/jobs/{job\_id}
Update fields on an existing job (prompt, schedule, etc.). Partial updates are merged.
### DELETE /api/jobs/{job\_id}
Remove a job. Also cancels any in-flight run.
### POST /api/jobs/{job\_id}/pause
Pause a job without deleting it. Next-scheduled-run timestamps are suspended until resumed.
### POST /api/jobs/{job\_id}/resume
Resume a previously paused job.
### POST /api/jobs/{job\_id}/run
Trigger the job to run immediately, out of schedule.
## System Prompt Handling
When a frontend sends a `system` message (Chat Completions) or `instructions` field (Responses API), hermes-agent **layers it on top** of its core system prompt. Your agent keeps all its tools, memory, and skills — the frontend's system prompt adds extra instructions.
This means you can customize behavior per-frontend without losing capabilities:
- Open WebUI system prompt: "You are a Python expert. Always include type hints."
- The agent still has terminal, file tools, web search, memory, etc.
## Authentication
Bearer token auth via the `Authorization` header:
```
Authorization: Bearer ***
```
Configure the key via `API_SERVER_KEY` env var. If you need a browser to call Hermes directly, also set `API_SERVER_CORS_ORIGINS` to an explicit allowlist.
:::warning Security The API server gives full access to hermes-agent's toolset, **including terminal commands**. When binding to a non-loopback address like `0.0.0.0`, `API_SERVER_KEY` is **required**. Also keep `API_SERVER_CORS_ORIGINS` narrow to control browser access.
The default bind address (`127.0.0.1`) is for local-only use. Browser access is disabled by default; enable it only for explicit trusted origins.:::
## Configuration
### Environment Variables
| Variable | Default | Description |
| --- | --- | --- |
| `API_SERVER_ENABLED` | `false` | Enable the API server |
| `API_SERVER_PORT` | `8642` | HTTP server port |
| `API_SERVER_HOST` | `127.0.0.1` | Bind address (localhost only by default) |
| `API_SERVER_KEY` | *(none)* | Bearer token for auth |
| `API_SERVER_CORS_ORIGINS` | *(none)* | Comma-separated allowed browser origins |
| `API_SERVER_MODEL_NAME` | *(profile name)* | Model name on `/v1/models`. Defaults to profile name, or `hermes-agent` for default profile. |
### config.yaml
```
# Not yet supported — use environment variables.
# config.yaml support coming in a future release.
```
## Security Headers
All responses include security headers:
- `X-Content-Type-Options: nosniff` — prevents MIME type sniffing
- `Referrer-Policy: no-referrer` — prevents referrer leakage
## CORS
The API server does **not** enable browser CORS by default.
For direct browser access, set an explicit allowlist:
```
API_SERVER_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
```
When CORS is enabled:
- **Preflight responses** include `Access-Control-Max-Age: 600` (10 minute cache)
- **SSE streaming responses** include CORS headers so browser EventSource clients work correctly
- **`Idempotency-Key`** is an allowed request header — clients can send it for deduplication (responses are cached by key for 5 minutes)
Most documented frontends such as Open WebUI connect server-to-server and do not need CORS at all.
## Compatible Frontends
Any frontend that supports the OpenAI API format works. Tested/documented integrations:
| Frontend | Stars | Connection |
| --- | --- | --- |
| [Open WebUI](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/messaging/open-webui) | 126k | Full guide available |
| LobeChat | 73k | Custom provider endpoint |
| LibreChat | 34k | Custom endpoint in librechat.yaml |
| AnythingLLM | 56k | Generic OpenAI provider |
| NextChat | 87k | BASE\_URL env var |
| ChatBox | 39k | API Host setting |
| Jan | 26k | Remote model config |
| HF Chat-UI | 8k | OPENAI\_BASE\_URL |
| big-AGI | 7k | Custom endpoint |
| OpenAI Python SDK | — | `OpenAI(base_url="http://localhost:8642/v1")` |
| curl | — | Direct HTTP requests |
## Multi-User Setup with Profiles
To give multiple users their own isolated Hermes instance (separate config, memory, skills), use [profiles](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/profiles):
```
# Create a profile per user
hermes profile create alice
hermes profile create bob
# Configure each profile's API server on a different port
hermes -p alice config set API_SERVER_ENABLED true
hermes -p alice config set API_SERVER_PORT 8643
hermes -p alice config set API_SERVER_KEY alice-secret
hermes -p bob config set API_SERVER_ENABLED true
hermes -p bob config set API_SERVER_PORT 8644
hermes -p bob config set API_SERVER_KEY bob-secret
# Start each profile's gateway
hermes -p alice gateway &
hermes -p bob gateway &
```
Each profile's API server automatically advertises the profile name as the model ID:
- `http://localhost:8643/v1/models` → model `alice`
- `http://localhost:8644/v1/models` → model `bob`
In Open WebUI, add each as a separate connection. The model dropdown shows `alice` and `bob` as distinct models, each backed by a fully isolated Hermes instance. See the [Open WebUI guide](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/messaging/open-webui#multi-user-setup-with-profiles) for details.
## Limitations
- **Response storage** — stored responses (for `previous_response_id`) are persisted in SQLite and survive gateway restarts. Max 100 stored responses (LRU eviction).
- **No file upload** — vision/document analysis via uploaded files is not yet supported through the API.
- **Model field is cosmetic** — the `model` field in requests is accepted but the actual LLM model used is configured server-side in config.yaml.
## Proxy Mode
The API server also serves as the backend for **gateway proxy mode**. When another Hermes gateway instance is configured with `GATEWAY_PROXY_URL` pointing at this API server, it forwards all messages here instead of running its own agent. This enables split deployments — for example, a Docker container handling Matrix E2EE that relays to a host-side agent.
See [Matrix Proxy Mode](https://github.com/NousResearch/hermes-agent/blob/main/docs/user-guide/messaging/matrix#proxy-mode-e2ee-on-macos) for the full setup guide.

View File

@@ -1,114 +1,114 @@
# Hermes 自定义技能说明
> 创建日期2026-04-20
> 更新日期2026-04-20
---
## 技能总览
| 技能名 | 用途 | 调用方式 |
| --------------------------------- | --------------------------------------------- | ------------- |
| `blogwatcher-daily` | RSS 订阅监控 + 每日笔记生成 | cronjob |
| `claude-code-executor` | 用 TMUX 启动 Claude Code 并委托任务 | delegate_task |
| `claude-code-infographic-prompts` | 调用 Claude Code 的 baoyu-infographic 生成信息图提示词 | 手动调用 |
| `marker-pdf-to-markdown` | PDF 转 Markdownmarker_single 单文件 / marker 批量) | terminal |
| `whisper-audio-to-text` | 音频/视频转文字Whisper | terminal |
---
## 1. claude-code-executor
**路径**`~/.hermes/skills/custom/claude-code-executor/SKILL.md`
**用途**:通过 TMUX 启动 Claude CodebypassPermissions 模式),发送任务指令,监控完成状态。
**触发关键词**:用户说"请用 Claude Code 做 xxx"、"用 Claude Code 执行 xxx"
**核心流程**
1. 启动 TMUX session `claude-task`
2. 等 8 秒后发送 EnterbypassPermissions 确认)
3. 发送完整任务指令
4. 监控 `done:` 输出
5. 清理 tmux session
**SSH 到 ubuntu2 命令**`ssh ubuntu2`
---
## 2. claude-code-infographic-build
**路径**`~/.hermes/skills/custom/claude-code-infographic-build/SKILL.md`
**用途**:调用 Claude Code 的 `baoyu-infographic` 技能,为笔记内容生成信息图提示词,并进一步生成图片添加到原始笔记。
**完整三步流程**
1. **步骤①**baoyu-infographic 生成三部分提示词 → 保存到 Hermes/xingzhi/
2. **步骤②**:在同一 Claude Code session 中baoyu-imagine 生成图片 → 保存到 Hermes/xingzhi/
3. **步骤③**obsidian skill 将图片引用添加到原始笔记
**三部分提示词格式**
1. **系统提示词** — Image Specifications + Core Principles
2. **风格锁定提示词** — Layout Guidelines + Style Guidelines
3. **内容结构提示词** — Text Requirements + 具体内容
**布局参考**`~/.claude/skills/baoyu-infographic/references/layouts/`
- `hub-spoke` — 适合 mind-map标注了 "Best For: Mind maps"
- `circular-flow` — 适合循环流程
- `venn` — 适合交集关系
**风格参考**`~/.claude/skills/baoyu-infographic/references/styles/`
- `corporate-memphis` — 商务孟菲斯风格
- `chalkboard` — 粉笔黑板风格
- `cyberpunk-neon` — 赛博霓虹风格
- `warm` / `notion` / `minimal` / `blueprint` / `watercolor` / `elegant`
**文件命名**`[主题]-infographic-prompts-YYYY-MM-DD.md`
---
## 3. blogwatcher-daily
**路径**`~/.hermes/skills/custom/blogwatcher-daily/SKILL.md`
**用途**RSS 订阅监控 + 每日笔记生成。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite新文章追加写入 Markdown 笔记。
---
## 4. marker-pdf-to-markdown
**路径**`~/.hermes/skills/custom/marker-pdf-to-markdown/SKILL.md`
**用途**PDF 转 Markdown/HTML/JSON 高精度工具,支持 OCR、表格、公式识别。
**安装位置**ubuntu2 服务器(`ssh ubuntu2`
| 命令 | 用途 | 输入 |
|------|------|------|
| `marker_single` | 单文件转换,默认输出到 PDF 同目录 | 单个 PDF 文件 |
| `marker` | 批量转换,需文件夹输入 | 文件夹路径 |
常用选项:`--output_dir``--output_format json/markdown/html``--no-images``--use_llm``--page_range`
---
## 5. whisper-audio-to-text
**路径**`~/.hermes/skills/custom/whisper-audio-to-text/SKILL.md`
**用途**:使用 OpenAI Whisper 将音频/视频转换为文字(转录或翻译)。
**安装位置**Mac miniGPU 加速、ubuntu1、ubuntu2CPU 模式),均直接可用。
---
## 相关 Claude Code 技能(`~/.claude/skills/`
| 技能名 | 用途 |
|--------|------|
| `baoyu-article-illustrator` | 文章配图(分析文章结构 + 生成配图) |
| `baoyu-infographic` | 信息图生成 |
| `baoyu-cover-image` | 封面图生成 |
| `fireworks-tech-graph` | 技术图生成 |
这些 Claude Code 技能通过 `claude-code-executor` 启动 Claude Code CLI 后调用。
# Hermes 自定义技能说明
> 创建日期2026-04-20
> 更新日期2026-04-20
---
## 技能总览
| 技能名 | 用途 | 调用方式 |
| --------------------------------- | --------------------------------------------- | ------------- |
| `blogwatcher-daily` | RSS 订阅监控 + 每日笔记生成 | cronjob |
| `claude-code-executor` | 用 TMUX 启动 Claude Code 并委托任务 | delegate_task |
| `claude-code-infographic-prompts` | 调用 Claude Code 的 baoyu-infographic 生成信息图提示词 | 手动调用 |
| `marker-pdf-to-markdown` | PDF 转 Markdownmarker_single 单文件 / marker 批量) | terminal |
| `whisper-audio-to-text` | 音频/视频转文字Whisper | terminal |
---
## 1. claude-code-executor
**路径**`~/.hermes/skills/custom/claude-code-executor/SKILL.md`
**用途**:通过 TMUX 启动 Claude CodebypassPermissions 模式),发送任务指令,监控完成状态。
**触发关键词**:用户说"请用 Claude Code 做 xxx"、"用 Claude Code 执行 xxx"
**核心流程**
1. 启动 TMUX session `claude-task`
2. 等 8 秒后发送 EnterbypassPermissions 确认)
3. 发送完整任务指令
4. 监控 `done:` 输出
5. 清理 tmux session
**SSH 到 ubuntu2 命令**`ssh ubuntu2`
---
## 2. claude-code-infographic-build
**路径**`~/.hermes/skills/custom/claude-code-infographic-build/SKILL.md`
**用途**:调用 Claude Code 的 `baoyu-infographic` 技能,为笔记内容生成信息图提示词,并进一步生成图片添加到原始笔记。
**完整三步流程**
1. **步骤①**baoyu-infographic 生成三部分提示词 → 保存到 Hermes/xingzhi/
2. **步骤②**:在同一 Claude Code session 中baoyu-imagine 生成图片 → 保存到 Hermes/xingzhi/
3. **步骤③**obsidian skill 将图片引用添加到原始笔记
**三部分提示词格式**
1. **系统提示词** — Image Specifications + Core Principles
2. **风格锁定提示词** — Layout Guidelines + Style Guidelines
3. **内容结构提示词** — Text Requirements + 具体内容
**布局参考**`~/.claude/skills/baoyu-infographic/references/layouts/`
- `hub-spoke` — 适合 mind-map标注了 "Best For: Mind maps"
- `circular-flow` — 适合循环流程
- `venn` — 适合交集关系
**风格参考**`~/.claude/skills/baoyu-infographic/references/styles/`
- `corporate-memphis` — 商务孟菲斯风格
- `chalkboard` — 粉笔黑板风格
- `cyberpunk-neon` — 赛博霓虹风格
- `warm` / `notion` / `minimal` / `blueprint` / `watercolor` / `elegant`
**文件命名**`[主题]-infographic-prompts-YYYY-MM-DD.md`
---
## 3. blogwatcher-daily
**路径**`~/.hermes/skills/custom/blogwatcher-daily/SKILL.md`
**用途**RSS 订阅监控 + 每日笔记生成。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite新文章追加写入 Markdown 笔记。
---
## 4. marker-pdf-to-markdown
**路径**`~/.hermes/skills/custom/marker-pdf-to-markdown/SKILL.md`
**用途**PDF 转 Markdown/HTML/JSON 高精度工具,支持 OCR、表格、公式识别。
**安装位置**ubuntu2 服务器(`ssh ubuntu2`
| 命令 | 用途 | 输入 |
|------|------|------|
| `marker_single` | 单文件转换,默认输出到 PDF 同目录 | 单个 PDF 文件 |
| `marker` | 批量转换,需文件夹输入 | 文件夹路径 |
常用选项:`--output_dir``--output_format json/markdown/html``--no-images``--use_llm``--page_range`
---
## 5. whisper-audio-to-text
**路径**`~/.hermes/skills/custom/whisper-audio-to-text/SKILL.md`
**用途**:使用 OpenAI Whisper 将音频/视频转换为文字(转录或翻译)。
**安装位置**Mac miniGPU 加速、ubuntu1、ubuntu2CPU 模式),均直接可用。
---
## 相关 Claude Code 技能(`~/.claude/skills/`
| 技能名 | 用途 |
|--------|------|
| `baoyu-article-illustrator` | 文章配图(分析文章结构 + 生成配图) |
| `baoyu-infographic` | 信息图生成 |
| `baoyu-cover-image` | 封面图生成 |
| `fireworks-tech-graph` | 技术图生成 |
这些 Claude Code 技能通过 `claude-code-executor` 启动 Claude Code CLI 后调用。

View File

@@ -1,169 +1,169 @@
# Chrome DevTools MCP - Infographic Prompts
Generated: 2026-04-20
Layout: circular-flow
Style: chalkboard
Aspect: 16:9
---
## Part 1: System Prompt
```
Create a professional infographic with the following specifications:
## Image Specifications
- Type: Infographic
- Layout: circular-flow (cyclic process showing continuous steps arranged in a circle)
- Style: chalkboard (chalk on black board aesthetic)
- Aspect Ratio: 16:9
- Language: English
## Core Principles
- Follow the circular-flow layout structure: arrange information nodes in a circle with directional arrows
- Each section represents a tool category as a node on the circle
- Use chalk-drawn style: imperfect hand-drawn lines, chalk dust effects, white/yellow/pink/blue chalk colors
- Black chalkboard background (#1A1A1A)
- Center can hold the main concept "Chrome DevTools MCP"
- Maintain authentic chalk texture on all elements
- Use circular arrangement to show the tool workflow cycle
- Clear visual hierarchy with color variety
## Text Requirements
- Main titles prominent and readable in chalk white
- Key concepts emphasized with chalk yellow/pink
- Labels clear and appropriately sized
```
---
## Part 2: Style Locking Prompt
```
## Style Guidelines - chalkboard
### Background
- Color: Chalkboard Black (#1A1A1A) or Dark Green-Black (#1C2B1C)
- Texture: Realistic chalkboard texture with subtle scratches, dust particles
### Typography
- Hand-drawn chalk lettering with visible chalk texture
- White or bright colored chalk for emphasis
### Color Palette
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - highlights, emphasis
- Accent 2: #FF9999 (Chalk Pink) - secondary highlights
- Accent 3: #66B3FF (Chalk Blue) - links, connections
- Accent 4: #90EE90 (Chalk Green) - success indicators
### Visual Elements
- Hand-drawn chalk illustrations with sketchy, imperfect lines
- Chalk dust effects around text and key elements
- Doodles: stars, arrows, underlines, circles
- Connection lines with hand-drawn feel
- Directional arrows showing cycle flow
### Do
- Maintain authentic chalk texture on all elements
- Use imperfect, hand-drawn quality
- Add subtle chalk dust and smudge effects
- Create visual hierarchy with color variety
- Include playful doodles and annotations
### Don't
- Use perfect geometric shapes
- Create clean digital-looking lines
- Add photorealistic elements
- Use gradients or glossy effects
```
---
## Part 3: Content Structure Prompt
```
## Content Structure - circular-flow
### Center Element
- "Chrome DevTools MCP" - main concept in center of circle
### Circle Nodes (6 categories, clockwise flow):
1. INPUT AUTOMATION (9 tools)
- click, drag, fill, fill_form, handle_dialog, hover, press_key, type_text, upload_file
- Chalk Pink icon/node
2. NAVIGATION AUTOMATION (6 tools)
- close_page, list_pages, navigate_page, new_page, select_page, wait_for
- Chalk Blue icon/node
3. EMULATION (2 tools)
- emulate, resize_page
- Chalk Yellow icon/node
4. PERFORMANCE (4 tools)
- performance_analyze_insight, performance_start_trace, performance_stop_trace, take_memory_snapshot
- Chalk Green icon/node
5. NETWORK (2 tools)
- get_network_request, list_network_requests
- Chalk Pink icon/node
6. DEBUGGING (6 tools)
- evaluate_script, get_console_message, lighthouse_audit, list_console_messages, take_screenshot, take_snapshot
- Chalk Blue icon/node
### Supported Clients (around the outer edge):
- Claude Code, Cline, Cursor, VS Code, Copilot, Codex, Gemini, JetBrains, Kiro, Windsurf, Amp, Antigravity, Command Code, Factory, Mistral, OpenCode, Qoder, Warp
### Configuration Options (bottom section):
- --headless, --slim, --autoConnect, --browser-url, --channel, --viewport, --isolated, --user-data-dir
### Arrows
- Curved directional arrows connecting each node in clockwise direction
- Showing continuous workflow cycle
### Labels in English
- All text labels in English
- Use chalk white for main text, chalk yellow for emphasis
```
---
## Visual Layout Diagram
```
[INPUT]
↙ ↘
[DEBUGGING] ← CENTER → [NAVIGATION]
↗ ↣
↙ ↘
[NETWORK] ←─────────────────────→ [EMULATION]
↘ ↙
↗ ↣
[PERFORMANCE] →
[???]
Actually circular flow (clockwise):
┌─────────────────────────────────────────┐
│ │
│ [EMULATION] → [PERFORMANCE] │
│ ↗ ↗ │
│ │ │ │
│ [DEBUGGING] [NAVIGATION] │
│ │ │ │
│ ↖ ↘ │
│ [NETWORK] ← [INPUT] │
│ │
│ Center: "Chrome DevTools MCP" │
└─────────────────────────────────────────┘
```
---
## Final Prompt Summary
# Chrome DevTools MCP - Infographic Prompts
Generated: 2026-04-20
Layout: circular-flow
Style: chalkboard
Aspect: 16:9
---
## Part 1: System Prompt
```
Create a professional infographic with the following specifications:
## Image Specifications
- Type: Infographic
- Layout: circular-flow (cyclic process showing continuous steps arranged in a circle)
- Style: chalkboard (chalk on black board aesthetic)
- Aspect Ratio: 16:9
- Language: English
## Core Principles
- Follow the circular-flow layout structure: arrange information nodes in a circle with directional arrows
- Each section represents a tool category as a node on the circle
- Use chalk-drawn style: imperfect hand-drawn lines, chalk dust effects, white/yellow/pink/blue chalk colors
- Black chalkboard background (#1A1A1A)
- Center can hold the main concept "Chrome DevTools MCP"
- Maintain authentic chalk texture on all elements
- Use circular arrangement to show the tool workflow cycle
- Clear visual hierarchy with color variety
## Text Requirements
- Main titles prominent and readable in chalk white
- Key concepts emphasized with chalk yellow/pink
- Labels clear and appropriately sized
```
---
## Part 2: Style Locking Prompt
```
## Style Guidelines - chalkboard
### Background
- Color: Chalkboard Black (#1A1A1A) or Dark Green-Black (#1C2B1C)
- Texture: Realistic chalkboard texture with subtle scratches, dust particles
### Typography
- Hand-drawn chalk lettering with visible chalk texture
- White or bright colored chalk for emphasis
### Color Palette
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - highlights, emphasis
- Accent 2: #FF9999 (Chalk Pink) - secondary highlights
- Accent 3: #66B3FF (Chalk Blue) - links, connections
- Accent 4: #90EE90 (Chalk Green) - success indicators
### Visual Elements
- Hand-drawn chalk illustrations with sketchy, imperfect lines
- Chalk dust effects around text and key elements
- Doodles: stars, arrows, underlines, circles
- Connection lines with hand-drawn feel
- Directional arrows showing cycle flow
### Do
- Maintain authentic chalk texture on all elements
- Use imperfect, hand-drawn quality
- Add subtle chalk dust and smudge effects
- Create visual hierarchy with color variety
- Include playful doodles and annotations
### Don't
- Use perfect geometric shapes
- Create clean digital-looking lines
- Add photorealistic elements
- Use gradients or glossy effects
```
---
## Part 3: Content Structure Prompt
```
## Content Structure - circular-flow
### Center Element
- "Chrome DevTools MCP" - main concept in center of circle
### Circle Nodes (6 categories, clockwise flow):
1. INPUT AUTOMATION (9 tools)
- click, drag, fill, fill_form, handle_dialog, hover, press_key, type_text, upload_file
- Chalk Pink icon/node
2. NAVIGATION AUTOMATION (6 tools)
- close_page, list_pages, navigate_page, new_page, select_page, wait_for
- Chalk Blue icon/node
3. EMULATION (2 tools)
- emulate, resize_page
- Chalk Yellow icon/node
4. PERFORMANCE (4 tools)
- performance_analyze_insight, performance_start_trace, performance_stop_trace, take_memory_snapshot
- Chalk Green icon/node
5. NETWORK (2 tools)
- get_network_request, list_network_requests
- Chalk Pink icon/node
6. DEBUGGING (6 tools)
- evaluate_script, get_console_message, lighthouse_audit, list_console_messages, take_screenshot, take_snapshot
- Chalk Blue icon/node
### Supported Clients (around the outer edge):
- Claude Code, Cline, Cursor, VS Code, Copilot, Codex, Gemini, JetBrains, Kiro, Windsurf, Amp, Antigravity, Command Code, Factory, Mistral, OpenCode, Qoder, Warp
### Configuration Options (bottom section):
- --headless, --slim, --autoConnect, --browser-url, --channel, --viewport, --isolated, --user-data-dir
### Arrows
- Curved directional arrows connecting each node in clockwise direction
- Showing continuous workflow cycle
### Labels in English
- All text labels in English
- Use chalk white for main text, chalk yellow for emphasis
```
---
## Visual Layout Diagram
```
[INPUT]
↙ ↘
[DEBUGGING] ← CENTER → [NAVIGATION]
↗ ↣
↙ ↘
[NETWORK] ←─────────────────────→ [EMULATION]
↘ ↙
↗ ↣
[PERFORMANCE] →
[???]
Actually circular flow (clockwise):
┌─────────────────────────────────────────┐
│ │
│ [EMULATION] → [PERFORMANCE] │
│ ↗ ↗ │
│ │ │ │
│ [DEBUGGING] [NAVIGATION] │
│ │ │ │
│ ↖ ↘ │
│ [NETWORK] ← [INPUT] │
│ │
│ Center: "Chrome DevTools MCP" │
└─────────────────────────────────────────┘
```
---
## Final Prompt Summary
Generate a chalkboard-style infographic showing Chrome DevTools MCP tool categories arranged in a circular flow pattern. The circle has 6 nodes representing the tool categories (Input Automation, Navigation, Emulation, Performance, Network, Debugging). Each node displays the tool count and key tool names in chalk-style lettering. The center shows "Chrome DevTools MCP" as the main concept. Arrows show clockwise flow indicating the continuous nature of browser automation workflow. Use authentic chalkboard aesthetic with black background (#1A1A1A), chalk white text, and colorful chalk accents (yellow, pink, blue, green) for visual hierarchy. Include chalk dust effects and hand-drawn imperfect lines throughout.

View File

@@ -1,63 +1,63 @@
# Hermes Custom Skills - Infographic Image Prompt
## Image Specifications
- **Type**: Infographic
- **Layout**: Circular Flow (cyclic process showing continuous recurring steps)
- **Style**: Chalkboard (black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
## Core Principles
- Follow the circular-flow layout structure precisely for information architecture
- Apply chalkboard style aesthetics consistently throughout
- Create stylistically similar alternatives for any specific figures
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Layout Guidelines
- Circular arrangement with 5 skill nodes evenly spaced around the circle
- Arrows showing clockwise direction flow
- No clear start/end (continuous cycle)
- Center holds main concept: "Hermes Automation Ecosystem"
- Title at top: "Hermes Custom Skills - Automation Workflows"
- Step labels at each node with skill names and taglines
- Brief descriptions near nodes
## Style Guidelines
- **Background**: Chalkboard Black (#1A1A1A) with realistic chalkboard texture, subtle scratches, dust particles, faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture, imperfect baseline, white or bright colored chalk for emphasis
- **Color Palette**:
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - for skill names
- Accent 2: #FF9999 (Chalk Pink) - for highlights
- Accent 3: #66B3FF (Chalk Blue) - for connections
- Accent 4: #90EE90 (Chalk Green) - for success indicators
- **Visual Elements**: Hand-drawn chalk illustrations with sketchy imperfect lines, chalk dust effects, doodles (stars, arrows, circles), stick figures, connection lines with hand-drawn feel
## Content
**Title**: Hermes Custom Skills - Automation Workflows
**Center Concept**: Hermes Automation Ecosystem
**5 Skill Nodes (clockwise from top)**:
1. **blogwatcher-daily** - RSS Subscription Monitor - RSS monitoring + daily note generation - cronjob
2. **claude-code-executor** - Claude Code Task Delegation - Launch Claude Code via TMUX, send tasks, monitor completion - delegate_task
3. **claude-code-infographic-prompts** - Infographic Prompt Generator - Generate 3-part prompts for baoyu-infographic skill - manual
4. **marker-pdf-to-markdown** - PDF to Markdown Converter - High-precision PDF conversion with OCR, table, formula recognition - terminal
5. **whisper-audio-to-text** - Audio/Video Transcription - OpenAI Whisper for speech-to-text - terminal
**Bottom Note**: "5 Custom Skills | Powering Hermes Automation"
**Visual Elements**:
- Chalk dust particles around text
- Hand-drawn arrows connecting skills in clockwise cycle
- Small doodle icons (stars, checkmarks) near each skill
# Hermes Custom Skills - Infographic Image Prompt
## Image Specifications
- **Type**: Infographic
- **Layout**: Circular Flow (cyclic process showing continuous recurring steps)
- **Style**: Chalkboard (black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
## Core Principles
- Follow the circular-flow layout structure precisely for information architecture
- Apply chalkboard style aesthetics consistently throughout
- Create stylistically similar alternatives for any specific figures
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Layout Guidelines
- Circular arrangement with 5 skill nodes evenly spaced around the circle
- Arrows showing clockwise direction flow
- No clear start/end (continuous cycle)
- Center holds main concept: "Hermes Automation Ecosystem"
- Title at top: "Hermes Custom Skills - Automation Workflows"
- Step labels at each node with skill names and taglines
- Brief descriptions near nodes
## Style Guidelines
- **Background**: Chalkboard Black (#1A1A1A) with realistic chalkboard texture, subtle scratches, dust particles, faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture, imperfect baseline, white or bright colored chalk for emphasis
- **Color Palette**:
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - for skill names
- Accent 2: #FF9999 (Chalk Pink) - for highlights
- Accent 3: #66B3FF (Chalk Blue) - for connections
- Accent 4: #90EE90 (Chalk Green) - for success indicators
- **Visual Elements**: Hand-drawn chalk illustrations with sketchy imperfect lines, chalk dust effects, doodles (stars, arrows, circles), stick figures, connection lines with hand-drawn feel
## Content
**Title**: Hermes Custom Skills - Automation Workflows
**Center Concept**: Hermes Automation Ecosystem
**5 Skill Nodes (clockwise from top)**:
1. **blogwatcher-daily** - RSS Subscription Monitor - RSS monitoring + daily note generation - cronjob
2. **claude-code-executor** - Claude Code Task Delegation - Launch Claude Code via TMUX, send tasks, monitor completion - delegate_task
3. **claude-code-infographic-prompts** - Infographic Prompt Generator - Generate 3-part prompts for baoyu-infographic skill - manual
4. **marker-pdf-to-markdown** - PDF to Markdown Converter - High-precision PDF conversion with OCR, table, formula recognition - terminal
5. **whisper-audio-to-text** - Audio/Video Transcription - OpenAI Whisper for speech-to-text - terminal
**Bottom Note**: "5 Custom Skills | Powering Hermes Automation"
**Visual Elements**:
- Chalk dust particles around text
- Hand-drawn arrows connecting skills in clockwise cycle
- Small doodle icons (stars, checkmarks) near each skill
- Eraser smudge textures as subtle background variation

View File

@@ -1,119 +1,119 @@
# Hermes Custom Skills Infographic Prompts
**Date**: 2026-04-20
**Layout**: circular-flow
**Style**: chalkboard
**Aspect**: 16:9
---
## Part 1: System Prompt (Image Specifications + Core Principles)
**Image Specifications**
- **Type**: Infographic
- **Layout**: Circular Flow (cyclic process showing continuous recurring steps)
- **Style**: Chalkboard (black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
**Core Principles**
- Follow the circular-flow layout structure precisely for information architecture
- Apply chalkboard style aesthetics consistently throughout
- Create stylistically similar alternatives for any specific figures
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
---
## Part 2: Style Locking Prompts (Layout Guidelines + Style Guidelines)
**Layout Guidelines (Circular Flow)**
- Circular arrangement with steps around the circle
- Arrows showing direction (clockwise flow)
- No clear start/end (continuous cycle)
- Center can hold main concept
- Steps around the circle: 5 skill nodes evenly spaced
- Icons per step representing each skill
- Title at top
- Step labels at each node
- Brief descriptions near nodes
- Center concept: "Hermes Automation Ecosystem"
**Style Guidelines (Chalkboard)**
- **Background**: Chalkboard Black (#1A1A1A) with realistic chalkboard texture, subtle scratches, dust particles, faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture, imperfect baseline, white or bright colored chalk for emphasis
- **Color Palette**:
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - for skill names
- Accent 2: #FF9999 (Chalk Pink) - for highlights
- Accent 3: #66B3FF (Chalk Blue) - for connections
- Accent 4: #90EE90 (Chalk Green) - for success indicators
- **Visual Elements**: Hand-drawn chalk illustrations with sketchy imperfect lines, chalk dust effects, doodles (stars, arrows, circles), stick figures, connection lines with hand-drawn feel
- **Do**: Maintain authentic chalk texture, use imperfect hand-drawn quality, add chalk dust effects, create visual hierarchy with color variety
- **Don't**: Use perfect geometric shapes, create clean digital-looking lines, add photorealistic elements, use gradients
---
## Part 3: Content Structure Prompts (Text Requirements + Structured Content)
**Text Requirements**
- All text must match chalkboard style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use English for all text content
**Structured Content**
**Title**: Hermes Custom Skills - Automation Workflows
**Center Concept**: Hermes Automation Ecosystem (in the middle of the circle)
**5 Skill Nodes (clockwise from top)**:
1. **blogwatcher-daily**
- Tagline: RSS Subscription Monitor
- Function: RSS monitoring + daily note generation
- Call method: cronjob
- Icon: RSS feed / newspaper symbol
2. **claude-code-executor**
- Tagline: Claude Code Task Delegation
- Function: Launch Claude Code via TMUX, send tasks, monitor completion
- Call method: delegate_task
- Icon: robot / terminal symbol
3. **claude-code-infographic-prompts**
- Tagline: Infographic Prompt Generator
- Function: Generate 3-part prompts for baoyu-infographic skill
- Call method: manual
- Icon: palette / image symbol
4. **marker-pdf-to-markdown**
- Tagline: PDF to Markdown Converter
- Function: High-precision PDF conversion with OCR, table, formula recognition
- Call method: terminal
- Icon: document conversion symbol
5. **whisper-audio-to-text**
- Tagline: Audio/Video Transcription
- Function: OpenAI Whisper for speech-to-text
- Call method: terminal
- Icon: microphone / audio wave symbol
**Connection Arrows**: Clockwise arrows showing continuous workflow between skills
**Bottom Note**: "5 Custom Skills | Powering Hermes Automation"
---
## Visual Elements to Include
- Wooden chalkboard frame border (optional)
- Chalk dust particles around text
- Hand-drawn arrows connecting skills in cycle
- Small doodle icons (stars, checkmarks) near each skill
- Mathematical/formula-style decorations (fitting the chalkboard theme)
# Hermes Custom Skills Infographic Prompts
**Date**: 2026-04-20
**Layout**: circular-flow
**Style**: chalkboard
**Aspect**: 16:9
---
## Part 1: System Prompt (Image Specifications + Core Principles)
**Image Specifications**
- **Type**: Infographic
- **Layout**: Circular Flow (cyclic process showing continuous recurring steps)
- **Style**: Chalkboard (black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
**Core Principles**
- Follow the circular-flow layout structure precisely for information architecture
- Apply chalkboard style aesthetics consistently throughout
- Create stylistically similar alternatives for any specific figures
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
---
## Part 2: Style Locking Prompts (Layout Guidelines + Style Guidelines)
**Layout Guidelines (Circular Flow)**
- Circular arrangement with steps around the circle
- Arrows showing direction (clockwise flow)
- No clear start/end (continuous cycle)
- Center can hold main concept
- Steps around the circle: 5 skill nodes evenly spaced
- Icons per step representing each skill
- Title at top
- Step labels at each node
- Brief descriptions near nodes
- Center concept: "Hermes Automation Ecosystem"
**Style Guidelines (Chalkboard)**
- **Background**: Chalkboard Black (#1A1A1A) with realistic chalkboard texture, subtle scratches, dust particles, faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture, imperfect baseline, white or bright colored chalk for emphasis
- **Color Palette**:
- Background: #1A1A1A (Chalkboard Black)
- Primary Text: #F5F5F5 (Chalk White)
- Accent 1: #FFE566 (Chalk Yellow) - for skill names
- Accent 2: #FF9999 (Chalk Pink) - for highlights
- Accent 3: #66B3FF (Chalk Blue) - for connections
- Accent 4: #90EE90 (Chalk Green) - for success indicators
- **Visual Elements**: Hand-drawn chalk illustrations with sketchy imperfect lines, chalk dust effects, doodles (stars, arrows, circles), stick figures, connection lines with hand-drawn feel
- **Do**: Maintain authentic chalk texture, use imperfect hand-drawn quality, add chalk dust effects, create visual hierarchy with color variety
- **Don't**: Use perfect geometric shapes, create clean digital-looking lines, add photorealistic elements, use gradients
---
## Part 3: Content Structure Prompts (Text Requirements + Structured Content)
**Text Requirements**
- All text must match chalkboard style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use English for all text content
**Structured Content**
**Title**: Hermes Custom Skills - Automation Workflows
**Center Concept**: Hermes Automation Ecosystem (in the middle of the circle)
**5 Skill Nodes (clockwise from top)**:
1. **blogwatcher-daily**
- Tagline: RSS Subscription Monitor
- Function: RSS monitoring + daily note generation
- Call method: cronjob
- Icon: RSS feed / newspaper symbol
2. **claude-code-executor**
- Tagline: Claude Code Task Delegation
- Function: Launch Claude Code via TMUX, send tasks, monitor completion
- Call method: delegate_task
- Icon: robot / terminal symbol
3. **claude-code-infographic-prompts**
- Tagline: Infographic Prompt Generator
- Function: Generate 3-part prompts for baoyu-infographic skill
- Call method: manual
- Icon: palette / image symbol
4. **marker-pdf-to-markdown**
- Tagline: PDF to Markdown Converter
- Function: High-precision PDF conversion with OCR, table, formula recognition
- Call method: terminal
- Icon: document conversion symbol
5. **whisper-audio-to-text**
- Tagline: Audio/Video Transcription
- Function: OpenAI Whisper for speech-to-text
- Call method: terminal
- Icon: microphone / audio wave symbol
**Connection Arrows**: Clockwise arrows showing continuous workflow between skills
**Bottom Note**: "5 Custom Skills | Powering Hermes Automation"
---
## Visual Elements to Include
- Wooden chalkboard frame border (optional)
- Chalk dust particles around text
- Hand-drawn arrows connecting skills in cycle
- Small doodle icons (stars, checkmarks) near each skill
- Mathematical/formula-style decorations (fitting the chalkboard theme)
- Eraser smudge textures as subtle background variation

View File

@@ -1,72 +1,72 @@
---
title: "llm-wiki-sync Circular Flow"
topic: technical
data_type: cycle
complexity: moderate
point_count: 7
source_language: zh
user_language: en
---
## Main Topic
A cyclical knowledge management pipeline that transforms raw notes into structured wiki pages through continuous LLM-powered ingestion, extraction, and reuse.
## Learning Objectives
After viewing this infographic, the viewer will understand:
1. The continuous circular flow of llm-wiki-sync from raw notes to reusable knowledge
2. The key extraction outputs: Summary, Claims, Entities, Concepts, Connections
3. How feedback and reuse complete the cycle back to new raw material
## Target Audience
- **Knowledge Level**: Intermediate technical audience
- **Context**: Developers and knowledge workers interested in AI-powered knowledge management
- **Expectations**: Clear understanding of the llm-wiki-sync pipeline and its cyclical nature
## Content Type Analysis
- **Data Structure**: Cyclic process with recurring steps
- **Key Relationships**: Raw → Ingest → Extract → Source Page → Graph/Site → Reuse → Raw (feedback loop)
- **Visual Opportunities**: Circular flow with nodes for each stage, arrows showing direction, center concept
## Key Data Points (Verbatim)
### Core Pipeline Steps
1. **Raw Note** - Original document in raw/ folder
2. **Ingest** - LLM analyzes and extracts structured information
3. **Extract** - Summary, Claims, Quotes, Entities, Concepts, Connections
4. **Source Page** - Structured wiki/sources/ page with frontmatter
5. **Graph & Site** - graph.json and Quartz static site generation
6. **Reuse** - Synthesize, query, create new content from structured knowledge
7. **Feedback Loop** - New raw notes created from reused knowledge
### Extraction Outputs
- **Summary**: 核心主题, 问题域, 方法/机制, 结论/价值
- **Key Claims**: Verifiable assertions extracted from text
- **Key Entities**: LaunchDarkly, HP, Christian Dior, etc.
- **Key Concepts**: RTO, RPO, Feature Flag, Kill Switch, Gradual Rollout
- **Connections**: depends_on, enables, provides relationships
### Key Quotes
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
## Layout × Style Signals
- Content type: cycle → circular-flow
- Tone: technical educational → chalkboard
- Audience: developers → clear, legible, professional
- Complexity: moderate → balanced density with clear visual hierarchy
## Design Instructions (from user input)
- **Layout**: circular-flow (NOT linear - must emphasize recurring cycle)
- **Style**: chalkboard (dark background, hand-drawn chalk accents)
- **Aspect**: 16:9 landscape
- **Language**: English
- Circular flow showing: raw note -> ingest -> extract -> source page -> graph/static site -> reuse/feedback -> knowledge base
- Include core extraction outputs as recurring nodes/callouts
- Keep text concise and legible
- Dark chalkboard background with hand-drawn chalk accents
- Avoid clutter, make cycle visually clear and publication-ready
## Recommended Combinations
1. **circular-flow + chalkboard** (Recommended): Perfect match for cycle/process content with chalkboard aesthetic
2. **hub-spoke + technical-schematic**: For emphasizing central concepts with technical precision
---
title: "llm-wiki-sync Circular Flow"
topic: technical
data_type: cycle
complexity: moderate
point_count: 7
source_language: zh
user_language: en
---
## Main Topic
A cyclical knowledge management pipeline that transforms raw notes into structured wiki pages through continuous LLM-powered ingestion, extraction, and reuse.
## Learning Objectives
After viewing this infographic, the viewer will understand:
1. The continuous circular flow of llm-wiki-sync from raw notes to reusable knowledge
2. The key extraction outputs: Summary, Claims, Entities, Concepts, Connections
3. How feedback and reuse complete the cycle back to new raw material
## Target Audience
- **Knowledge Level**: Intermediate technical audience
- **Context**: Developers and knowledge workers interested in AI-powered knowledge management
- **Expectations**: Clear understanding of the llm-wiki-sync pipeline and its cyclical nature
## Content Type Analysis
- **Data Structure**: Cyclic process with recurring steps
- **Key Relationships**: Raw → Ingest → Extract → Source Page → Graph/Site → Reuse → Raw (feedback loop)
- **Visual Opportunities**: Circular flow with nodes for each stage, arrows showing direction, center concept
## Key Data Points (Verbatim)
### Core Pipeline Steps
1. **Raw Note** - Original document in raw/ folder
2. **Ingest** - LLM analyzes and extracts structured information
3. **Extract** - Summary, Claims, Quotes, Entities, Concepts, Connections
4. **Source Page** - Structured wiki/sources/ page with frontmatter
5. **Graph & Site** - graph.json and Quartz static site generation
6. **Reuse** - Synthesize, query, create new content from structured knowledge
7. **Feedback Loop** - New raw notes created from reused knowledge
### Extraction Outputs
- **Summary**: 核心主题, 问题域, 方法/机制, 结论/价值
- **Key Claims**: Verifiable assertions extracted from text
- **Key Entities**: LaunchDarkly, HP, Christian Dior, etc.
- **Key Concepts**: RTO, RPO, Feature Flag, Kill Switch, Gradual Rollout
- **Connections**: depends_on, enables, provides relationships
### Key Quotes
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
## Layout × Style Signals
- Content type: cycle → circular-flow
- Tone: technical educational → chalkboard
- Audience: developers → clear, legible, professional
- Complexity: moderate → balanced density with clear visual hierarchy
## Design Instructions (from user input)
- **Layout**: circular-flow (NOT linear - must emphasize recurring cycle)
- **Style**: chalkboard (dark background, hand-drawn chalk accents)
- **Aspect**: 16:9 landscape
- **Language**: English
- Circular flow showing: raw note -> ingest -> extract -> source page -> graph/static site -> reuse/feedback -> knowledge base
- Include core extraction outputs as recurring nodes/callouts
- Keep text concise and legible
- Dark chalkboard background with hand-drawn chalk accents
- Avoid clutter, make cycle visually clear and publication-ready
## Recommended Combinations
1. **circular-flow + chalkboard** (Recommended): Perfect match for cycle/process content with chalkboard aesthetic
2. **hub-spoke + technical-schematic**: For emphasizing central concepts with technical precision
3. **bento-grid + craft-handmade**: For multiple topic overview with friendly handmade feel

View File

@@ -1,158 +1,158 @@
Create a professional infographic following these specifications:
## Image Specifications
- **Type**: Infographic
- **Layout**: circular-flow (Cyclic process showing continuous or recurring steps)
- **Style**: chalkboard (Black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
## Core Principles
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use the specified language for all text content
## Layout Guidelines
- Circular arrangement
- Steps around the circle
- Arrows showing direction
- No clear start/end (continuous)
- Center can hold main concept
- Circle or ring shape
- Directional arrows
- Step nodes evenly spaced
- Icons per step
- Optional center element
## Style Guidelines
- **Background**: Chalkboard Black (#1A1A1A) or Dark Green-Black (#1C2B1C)
- **Texture**: Realistic chalkboard texture with subtle scratches, dust particles, and faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture. Imperfect baseline adds authenticity.
- **Color Palette**:
- Background: Chalkboard Black (#1A1A1A)
- Primary Text: Chalk White (#F5F5F5)
- Accent 1: Chalk Yellow (#FFE566)
- Accent 2: Chalk Pink (#FF9999)
- Accent 3: Chalk Blue (#66B3FF)
- Accent 4: Chalk Green (#90EE90)
- Accent 5: Chalk Orange (#FFB366)
- **Visual Elements**:
- Hand-drawn chalk illustrations with sketchy, imperfect lines
- Chalk dust effects around text and key elements
- Doodles: stars, arrows, underlines, circles, checkmarks
- Eraser smudges and chalk residue textures
- Wooden frame border optional
- Stick figures and simple icons
- Connection lines with hand-drawn feel
- **Style Rules**:
- Maintain authentic chalk texture on all elements
- Use imperfect, hand-drawn quality throughout
- Add subtle chalk dust and smudge effects
- Create visual hierarchy with color variety
- Include playful doodles and annotations
- DO NOT use perfect geometric shapes
- DO NOT create clean digital-looking lines
---
Generate the infographic based on the content below:
# llm-wiki-sync: Turning Scattered Notes into a Reusable Knowledge Base
## Overview
A cyclical pipeline showing how raw notes are continuously transformed through LLM-powered ingestion into structured wiki pages, then feedback into the knowledge base for reuse.
## The Knowledge Pipeline Cycle (Center Concept)
The llm-wiki-sync pipeline operates as a continuous cycle, not a linear process.
7 stages in the cycle: Raw Note → Ingest → Extract → Source Page → Graph/Site → Reuse → Feedback Loop
## Circular Flow Diagram with 7 Stages:
1. **Raw Note** (Stage 1)
- Original documents stored in raw/ folder
- Contains unprocessed information awaiting structure
- Icon: Stack of paper/note icon
2. **Ingest** (Stage 2)
- LLM analyzes and extracts structured information
- Hermes skill triggers Claude Code for ingestion
- Context check against wiki/index.md prevents duplicates
- Icon: Brain/processing icon
3. **Extract** (Stage 3)
- Six key elements extracted from each document:
- Summary (核心主题, 问题域, 方法/机制, 结论/价值)
- Key Claims (Verifiable assertions)
- Key Quotes (Preserved citations)
- Key Entities (LaunchDarkly, HP, etc.)
- Key Concepts (RTO, RPO, Feature Flag, etc.)
- Connections (depends_on, enables, provides)
- Icon: Six circles/callouts
4. **Source Page** (Stage 4)
- Written to wiki/sources/<slug>.md
- Contains frontmatter and standard sections
- Links use [[PageName]] format
- Icon: Document/page icon
5. **Graph & Site** (Stage 5)
- graph.json: Machine-readable graph structure
- graph.html: Interactive visualization
- Quartz: Static site generation
- Icon: Network/graph icon
6. **Reuse** (Stage 6)
- Query, Synthesize, Write, Connect
- Icon: Multiple arrows pointing outward
7. **Feedback Loop** (Stage 7)
- New insights become new raw notes
- Cycle continues indefinitely
- Icon: Circular arrow completing the cycle
## Key Quotes (to include as callouts):
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
## Design Requirements:
- Circular flow with 7 stages evenly spaced around a circle
- Clockwise arrow direction
- Center contains: "llm-wiki-sync" as main concept
- Each stage is a node with icon + label
- Extraction outputs (6 items) shown as callouts or inner ring
- Dark chalkboard background with hand-drawn chalk accents
- Chalk colors for visual hierarchy
- Imperfect, sketchy lines throughout
- Publication-ready quality
- NO clutter - only essential elements
- Clear hierarchy: title > headlines > labels > descriptions
## Text Labels (in English):
- Headline: "The Knowledge Pipeline Cycle"
- Subhead: "How llm-wiki-sync transforms scattered notes into a reusable knowledge base"
- Stage labels: "Raw Note", "Ingest", "Extract", "Source Page", "Graph & Site", "Reuse", "Feedback"
- Center: "llm-wiki-sync"
- Extraction labels: "Summary", "Claims", "Quotes", "Entities", "Concepts", "Connections"
## Key Constraints:
- 16:9 aspect ratio (landscape)
- All text in English
- Chalkboard style (dark background, chalk-like hand-drawn elements)
- Circular flow layout (NOT linear)
- Publication-ready, visually clear
Create a professional infographic following these specifications:
## Image Specifications
- **Type**: Infographic
- **Layout**: circular-flow (Cyclic process showing continuous or recurring steps)
- **Style**: chalkboard (Black chalkboard background with colorful chalk drawing style)
- **Aspect Ratio**: 16:9
- **Language**: English
## Core Principles
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use the specified language for all text content
## Layout Guidelines
- Circular arrangement
- Steps around the circle
- Arrows showing direction
- No clear start/end (continuous)
- Center can hold main concept
- Circle or ring shape
- Directional arrows
- Step nodes evenly spaced
- Icons per step
- Optional center element
## Style Guidelines
- **Background**: Chalkboard Black (#1A1A1A) or Dark Green-Black (#1C2B1C)
- **Texture**: Realistic chalkboard texture with subtle scratches, dust particles, and faint eraser marks
- **Typography**: Hand-drawn chalk lettering style with visible chalk texture. Imperfect baseline adds authenticity.
- **Color Palette**:
- Background: Chalkboard Black (#1A1A1A)
- Primary Text: Chalk White (#F5F5F5)
- Accent 1: Chalk Yellow (#FFE566)
- Accent 2: Chalk Pink (#FF9999)
- Accent 3: Chalk Blue (#66B3FF)
- Accent 4: Chalk Green (#90EE90)
- Accent 5: Chalk Orange (#FFB366)
- **Visual Elements**:
- Hand-drawn chalk illustrations with sketchy, imperfect lines
- Chalk dust effects around text and key elements
- Doodles: stars, arrows, underlines, circles, checkmarks
- Eraser smudges and chalk residue textures
- Wooden frame border optional
- Stick figures and simple icons
- Connection lines with hand-drawn feel
- **Style Rules**:
- Maintain authentic chalk texture on all elements
- Use imperfect, hand-drawn quality throughout
- Add subtle chalk dust and smudge effects
- Create visual hierarchy with color variety
- Include playful doodles and annotations
- DO NOT use perfect geometric shapes
- DO NOT create clean digital-looking lines
---
Generate the infographic based on the content below:
# llm-wiki-sync: Turning Scattered Notes into a Reusable Knowledge Base
## Overview
A cyclical pipeline showing how raw notes are continuously transformed through LLM-powered ingestion into structured wiki pages, then feedback into the knowledge base for reuse.
## The Knowledge Pipeline Cycle (Center Concept)
The llm-wiki-sync pipeline operates as a continuous cycle, not a linear process.
7 stages in the cycle: Raw Note → Ingest → Extract → Source Page → Graph/Site → Reuse → Feedback Loop
## Circular Flow Diagram with 7 Stages:
1. **Raw Note** (Stage 1)
- Original documents stored in raw/ folder
- Contains unprocessed information awaiting structure
- Icon: Stack of paper/note icon
2. **Ingest** (Stage 2)
- LLM analyzes and extracts structured information
- Hermes skill triggers Claude Code for ingestion
- Context check against wiki/index.md prevents duplicates
- Icon: Brain/processing icon
3. **Extract** (Stage 3)
- Six key elements extracted from each document:
- Summary (核心主题, 问题域, 方法/机制, 结论/价值)
- Key Claims (Verifiable assertions)
- Key Quotes (Preserved citations)
- Key Entities (LaunchDarkly, HP, etc.)
- Key Concepts (RTO, RPO, Feature Flag, etc.)
- Connections (depends_on, enables, provides)
- Icon: Six circles/callouts
4. **Source Page** (Stage 4)
- Written to wiki/sources/<slug>.md
- Contains frontmatter and standard sections
- Links use [[PageName]] format
- Icon: Document/page icon
5. **Graph & Site** (Stage 5)
- graph.json: Machine-readable graph structure
- graph.html: Interactive visualization
- Quartz: Static site generation
- Icon: Network/graph icon
6. **Reuse** (Stage 6)
- Query, Synthesize, Write, Connect
- Icon: Multiple arrows pointing outward
7. **Feedback Loop** (Stage 7)
- New insights become new raw notes
- Cycle continues indefinitely
- Icon: Circular arrow completing the cycle
## Key Quotes (to include as callouts):
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
## Design Requirements:
- Circular flow with 7 stages evenly spaced around a circle
- Clockwise arrow direction
- Center contains: "llm-wiki-sync" as main concept
- Each stage is a node with icon + label
- Extraction outputs (6 items) shown as callouts or inner ring
- Dark chalkboard background with hand-drawn chalk accents
- Chalk colors for visual hierarchy
- Imperfect, sketchy lines throughout
- Publication-ready quality
- NO clutter - only essential elements
- Clear hierarchy: title > headlines > labels > descriptions
## Text Labels (in English):
- Headline: "The Knowledge Pipeline Cycle"
- Subhead: "How llm-wiki-sync transforms scattered notes into a reusable knowledge base"
- Stage labels: "Raw Note", "Ingest", "Extract", "Source Page", "Graph & Site", "Reuse", "Feedback"
- Center: "llm-wiki-sync"
- Extraction labels: "Summary", "Claims", "Quotes", "Entities", "Concepts", "Connections"
## Key Constraints:
- 16:9 aspect ratio (landscape)
- All text in English
- Chalkboard style (dark background, chalk-like hand-drawn elements)
- Circular flow layout (NOT linear)
- Publication-ready, visually clear
- No clutter or excessive elements

View File

@@ -1,229 +1,229 @@
# llm-wiki-sync: Turning Scattered Notes into a Reusable Knowledge Base
## Overview
A cyclical pipeline showing how raw notes are continuously transformed through LLM-powered ingestion into structured wiki pages, then feedback into the knowledge base for reuse.
## Learning Objectives
The viewer will understand:
1. The continuous circular flow of llm-wiki-sync from raw notes to reusable knowledge
2. The six key extraction outputs: Summary, Claims, Quotes, Entities, Concepts, Connections
3. How feedback and reuse complete the cycle back to new raw material
---
## Section 1: The Circular Flow (Center Concept)
**Key Concept**: The llm-wiki-sync pipeline operates as a continuous cycle, not a linear process.
**Content**:
- 7 stages in the cycle: Raw Note → Ingest → Extract → Source Page → Graph/Site → Reuse → Feedback Loop
- Each stage feeds into the next, with feedback returning to the beginning
- The cycle is continuous and self-reinforcing
**Visual Element**:
- Type: circular flow diagram
- Subject: 7 stages arranged in a circle with clockwise arrows
- Center label: "llm-wiki-sync Cycle"
- Treatment: chalk style with hand-drawn arrows connecting stages
**Text Labels**:
- Headline: "The Knowledge Pipeline Cycle"
- Stage labels: "Raw Note", "Ingest", "Extract", "Source Page", "Graph/Site", "Reuse", "Feedback"
- Center: "llm-wiki-sync"
---
## Section 2: Stage 1 — Raw Note (Input)
**Key Concept**: Raw notes are the starting point of the cycle.
**Content**:
- Original documents stored in raw/ folder
- Can be any format: markdown, text, research notes
- Contains unprocessed information awaiting structure
**Visual Element**:
- Type: document/note icon
- Subject: Stack of paper or note icon
- Treatment: Chalk sketch style
**Text Labels**:
- Label: "Raw Note"
- Description: "Original input"
---
## Section 3: Stage 2 — Ingest (LLM Analysis)
**Key Concept**: The LLM analyzes raw notes and extracts structured information.
**Content**:
- Hermes skill triggers Claude Code for ingestion
- LLM reads and analyzes the full document
- Context check against wiki/index.md prevents duplicates
**Visual Element**:
- Type: brain/processing icon
- Subject: Brain or gears with chalk lines
- Treatment: Hand-drawn chalk illustration
**Text Labels**:
- Label: "Ingest"
- Description: "LLM Analysis"
---
## Section 4: Stage 3 — Extract (Six Core Outputs)
**Key Concept**: Six key elements are extracted from each document.
**Content**:
1. **Summary**: 核心主题, 问题域, 方法/机制, 结论/价值
2. **Key Claims**: Verifiable assertions extracted from text
3. **Key Quotes**: Preserved citations for reference
4. **Key Entities**: Named people, companies, products (e.g., LaunchDarkly, HP)
5. **Key Concepts**: Abstract terms that can be reused (e.g., RTO, RPO, Feature Flag)
6. **Connections**: Relationships between elements (depends_on, enables, provides)
**Visual Element**:
- Type: 6 callout nodes around center
- Subject: Six boxes or bubbles representing extraction outputs
- Treatment: Chalk circles with icons inside each
**Text Labels**:
- Headline: "Extraction Outputs"
- Labels: "Summary", "Claims", "Quotes", "Entities", "Concepts", "Connections"
---
## Section 5: Stage 4 — Source Page (Structured Output)
**Key Concept**: Extracted information is written as a structured wiki source page.
**Content**:
- Written to wiki/sources/<slug>.md
- Contains frontmatter (id, title, type, tags, sources, last_updated)
- Standard sections: Summary, Key Claims, Key Quotes, Key Concepts, Key Entities, Connections, Contradictions
- Links use [[PageName]] format for interconnections
**Visual Element**:
- Type: document/page icon
- Subject: Page with visible structure headers
- Treatment: Chalk sketch with text lines
**Text Labels**:
- Label: "Source Page"
- Description: "wiki/sources/*.md"
---
## Section 6: Stage 5 — Graph & Static Site
**Key Concept**: Structured pages generate knowledge graphs and static websites.
**Content**:
- graph.json: Machine-readable graph structure
- graph.html: Interactive visualization
- Quartz: Static site generation for sharing/export
- Connections become edges in the knowledge graph
**Visual Element**:
- Type: network/graph icon
- Subject: Connected nodes representing knowledge graph
- Treatment: Chalk diagram with nodes and edges
**Text Labels**:
- Label: "Graph & Site"
- Description: "graph.json + Quartz"
---
## Section 7: Stage 6 — Reuse (Knowledge Application)
**Key Concept**: Structured knowledge enables multiple reuse scenarios.
**Content**:
- Query: Ask questions against the knowledge base
- Synthesize: Create new content from existing knowledge
- Write: Generate articles, reports from source material
- Connect: Link ideas across different source pages
**Visual Element**:
- Type: multiple arrows pointing outward
- Subject: Reuse scenarios as icons (question, document, pen)
- Treatment: Chalk illustration
**Text Labels**:
- Label: "Reuse"
- Sub-labels: "Query", "Synthesize", "Write", "Connect"
---
## Section 8: Stage 7 — Feedback Loop (Continuous Cycle)
**Key Concept**: Reuse generates new raw notes, completing the cycle.
**Content**:
- New insights from synthesis become new raw notes
- Updated knowledge feeds back to raw/ folder
- Cycle continues indefinitely
- Each iteration strengthens the knowledge base
**Visual Element**:
- Type: circular arrow
- Subject: Feedback loop arrow returning to Raw Note stage
- Treatment: Large chalk arrow completing the circle
**Text Labels**:
- Label: "Feedback Loop"
- Description: "New notes → Cycle repeats"
---
## Data Points (Verbatim)
### Key Quotes
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
### Key Entities
- LaunchDarkly (Feature Flag management platform)
- HP (example enterprise)
- Christian Dior (example case)
### Key Concepts
- RTO (Recovery Time Objective)
- RPO (Recovery Point Objective)
- Feature Flag (特性开关)
- Kill Switch (紧急关闭机制)
- 渐进式发布 (Gradual Rollout)
---
## Design Instructions
### Layout Preferences
- Circular flow with 7 stages evenly spaced around a circle
- Clockwise arrow direction
- Center contains the main concept "llm-wiki-sync"
- Each stage is a node with icon + label
- Extraction outputs (6 items) shown as callouts or inner ring
### Style Preferences
- Chalkboard: Dark background (#1A1A1A)
- Hand-drawn chalk style for all elements
- Chalk colors: white, yellow, pink, blue, green, orange
- Imperfect, sketchy lines throughout
- Chalk dust effects for authenticity
### Text Requirements
- All text in English
- Legible font sizes (minimum 14pt for labels)
- Clear hierarchy: title > headlines > labels > descriptions
- Ample whitespace between stages
### Visual Clarity
- Avoid clutter - only essential elements
- Each stage should be clearly distinguishable
- Arrows should clearly indicate flow direction
# llm-wiki-sync: Turning Scattered Notes into a Reusable Knowledge Base
## Overview
A cyclical pipeline showing how raw notes are continuously transformed through LLM-powered ingestion into structured wiki pages, then feedback into the knowledge base for reuse.
## Learning Objectives
The viewer will understand:
1. The continuous circular flow of llm-wiki-sync from raw notes to reusable knowledge
2. The six key extraction outputs: Summary, Claims, Quotes, Entities, Concepts, Connections
3. How feedback and reuse complete the cycle back to new raw material
---
## Section 1: The Circular Flow (Center Concept)
**Key Concept**: The llm-wiki-sync pipeline operates as a continuous cycle, not a linear process.
**Content**:
- 7 stages in the cycle: Raw Note → Ingest → Extract → Source Page → Graph/Site → Reuse → Feedback Loop
- Each stage feeds into the next, with feedback returning to the beginning
- The cycle is continuous and self-reinforcing
**Visual Element**:
- Type: circular flow diagram
- Subject: 7 stages arranged in a circle with clockwise arrows
- Center label: "llm-wiki-sync Cycle"
- Treatment: chalk style with hand-drawn arrows connecting stages
**Text Labels**:
- Headline: "The Knowledge Pipeline Cycle"
- Stage labels: "Raw Note", "Ingest", "Extract", "Source Page", "Graph/Site", "Reuse", "Feedback"
- Center: "llm-wiki-sync"
---
## Section 2: Stage 1 — Raw Note (Input)
**Key Concept**: Raw notes are the starting point of the cycle.
**Content**:
- Original documents stored in raw/ folder
- Can be any format: markdown, text, research notes
- Contains unprocessed information awaiting structure
**Visual Element**:
- Type: document/note icon
- Subject: Stack of paper or note icon
- Treatment: Chalk sketch style
**Text Labels**:
- Label: "Raw Note"
- Description: "Original input"
---
## Section 3: Stage 2 — Ingest (LLM Analysis)
**Key Concept**: The LLM analyzes raw notes and extracts structured information.
**Content**:
- Hermes skill triggers Claude Code for ingestion
- LLM reads and analyzes the full document
- Context check against wiki/index.md prevents duplicates
**Visual Element**:
- Type: brain/processing icon
- Subject: Brain or gears with chalk lines
- Treatment: Hand-drawn chalk illustration
**Text Labels**:
- Label: "Ingest"
- Description: "LLM Analysis"
---
## Section 4: Stage 3 — Extract (Six Core Outputs)
**Key Concept**: Six key elements are extracted from each document.
**Content**:
1. **Summary**: 核心主题, 问题域, 方法/机制, 结论/价值
2. **Key Claims**: Verifiable assertions extracted from text
3. **Key Quotes**: Preserved citations for reference
4. **Key Entities**: Named people, companies, products (e.g., LaunchDarkly, HP)
5. **Key Concepts**: Abstract terms that can be reused (e.g., RTO, RPO, Feature Flag)
6. **Connections**: Relationships between elements (depends_on, enables, provides)
**Visual Element**:
- Type: 6 callout nodes around center
- Subject: Six boxes or bubbles representing extraction outputs
- Treatment: Chalk circles with icons inside each
**Text Labels**:
- Headline: "Extraction Outputs"
- Labels: "Summary", "Claims", "Quotes", "Entities", "Concepts", "Connections"
---
## Section 5: Stage 4 — Source Page (Structured Output)
**Key Concept**: Extracted information is written as a structured wiki source page.
**Content**:
- Written to wiki/sources/<slug>.md
- Contains frontmatter (id, title, type, tags, sources, last_updated)
- Standard sections: Summary, Key Claims, Key Quotes, Key Concepts, Key Entities, Connections, Contradictions
- Links use [[PageName]] format for interconnections
**Visual Element**:
- Type: document/page icon
- Subject: Page with visible structure headers
- Treatment: Chalk sketch with text lines
**Text Labels**:
- Label: "Source Page"
- Description: "wiki/sources/*.md"
---
## Section 6: Stage 5 — Graph & Static Site
**Key Concept**: Structured pages generate knowledge graphs and static websites.
**Content**:
- graph.json: Machine-readable graph structure
- graph.html: Interactive visualization
- Quartz: Static site generation for sharing/export
- Connections become edges in the knowledge graph
**Visual Element**:
- Type: network/graph icon
- Subject: Connected nodes representing knowledge graph
- Treatment: Chalk diagram with nodes and edges
**Text Labels**:
- Label: "Graph & Site"
- Description: "graph.json + Quartz"
---
## Section 7: Stage 6 — Reuse (Knowledge Application)
**Key Concept**: Structured knowledge enables multiple reuse scenarios.
**Content**:
- Query: Ask questions against the knowledge base
- Synthesize: Create new content from existing knowledge
- Write: Generate articles, reports from source material
- Connect: Link ideas across different source pages
**Visual Element**:
- Type: multiple arrows pointing outward
- Subject: Reuse scenarios as icons (question, document, pen)
- Treatment: Chalk illustration
**Text Labels**:
- Label: "Reuse"
- Sub-labels: "Query", "Synthesize", "Write", "Connect"
---
## Section 8: Stage 7 — Feedback Loop (Continuous Cycle)
**Key Concept**: Reuse generates new raw notes, completing the cycle.
**Content**:
- New insights from synthesis become new raw notes
- Updated knowledge feeds back to raw/ folder
- Cycle continues indefinitely
- Each iteration strengthens the knowledge base
**Visual Element**:
- Type: circular arrow
- Subject: Feedback loop arrow returning to Raw Note stage
- Treatment: Large chalk arrow completing the circle
**Text Labels**:
- Label: "Feedback Loop"
- Description: "New notes → Cycle repeats"
---
## Data Points (Verbatim)
### Key Quotes
- "RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose."
- "Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users."
### Key Entities
- LaunchDarkly (Feature Flag management platform)
- HP (example enterprise)
- Christian Dior (example case)
### Key Concepts
- RTO (Recovery Time Objective)
- RPO (Recovery Point Objective)
- Feature Flag (特性开关)
- Kill Switch (紧急关闭机制)
- 渐进式发布 (Gradual Rollout)
---
## Design Instructions
### Layout Preferences
- Circular flow with 7 stages evenly spaced around a circle
- Clockwise arrow direction
- Center contains the main concept "llm-wiki-sync"
- Each stage is a node with icon + label
- Extraction outputs (6 items) shown as callouts or inner ring
### Style Preferences
- Chalkboard: Dark background (#1A1A1A)
- Hand-drawn chalk style for all elements
- Chalk colors: white, yellow, pink, blue, green, orange
- Imperfect, sketchy lines throughout
- Chalk dust effects for authenticity
### Text Requirements
- All text in English
- Legible font sizes (minimum 14pt for labels)
- Clear hierarchy: title > headlines > labels > descriptions
- Ample whitespace between stages
### Visual Clarity
- Avoid clutter - only essential elements
- Each stage should be clearly distinguishable
- Arrows should clearly indicate flow direction
- Publication-ready quality

View File

@@ -1,182 +1,182 @@
# Major Incident Definition - Infographic Prompts
# Generated: 2026-04-20
# Layout: Venn Diagram
# Style: Cyberpunk Neon
# Aspect: 16:9
# Language: English
---
## Part 1: System Prompt (Image Specifications + Core Principles)
Create a professional infographic following these specifications:
**Image Specifications**
- **Type**: Infographic
- **Layout**: Venn Diagram (venn-diagram)
- **Style**: Cyberpunk Neon
- **Aspect Ratio**: 16:9
- **Language**: English
**Core Principles**
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
**Text Requirements**
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use English for all text content
---
## Part 2: Style Lock Prompt (Layout Guidelines + Style Guidelines)
### Layout Guidelines (Venn Diagram)
**Structure**: Three overlapping circles forming a central intersection area
**Visual Elements**:
- Three large overlapping circles representing different categories of Major Incident
- Central intersection zone highlighting the combined criteria
- Each circle contains key concepts specific to its category
- Outer labels for each circle region
- Connection lines from concepts to their respective regions
- Minimal text, emphasize visual grouping
**Composition**:
- Canvas divided into three equal sections for each circle
- 30-40% overlap area in center for intersection
- Ample negative space between elements
- Clear visual boundaries between overlapping regions
### Style Guidelines (Cyberpunk Neon)
**Color Palette**:
- Primary: Neon pink (#FF00FF), cyan (#00FFFF), electric blue
- Background: Deep black (#0A0A0A), dark purple gradients
- Accents: Neon glow effects, chrome reflections
**Visual Elements**:
- Glowing neon outlines on all circle boundaries
- Dark atmospheric backgrounds with subtle grid patterns
- Digital glitch effects on text
- Circuit patterns along connection lines
- Holographic elements in intersection zone
- Rain and reflections on edges
**Typography**:
- Glowing neon text (cyan for primary labels, pink for emphasis)
- Digital/tech monospace fonts
- Subtle flickering effects on key terms
- Outlined glow letters for main title
- All caps for section headers
**Effects**:
- Neon glow (2-3px bloom) around all text and shapes
- Gradient fills with 40-60% opacity
- Scanline overlay at 10% opacity
- Chromatic aberration on text edges
---
## Part 3: Content Structure Prompt (Text Requirements + Content)
### Main Title
**MAJOR INCIDENT DEFINITION**
*Highest Severity Level (S1/P1/Critical)*
### Three Venn Circles Content
**Circle 1: Business Impact**
- Total Service Outage
- Critical Feature Failure
- Data Corruption/Loss
- Security Breach
- Regulatory Compliance Risk
- High-Impact SLA Breach
**Circle 2: Incident Response**
- Immediate Actions (0-15 min)
- Automated Monitoring Alerts
- Incident Commander Assigned
- Major Incident Bridge
- Customer Communication
- Investigation & Mitigation (15-60 min)
- Root Cause Analysis
- Rollback/Hotfix
- Failover to Backup
- Workarounds
- Recovery & Post-Mortem (1-24h+)
- Full Service Restored
- RCA Report Published
- Long-Term Fixes
**Circle 3: Prevention Measures**
- High Availability Architecture
- Chaos Engineering & Load Testing
- Real-Time Monitoring & Alerting
- Automated Rollbacks
- Strict Change Management
- Security Hardening & Compliance
### Central Intersection (All Three Circles)
The intersection represents the critical overlap showing that Major Incidents require:
- Cross-team collaboration
- Immediate response (15-30 min SLA)
- Significant business impact
- Coordinated resolution
### Key Criteria Table (Bottom Section)
| Criteria | Description |
|----------|-------------|
| Scope | Multiple tenants/customers, entire platform |
| Business Criticality | Severe financial/reputational impact |
| Resolution Time | Immediate, 15-30 min acknowledgment |
| Workload Impact | Cloud Ops, DevOps, Security, Support |
| Regulatory Compliance | Legal/security obligations at risk |
### Footer Text
"Requires Swift, Coordinated Response to Minimize Downtime"
---
## Visual Composition for 16:9
- **Top Section** (10%): Title with neon glow effect
- **Middle Section** (70%): Three overlapping circles with content
- **Bottom Section** (20%): Criteria table in minimalist style
### Color Assignment per Circle
- Circle 1 (Business Impact): Cyan (#00FFFF) neon outline
- Circle 2 (Incident Response): Neon Pink (#FF00FF) neon outline
- Circle 3 (Prevention): Electric Blue (#0080FF) neon outline
- Intersection: White glow with purple tint
- Background: Deep black (#0A0A0A) with dark purple gradient
---
# Major Incident Definition - Infographic Prompts
# Generated: 2026-04-20
# Layout: Venn Diagram
# Style: Cyberpunk Neon
# Aspect: 16:9
# Language: English
---
## Part 1: System Prompt (Image Specifications + Core Principles)
Create a professional infographic following these specifications:
**Image Specifications**
- **Type**: Infographic
- **Layout**: Venn Diagram (venn-diagram)
- **Style**: Cyberpunk Neon
- **Aspect Ratio**: 16:9
- **Language**: English
**Core Principles**
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
**Text Requirements**
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use English for all text content
---
## Part 2: Style Lock Prompt (Layout Guidelines + Style Guidelines)
### Layout Guidelines (Venn Diagram)
**Structure**: Three overlapping circles forming a central intersection area
**Visual Elements**:
- Three large overlapping circles representing different categories of Major Incident
- Central intersection zone highlighting the combined criteria
- Each circle contains key concepts specific to its category
- Outer labels for each circle region
- Connection lines from concepts to their respective regions
- Minimal text, emphasize visual grouping
**Composition**:
- Canvas divided into three equal sections for each circle
- 30-40% overlap area in center for intersection
- Ample negative space between elements
- Clear visual boundaries between overlapping regions
### Style Guidelines (Cyberpunk Neon)
**Color Palette**:
- Primary: Neon pink (#FF00FF), cyan (#00FFFF), electric blue
- Background: Deep black (#0A0A0A), dark purple gradients
- Accents: Neon glow effects, chrome reflections
**Visual Elements**:
- Glowing neon outlines on all circle boundaries
- Dark atmospheric backgrounds with subtle grid patterns
- Digital glitch effects on text
- Circuit patterns along connection lines
- Holographic elements in intersection zone
- Rain and reflections on edges
**Typography**:
- Glowing neon text (cyan for primary labels, pink for emphasis)
- Digital/tech monospace fonts
- Subtle flickering effects on key terms
- Outlined glow letters for main title
- All caps for section headers
**Effects**:
- Neon glow (2-3px bloom) around all text and shapes
- Gradient fills with 40-60% opacity
- Scanline overlay at 10% opacity
- Chromatic aberration on text edges
---
## Part 3: Content Structure Prompt (Text Requirements + Content)
### Main Title
**MAJOR INCIDENT DEFINITION**
*Highest Severity Level (S1/P1/Critical)*
### Three Venn Circles Content
**Circle 1: Business Impact**
- Total Service Outage
- Critical Feature Failure
- Data Corruption/Loss
- Security Breach
- Regulatory Compliance Risk
- High-Impact SLA Breach
**Circle 2: Incident Response**
- Immediate Actions (0-15 min)
- Automated Monitoring Alerts
- Incident Commander Assigned
- Major Incident Bridge
- Customer Communication
- Investigation & Mitigation (15-60 min)
- Root Cause Analysis
- Rollback/Hotfix
- Failover to Backup
- Workarounds
- Recovery & Post-Mortem (1-24h+)
- Full Service Restored
- RCA Report Published
- Long-Term Fixes
**Circle 3: Prevention Measures**
- High Availability Architecture
- Chaos Engineering & Load Testing
- Real-Time Monitoring & Alerting
- Automated Rollbacks
- Strict Change Management
- Security Hardening & Compliance
### Central Intersection (All Three Circles)
The intersection represents the critical overlap showing that Major Incidents require:
- Cross-team collaboration
- Immediate response (15-30 min SLA)
- Significant business impact
- Coordinated resolution
### Key Criteria Table (Bottom Section)
| Criteria | Description |
|----------|-------------|
| Scope | Multiple tenants/customers, entire platform |
| Business Criticality | Severe financial/reputational impact |
| Resolution Time | Immediate, 15-30 min acknowledgment |
| Workload Impact | Cloud Ops, DevOps, Security, Support |
| Regulatory Compliance | Legal/security obligations at risk |
### Footer Text
"Requires Swift, Coordinated Response to Minimize Downtime"
---
## Visual Composition for 16:9
- **Top Section** (10%): Title with neon glow effect
- **Middle Section** (70%): Three overlapping circles with content
- **Bottom Section** (20%): Criteria table in minimalist style
### Color Assignment per Circle
- Circle 1 (Business Impact): Cyan (#00FFFF) neon outline
- Circle 2 (Incident Response): Neon Pink (#FF00FF) neon outline
- Circle 3 (Prevention): Electric Blue (#0080FF) neon outline
- Intersection: White glow with purple tint
- Background: Deep black (#0A0A0A) with dark purple gradient
---
**Note**: Do not generate the actual image. Output only the prompt for image generation.

View File

@@ -0,0 +1,25 @@
import requests
# 获取曲目信息的函数
def get_recording_info(mbid):
url = f'https://musicbrainz.org/ws/2/recording/{mbid}?fmt=json'
response = requests.get(url)
if response.status_code == 200:
return response.json() # 返回曲目信息
else:
return {'error': 'Not Found', 'status_code': response.status_code}
# 测试 MBID 列表
mbids = [
'a34ecc5d-388e-40fb-a2a2-5354db8fdfaa', # 示例 MBID
'c6f24108-1f3f-4bf7-a52d-818ec956c2de',
'cd2d5cc0-7cfa-4f7c-99f5-4fd05b07873c'
]
# 循环查询每个 MBID
for mbid in mbids:
info = get_recording_info(mbid)
print(f'情報for MBID {mbid}:')
print(info)
print('-' * 40) # 分隔线

View File

@@ -1,256 +1,256 @@
# Wiki Sync 循环流程信息图 - 提示词
> 日期2026-04-20
> 布局circular-flow
> 风格chalkboard
---
## 一、系统提示词
```
Create a professional infographic following these specifications:
## Image Specifications
- **Type**: Infographic
- **Layout**: circular-flow
- **Style**: chalkboard
- **Aspect Ratio**: 16:9 (landscape)
- **Language**: 中文 (Simplified Chinese)
## Core Principles
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use Chinese for all text content
## Layout Guidelines
### Circular Flow Layout
- 循环流程:展示持续运行的过程
- 环形排列:所有节点围绕中心点均布
- 方向箭头:显示顺时针或逆时针流动方向
- 无明确起点/终点(持续循环)
- 中心可放置核心概念或系统名称
### Wiki Sync 循环结构
1. 9个步骤节点均匀分布在圆环上
2. 循环箭头连接各步骤
3. 中心显示 "Wiki Sync" 系统名称
4. 标题位于圆环上方
## Style Guidelines
### Chalkboard Style
- 背景:黑板黑 (#1A1A1A) 或深绿黑板色 (#1C2B1C)
- 纹理:真实黑板质感,伴有划痕、粉尘、橡皮擦痕迹
- 线条手绘粉笔质感不完美的sketchy线条
- 颜色:仅使用粉笔色板
- 字体:手绘粉笔字体
- 装饰:手绘星星、下划线、箭头、圆圈、问号
```
---
## 二、风格锁定提示词
```
## Style Lock — 必须遵守
本信息图使用 chalkboard粉笔黑板风格。
### 背景
- 颜色:#1C2B1C深绿黑板或 #1A1A1A黑板黑
- 纹理:真实黑板纹理,细微划痕、粉尘粒子、橡皮擦痕迹
- 木质框边(可选):手绘木纹线条
### 粉笔线条质量
- 所有线条必须是手绘的、不完美的sketchy风格
- 轻微抖动和歪斜
- 不要干净的矢量线条
### 颜色色板(严格使用)
| 角色 | 颜色 | Hex |
|-----|------|-----|
| 主文字/轮廓 | 粉笔白 | #F5F5F5 |
| 高亮/下划线 | 粉笔黄 | #FFE566 |
| 次要高亮 | 粉笔粉 | #FF9999 |
| 技术元素/图表 | 粉笔蓝 | #66B3FF |
| 成功/自然 | 粉笔绿 | #90EE90 |
| 警告/能量 | 粉笔橙 | #FFB366 |
| 木框 | 棕色 | #8B6914 |
### 装饰元素
- 5-7角手绘星星不规则
- 手绘波浪下划线
- 手绘箭头(歪斜的笔触 + 箭头)
- 角落的粉笔问号、圆圈
- 底部散落的粉笔粉末
### 禁止出现
- 渐变
- 完美几何形状
- 写实元素
- 扁平数字图标
### 布局规则circular-flow
- 9个节点均布于圆环上
- 顺时针方向箭头连接各节点
- 中心放置核心概念
- 标题在圆环顶部
## Layout Guidelines - Circular Flow
### 环形结构
- 步骤节点9个步骤均匀分布在圆周上
- 方向:顺时针流动的箭头
- 中心:系统名称 "Wiki Sync"
- 标题:信息图主标题在顶部
### 节点内容
每个节点包含:
- 步骤编号(圆形框)
- 步骤名称(粉笔白大字)
- 简要描述1-2行较小字体
- 对应图标或符号
### 节点顺序Wiki Sync 流程)
1. Cron 触发
2. 加载 skill
3. 启动 TMUX
4. 启动 Claude Code
5. 发送任务指令
6. 执行 9 步 ingest
7. 解析 SLUG
8. 更新 manifest
9. Telegram 通知
```
---
## 三、具体内容提示词
```
INFOGRAPHIC: Wiki Sync 系统循环流程
## 主标题
- 位置:圆环顶部居中
- 内容:"Wiki Sync 自动同步系统"
- 字体:大型粉笔白(#F5F5F5手写体
- 装饰:粉笔黄(#FFE566双下划线波浪形手绘
- 两侧:粉笔粉(#FF9999小星星装饰
## 圆环中心
- 圆形区域放置系统图标或 "WS" 字样
- 颜色:粉笔蓝(#66B3FF轮廓
- 背景:深绿黑板色 #1C2B1C
## 循环节点(顺时针排列)
### 节点 1Cron 触发
- 编号圆圈:粉笔黄
- 步骤名:① Cron 触发
- 描述定时器启动每15分钟
- 图标:时钟或定时器手绘
### 节点 2加载 Skill
- 编号圆圈:粉笔黄
- 步骤名:② 加载 Skill
- 描述:加载 llm-wiki-sync
- 图标:齿轮或技能图标
### 节点 3检查待摄
- 编号圆圈:粉笔粉
- 步骤名:③ 待摄检查
- 描述sync.py --check
- 图标:文档或清单
### 节点 4启动 TMUX
- 编号圆圈:粉笔粉
- 步骤名:④ 启动 TMUX
- 描述:创建会话
- 图标:终端图标
### 节点 5Claude Code
- 编号圆圈:粉笔蓝
- 步骤名:⑤ Claude Code
- 描述bypassPermissions
- 图标AI/机器人
### 节点 6执行 Ingest
- 编号圆圈:粉笔蓝
- 步骤名:执行 Ingest
- 描述9步标准流程
- 图标:输入箭头
### 节点 7解析 SLUG
- 编号圆圈:粉笔绿
- 步骤名:⑦ 解析 SLUG
- 描述:从输出提取标识符
- 图标:标签/条形码
### 节点 8更新 Manifest
- 编号圆圈:粉笔绿
- 步骤名:更新 Manifest
- 描述:写入 JSON 状态
- 图标:数据保存
### 节点 9Telegram 通知
- 编号圆圈:粉笔橙
- 步骤名: Telegram 通知
- 描述:发送同步结果
- 图标:消息气泡
## 循环箭头
- 颜色:粉笔蓝(#66B3FF
- 风格:手绘歪斜箭头
- 方向:顺时针
- 每两个节点之间
## 底部装饰
- 左侧:粉笔粉 "Claude Code Agent"
- 右侧:粉笔黄 "持续运行 ∞"
- 底部边缘:散落粉笔粉末痕迹
- 角落:粉笔问号、圆圈装饰
## 整体效果
- 深绿黑板背景 (#1C2B1C)
- 所有文字使用粉笔质感手写体
- 节点框使用手绘不规则圆形/圆角矩形
- 箭头使用手绘歪斜线条
- 保持信息密度,流程清晰
```
---
## 四、使用说明
```
使用方法:
1. 使用支持 circular-flow 布局 + chalkboard 风格的 AI 图像生成工具
2. 建议比例16:9 (landscape)
3. 语言:中文(简体)
4. 参考本文件的三部分提示词组合使用
提示词组合优先级:
1. 第一部分(系统提示词)- 基础规范
2. 第二部分(风格锁定)- 必须遵守的约束
3. 具体内容提示词 - 实际图像内容
```
---
# Wiki Sync 循环流程信息图 - 提示词
> 日期2026-04-20
> 布局circular-flow
> 风格chalkboard
---
## 一、系统提示词
```
Create a professional infographic following these specifications:
## Image Specifications
- **Type**: Infographic
- **Layout**: circular-flow
- **Style**: chalkboard
- **Aspect Ratio**: 16:9 (landscape)
- **Language**: 中文 (Simplified Chinese)
## Core Principles
- Follow the layout structure precisely for information architecture
- Apply style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use Chinese for all text content
## Layout Guidelines
### Circular Flow Layout
- 循环流程:展示持续运行的过程
- 环形排列:所有节点围绕中心点均布
- 方向箭头:显示顺时针或逆时针流动方向
- 无明确起点/终点(持续循环)
- 中心可放置核心概念或系统名称
### Wiki Sync 循环结构
1. 9个步骤节点均匀分布在圆环上
2. 循环箭头连接各步骤
3. 中心显示 "Wiki Sync" 系统名称
4. 标题位于圆环上方
## Style Guidelines
### Chalkboard Style
- 背景:黑板黑 (#1A1A1A) 或深绿黑板色 (#1C2B1C)
- 纹理:真实黑板质感,伴有划痕、粉尘、橡皮擦痕迹
- 线条手绘粉笔质感不完美的sketchy线条
- 颜色:仅使用粉笔色板
- 字体:手绘粉笔字体
- 装饰:手绘星星、下划线、箭头、圆圈、问号
```
---
## 二、风格锁定提示词
```
## Style Lock — 必须遵守
本信息图使用 chalkboard粉笔黑板风格。
### 背景
- 颜色:#1C2B1C深绿黑板或 #1A1A1A黑板黑
- 纹理:真实黑板纹理,细微划痕、粉尘粒子、橡皮擦痕迹
- 木质框边(可选):手绘木纹线条
### 粉笔线条质量
- 所有线条必须是手绘的、不完美的sketchy风格
- 轻微抖动和歪斜
- 不要干净的矢量线条
### 颜色色板(严格使用)
| 角色 | 颜色 | Hex |
|-----|------|-----|
| 主文字/轮廓 | 粉笔白 | #F5F5F5 |
| 高亮/下划线 | 粉笔黄 | #FFE566 |
| 次要高亮 | 粉笔粉 | #FF9999 |
| 技术元素/图表 | 粉笔蓝 | #66B3FF |
| 成功/自然 | 粉笔绿 | #90EE90 |
| 警告/能量 | 粉笔橙 | #FFB366 |
| 木框 | 棕色 | #8B6914 |
### 装饰元素
- 5-7角手绘星星不规则
- 手绘波浪下划线
- 手绘箭头(歪斜的笔触 + 箭头)
- 角落的粉笔问号、圆圈
- 底部散落的粉笔粉末
### 禁止出现
- 渐变
- 完美几何形状
- 写实元素
- 扁平数字图标
### 布局规则circular-flow
- 9个节点均布于圆环上
- 顺时针方向箭头连接各节点
- 中心放置核心概念
- 标题在圆环顶部
## Layout Guidelines - Circular Flow
### 环形结构
- 步骤节点9个步骤均匀分布在圆周上
- 方向:顺时针流动的箭头
- 中心:系统名称 "Wiki Sync"
- 标题:信息图主标题在顶部
### 节点内容
每个节点包含:
- 步骤编号(圆形框)
- 步骤名称(粉笔白大字)
- 简要描述1-2行较小字体
- 对应图标或符号
### 节点顺序Wiki Sync 流程)
1. Cron 触发
2. 加载 skill
3. 启动 TMUX
4. 启动 Claude Code
5. 发送任务指令
6. 执行 9 步 ingest
7. 解析 SLUG
8. 更新 manifest
9. Telegram 通知
```
---
## 三、具体内容提示词
```
INFOGRAPHIC: Wiki Sync 系统循环流程
## 主标题
- 位置:圆环顶部居中
- 内容:"Wiki Sync 自动同步系统"
- 字体:大型粉笔白(#F5F5F5手写体
- 装饰:粉笔黄(#FFE566双下划线波浪形手绘
- 两侧:粉笔粉(#FF9999小星星装饰
## 圆环中心
- 圆形区域放置系统图标或 "WS" 字样
- 颜色:粉笔蓝(#66B3FF轮廓
- 背景:深绿黑板色 #1C2B1C
## 循环节点(顺时针排列)
### 节点 1Cron 触发
- 编号圆圈:粉笔黄
- 步骤名:① Cron 触发
- 描述定时器启动每15分钟
- 图标:时钟或定时器手绘
### 节点 2加载 Skill
- 编号圆圈:粉笔黄
- 步骤名:② 加载 Skill
- 描述:加载 llm-wiki-sync
- 图标:齿轮或技能图标
### 节点 3检查待摄
- 编号圆圈:粉笔粉
- 步骤名:③ 待摄检查
- 描述sync.py --check
- 图标:文档或清单
### 节点 4启动 TMUX
- 编号圆圈:粉笔粉
- 步骤名:④ 启动 TMUX
- 描述:创建会话
- 图标:终端图标
### 节点 5Claude Code
- 编号圆圈:粉笔蓝
- 步骤名:⑤ Claude Code
- 描述bypassPermissions
- 图标AI/机器人
### 节点 6执行 Ingest
- 编号圆圈:粉笔蓝
- 步骤名:执行 Ingest
- 描述9步标准流程
- 图标:输入箭头
### 节点 7解析 SLUG
- 编号圆圈:粉笔绿
- 步骤名:⑦ 解析 SLUG
- 描述:从输出提取标识符
- 图标:标签/条形码
### 节点 8更新 Manifest
- 编号圆圈:粉笔绿
- 步骤名:更新 Manifest
- 描述:写入 JSON 状态
- 图标:数据保存
### 节点 9Telegram 通知
- 编号圆圈:粉笔橙
- 步骤名: Telegram 通知
- 描述:发送同步结果
- 图标:消息气泡
## 循环箭头
- 颜色:粉笔蓝(#66B3FF
- 风格:手绘歪斜箭头
- 方向:顺时针
- 每两个节点之间
## 底部装饰
- 左侧:粉笔粉 "Claude Code Agent"
- 右侧:粉笔黄 "持续运行 ∞"
- 底部边缘:散落粉笔粉末痕迹
- 角落:粉笔问号、圆圈装饰
## 整体效果
- 深绿黑板背景 (#1C2B1C)
- 所有文字使用粉笔质感手写体
- 节点框使用手绘不规则圆形/圆角矩形
- 箭头使用手绘歪斜线条
- 保持信息密度,流程清晰
```
---
## 四、使用说明
```
使用方法:
1. 使用支持 circular-flow 布局 + chalkboard 风格的 AI 图像生成工具
2. 建议比例16:9 (landscape)
3. 语言:中文(简体)
4. 参考本文件的三部分提示词组合使用
提示词组合优先级:
1. 第一部分(系统提示词)- 基础规范
2. 第二部分(风格锁定)- 必须遵守的约束
3. 具体内容提示词 - 实际图像内容
```
---
*Generated: 2026-04-20*

View File

@@ -1,148 +1,148 @@
# Wiki Sync 系统信息图提示词
> 生成日期2026-04-20
> 用途:为 LLM Wiki 自动化同步系统生成信息图
---
## 第一部分系统提示词System Prompt
```
Create a professional infographic following these specifications:
## Image Specifications
- Type: Infographic
- Layout: hub-spoke
- Style: corporate-memphis
- Aspect Ratio: 16:9
- Language: 简体中文
## Core Principles
- Follow the hub-spoke layout structure precisely for information architecture
- Apply corporate-memphis style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use simplified Chinese for all text content
## Layout Guidelines
- Prominent central hub at the center of the canvas
- Clear spoke lines radiating outward from center
- Nodes at spoke ends with consistent styling
- Even distribution of spokes around the hub
- Icons representing each spoke item
- Brief labels near each node
## Style Guidelines
- Flat vector illustration style
- Bright, saturated colors: purple, orange, teal, yellow
- White or light pastel background
- Disproportionate human figures (optional)
- Abstract geometric shapes and floating elements
- Solid fills without outlines
- Clean sans-serif typography with bold headings
- Professional but friendly appearance
---
Generate the infographic based on the content below:
Text labels (in 简体中文):
```
---
## 第二部分风格锁定提示词Style Lock Prompt
```
## 布局规范hub-spoke
- 中心:突出显示核心主题"Wiki Sync 自动化同步系统"
- 辐射6条均匀分布的辐条连接中心与外圈节点
- 节点:每个辐条末端放置一个组件节点,使用统一的图标风格
- 连接:辐条使用浅灰色线条,带有标签
## 视觉元素corporate-memphis
- 背景:纯白色或浅奶油色 (#FFF8F0)
- 主色:明快的紫色 (#8B5CF6)、橙色 (#F97316)、青绿色 (#14B8A6)、黄色 (#FBBF24)
- 图形:扁平化几何图形填充,无描边
- 人物:抽象的不成比例人物剪影
- 装饰:漂浮的几何元素(圆形、方形、三角形)
- 字体:思源黑体或类似无衬线字体,标题加粗
## 图标风格
- 简化的线框图标
- 保持一致的线条粗细2-3px
- 圆角处理
```
---
## 第三部分具体内容提示词Content Prompt
```
INFOGRAPHIC: Wiki Sync 自动化同步系统
使用 hub-spoke 布局和 corporate-memphis 风格创建信息图。
## 中心主题Hub
- 主标题Wiki Sync 系统
- 副标题LLM Wiki 自动化同步解决方案
## 六个辐条节点Spokes
### 1. 核心组件Core
- 图标:齿轮/工具箱
- 标签sync.py CLI
- 描述manifest 管理、文件追踪
### 2. 工作流Workflow
- 图标:流程图/箭头
- 标签llm-wiki-sync
- 描述9步标准化执行流程
### 3. 定时任务Schedule
- 图标:时钟/日历
- 标签Cron Job
- 描述15分钟自动触发
### 4. 状态追踪Tracking
- 图标:清单/勾选
- 标签manifest.json
- 描述181篇文件状态记录
### 5. 交互模式Interaction
- 图标:终端/命令行
- 标签TMUX + Claude
- 描述bypassPermissions 启动
### 6. 交付通知Delivery
- 图标:消息/ telegram
- 标签Telegram Bot
- 描述5038825565 消息推送
## 底部补充信息
- 当前状态:已摄入 16 篇 / 待摄入 165 篇
- 关键规则:顺序执行 + SLUG 解析
```
---
## 使用说明
1.**第一部分** 作为系统提示词设置到图像生成模型会话开始时
2.**第二部分** 作为风格参考添加到提示词中
3.**第三部分** 作为具体内容提示词发送给图像生成模型
4. 生成的图像应呈现:
- 白色/浅奶油色背景
- 中心为"Wiki Sync 系统"标题
- 6个均匀分布的辐条节点
- 明快的紫、橙、青、黄配色
- 扁平化几何图标
# Wiki Sync 系统信息图提示词
> 生成日期2026-04-20
> 用途:为 LLM Wiki 自动化同步系统生成信息图
---
## 第一部分系统提示词System Prompt
```
Create a professional infographic following these specifications:
## Image Specifications
- Type: Infographic
- Layout: hub-spoke
- Style: corporate-memphis
- Aspect Ratio: 16:9
- Language: 简体中文
## Core Principles
- Follow the hub-spoke layout structure precisely for information architecture
- Apply corporate-memphis style aesthetics consistently throughout
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives
- Keep information concise, highlight keywords and core concepts
- Use ample whitespace for visual clarity
- Maintain clear visual hierarchy
## Text Requirements
- All text must match the specified style treatment
- Main titles should be prominent and readable
- Key concepts should be visually emphasized
- Labels should be clear and appropriately sized
- Use simplified Chinese for all text content
## Layout Guidelines
- Prominent central hub at the center of the canvas
- Clear spoke lines radiating outward from center
- Nodes at spoke ends with consistent styling
- Even distribution of spokes around the hub
- Icons representing each spoke item
- Brief labels near each node
## Style Guidelines
- Flat vector illustration style
- Bright, saturated colors: purple, orange, teal, yellow
- White or light pastel background
- Disproportionate human figures (optional)
- Abstract geometric shapes and floating elements
- Solid fills without outlines
- Clean sans-serif typography with bold headings
- Professional but friendly appearance
---
Generate the infographic based on the content below:
Text labels (in 简体中文):
```
---
## 第二部分风格锁定提示词Style Lock Prompt
```
## 布局规范hub-spoke
- 中心:突出显示核心主题"Wiki Sync 自动化同步系统"
- 辐射6条均匀分布的辐条连接中心与外圈节点
- 节点:每个辐条末端放置一个组件节点,使用统一的图标风格
- 连接:辐条使用浅灰色线条,带有标签
## 视觉元素corporate-memphis
- 背景:纯白色或浅奶油色 (#FFF8F0)
- 主色:明快的紫色 (#8B5CF6)、橙色 (#F97316)、青绿色 (#14B8A6)、黄色 (#FBBF24)
- 图形:扁平化几何图形填充,无描边
- 人物:抽象的不成比例人物剪影
- 装饰:漂浮的几何元素(圆形、方形、三角形)
- 字体:思源黑体或类似无衬线字体,标题加粗
## 图标风格
- 简化的线框图标
- 保持一致的线条粗细2-3px
- 圆角处理
```
---
## 第三部分具体内容提示词Content Prompt
```
INFOGRAPHIC: Wiki Sync 自动化同步系统
使用 hub-spoke 布局和 corporate-memphis 风格创建信息图。
## 中心主题Hub
- 主标题Wiki Sync 系统
- 副标题LLM Wiki 自动化同步解决方案
## 六个辐条节点Spokes
### 1. 核心组件Core
- 图标:齿轮/工具箱
- 标签sync.py CLI
- 描述manifest 管理、文件追踪
### 2. 工作流Workflow
- 图标:流程图/箭头
- 标签llm-wiki-sync
- 描述9步标准化执行流程
### 3. 定时任务Schedule
- 图标:时钟/日历
- 标签Cron Job
- 描述15分钟自动触发
### 4. 状态追踪Tracking
- 图标:清单/勾选
- 标签manifest.json
- 描述181篇文件状态记录
### 5. 交互模式Interaction
- 图标:终端/命令行
- 标签TMUX + Claude
- 描述bypassPermissions 启动
### 6. 交付通知Delivery
- 图标:消息/ telegram
- 标签Telegram Bot
- 描述5038825565 消息推送
## 底部补充信息
- 当前状态:已摄入 16 篇 / 待摄入 165 篇
- 关键规则:顺序执行 + SLUG 解析
```
---
## 使用说明
1.**第一部分** 作为系统提示词设置到图像生成模型会话开始时
2.**第二部分** 作为风格参考添加到提示词中
3.**第三部分** 作为具体内容提示词发送给图像生成模型
4. 生成的图像应呈现:
- 白色/浅奶油色背景
- 中心为"Wiki Sync 系统"标题
- 6个均匀分布的辐条节点
- 明快的紫、橙、青、黄配色
- 扁平化几何图标
- 思源黑体加粗标题

View File

@@ -1,272 +1,272 @@
# Wiki Sync 系统搭建完整记录
> 日期2026-04-16
> 作者Hermes Agent
> 用途:记录 llm-wiki-agent 自动化同步系统的完整搭建过程
---
## 背景
用户希望将 Obsidian vault 中的 markdown 文件批量摄入到 LLM WikiKarpathy's LLM Wiki中。原有方案是手动逐篇执行效率低下。本次目标搭建自动化系统实现定时自动摄入。
---
## 系统架构
### 架构图
![Wiki Sync Architecture](IMG-20260418081458052.png)
### 同步时序图
![Wiki Sync Sequence Diagram](IMG-20260418081458100.png)
### Ingest 9 步流程图
![[IMG-20260418081458161.png]]
### 核心组件
| 组件 | 位置 | 职责 |
|------|------|------|
| **llm-wiki-sync skill** | `~/.hermes/skills/research/llm-wiki-sync/SKILL.md` | 执行模板和工作流定义 |
| **sync.py** | `/Users/weishen/Git/llm-wiki-agent/tools/sync.py` | manifest 管理、CLI 工具 |
| **manifest.json** | `/Users/weishen/Git/llm-wiki-agent/tools/manifest.json` | 文件状态追踪181篇 |
| **Hermes Cron Job** | 内部调度器 | 每 15 分钟触发一次摄入 |
## 一、初始探索(手动执行阶段)
### 1.1 发现 raw 文件
- 路径:`/Users/weishen/Workspace/nexus/raw/`Obsidian vault
- 通过 symlink 挂载到:`/Users/weishen/Git/llm-wiki-agent/raw/`
- 文件数182 个 markdown 文件
### 1.2 创建 manifest.json
手动扫描 raw 目录,生成初始 manifest
```
# 扫描 raw 目录,提取 frontmatter
for md in Path("raw").glob("**/*.md"):
# 提取 title, ingested, slug 等字段
manifest.append({...})
```
### 1.3 测试 /wiki-ingest
Claude Code 的 `/wiki-ingest` 命令执行 9 步流程:
读取 source 文档
读取 wiki/index.md 和 overview.md
生成 wiki/sources/<slug>.md
更新 wiki/index.md
更新 wiki/overview.md
创建/更新 Entity 页面
创建/更新 Concept 页面
检测并记录冲突
追加 wiki/log.md
## 二、遇到的问题和解决方案
### 问题 1stdin 交互问题
**现象**`claude --print` 模式在非交互环境下无法正常工作stdin 被占用导致命令卡住。
**解决方案**:使用 TMUX 交互模式启动 Claude Code
```bash
# 启动 TMUX session
tmux new-session -d -s wiki-ingest "cd /Users/weishen/Git/llm-wiki-agent && claude --permission-mode bypassPermissions"
# 等待启动完成
sleep 8 && tmux send-keys -t wiki-ingest Enter
# 发送任务
tmux send-keys -t wiki-ingest "请执行任务..."
```
### 问题 2manifest slug 与实际文件不匹配
**现象**manifest 中记录的 slug 和 source_path 与 LLM 实际生成的文件名不一致。
**原因**LLM 根据内容自动生成 slug而不是简单从文件名转换。
**解决方案**:要求 LLM 在任务完成后输出 `SLUG: xxx`,然后 Hermes 解析并更新 manifest
```python
def parse_slug_from_output(output: str) -> str:
"""从 LLM 输出中解析实际使用的 slug"""
match = re.search(r'SLUG:\s*([^\s]+)', output)
return match.group(1) if match else None
```
### 问题 3170 条 ingest 错误记录
**现象**manifest 中有 170 条记录标记为 `ingested=true` 但实际未成功。
**原因**:早期测试时使用 `--print` 模式失败但仍标记为成功。
**解决方案**:使用 `sync.py --reset-failed` 清理错误状态。
---
## 三、自建组件
### 3.1 sync.py CLI 工具
**位置**`/Users/weishen/Git/llm-wiki-agent/tools/sync.py`
**功能**
- `--pending`:列出待摄取文件
- `--check`:预览变化
- `--reset-failed`:重置失败记录
- `--bootstrap`:从现有 wiki sources 重建 manifest
**核心函数**
```python
def get_pending_files() -> list:
"""返回所有未摄入的文件"""
def mark_ingested(file_path: str, slug: str):
"""标记文件为已摄入"""
def reset_failed():
"""重置所有失败状态"""
def parse_slug_from_output(output: str) -> str:
"""从 LLM 输出解析 SLUG"""
```
### 3.2 llm-wiki-sync skill
**位置**`~/.hermes/skills/research/llm-wiki-sync/SKILL.md`
**版本**1.4.0
**核心内容**
- **角色分工**Hermes 编排流程 → Claude Code 执行 ingest
- **关键设计**TMUX 交互模式、顺序执行、SLUG 输出要求
- **TMUX 执行流程**:完整的启动、发送任务、监控、清理流程
- **Ingest Workflow 9 步**:标准化执行步骤
- **Cron Job 自动化**:使用 Hermes 原生 cron job
### 3.3 Hermes Cron Job
**创建命令**
```bash
cronjob create \
--name wiki-sync-15min \
--skill llm-wiki-sync \
--schedule "*/15 * * * *" \
--repeat 999999 \
--deliver "telegram:5038825565" \
--prompt "使用 llm-wiki-sync 技能执行一次 wiki 文章摄入..."
```
**配置**
- Job ID`98265b6998c5`
- Schedule`*/15 * * * *`(每 15 分钟)
- 交付方式TelegramID: 5038825565
- 技能llm-wiki-sync
---
## 四、执行流程(自动化阶段)
### 4.1 Cron Job 触发
1. 每 15 分钟00, 15, 30, 45 分)触发
2. 加载 llm-wiki-sync skill
3. 执行 skill 中的 prompt
### 4.2 实际执行步骤
```
1. 加载 llm-wiki-sync 技能
2. 检查 manifestpython tools/sync.py --pending
3. 启动 TMUX session
4. 启动 Claude CodebypassPermissions
5. 发送任务指令(含 SLUG 输出要求)
6. 监控任务完成tmux capture-pane
7. 解析 SLUG 并更新 manifest.json
8. 清理 TMUX session
9. 输出结果(自动发往 Telegram
```
### 4.3 示例输出
```
## Wiki Sync 完成
| 项目 | 结果 |
|------|------|
| 摄入文件 | raw/Home Office/用Docker中安装Navidrome.md |
| Slug | 用docker中安装navidrome |
| 状态 | ✅ 已完成 |
新增页面:
- wiki/sources/用docker中安装navidrome.md
- wiki/entities/Navidrome.md
- wiki/concepts/Docker-Compose.md
manifest 已更新ingested: true
剩余待摄入165 篇
```
---
## 五、当前状态
| 指标 | 值 |
|------|-----|
| 总文件数 | 181 篇 |
| 已摄入 | 16 篇 |
| 待摄入 | 165 篇 |
| Cron Job 状态 | 运行中 |
| 下次运行 | 18:15:00 |
---
## 六、关键命令速查
```bash
# 检查待摄取文件
cd /Users/weishen/Git/llm-wiki-agent && python tools/sync.py --pending
# 预览变化
python tools/sync.py --check
# 重置失败记录
python tools/sync.py --reset-failed
# 查看 cron job 状态
cronjob --list
# 手动触发 cron job
cronjob --run <job_id>
```
---
## 七、注意事项
1. **必须使用 TMUX**:不能用 subprocess 或 --print 模式
2. **必须顺序执行**:并发会触发 529 rate limit
3. **必须解析 SLUG**LLM 输出的实际 slug 用于更新 manifest
4. **交付方式**:使用 `telegram:5038825565` 发给用户
5. **保留 orphan**:不删除任何原始数据
---
## 八、扩展方向
- [ ] 添加错误重试机制529 时等待后重试)
- [ ] 支持批量摄入(改为每次 3-5 篇)
- [ ] 添加 webhook 通知(不只是 Telegram
- [ ] 统计摄入速率和成功率
---
# Wiki Sync 系统搭建完整记录
> 日期2026-04-16
> 作者Hermes Agent
> 用途:记录 llm-wiki-agent 自动化同步系统的完整搭建过程
---
## 背景
用户希望将 Obsidian vault 中的 markdown 文件批量摄入到 LLM WikiKarpathy's LLM Wiki中。原有方案是手动逐篇执行效率低下。本次目标搭建自动化系统实现定时自动摄入。
---
## 系统架构
### 架构图
![Wiki Sync Architecture](IMG-20260418081458052.png)
### 同步时序图
![Wiki Sync Sequence Diagram](IMG-20260418081458100.png)
### Ingest 9 步流程图
![[IMG-20260418081458161.png]]
### 核心组件
| 组件 | 位置 | 职责 |
|------|------|------|
| **llm-wiki-sync skill** | `~/.hermes/skills/research/llm-wiki-sync/SKILL.md` | 执行模板和工作流定义 |
| **sync.py** | `/Users/weishen/Git/llm-wiki-agent/tools/sync.py` | manifest 管理、CLI 工具 |
| **manifest.json** | `/Users/weishen/Git/llm-wiki-agent/tools/manifest.json` | 文件状态追踪181篇 |
| **Hermes Cron Job** | 内部调度器 | 每 15 分钟触发一次摄入 |
## 一、初始探索(手动执行阶段)
### 1.1 发现 raw 文件
- 路径:`/Users/weishen/Workspace/nexus/raw/`Obsidian vault
- 通过 symlink 挂载到:`/Users/weishen/Git/llm-wiki-agent/raw/`
- 文件数182 个 markdown 文件
### 1.2 创建 manifest.json
手动扫描 raw 目录,生成初始 manifest
```
# 扫描 raw 目录,提取 frontmatter
for md in Path("raw").glob("**/*.md"):
# 提取 title, ingested, slug 等字段
manifest.append({...})
```
### 1.3 测试 /wiki-ingest
Claude Code 的 `/wiki-ingest` 命令执行 9 步流程:
读取 source 文档
读取 wiki/index.md 和 overview.md
生成 wiki/sources/<slug>.md
更新 wiki/index.md
更新 wiki/overview.md
创建/更新 Entity 页面
创建/更新 Concept 页面
检测并记录冲突
追加 wiki/log.md
## 二、遇到的问题和解决方案
### 问题 1stdin 交互问题
**现象**`claude --print` 模式在非交互环境下无法正常工作stdin 被占用导致命令卡住。
**解决方案**:使用 TMUX 交互模式启动 Claude Code
```bash
# 启动 TMUX session
tmux new-session -d -s wiki-ingest "cd /Users/weishen/Git/llm-wiki-agent && claude --permission-mode bypassPermissions"
# 等待启动完成
sleep 8 && tmux send-keys -t wiki-ingest Enter
# 发送任务
tmux send-keys -t wiki-ingest "请执行任务..."
```
### 问题 2manifest slug 与实际文件不匹配
**现象**manifest 中记录的 slug 和 source_path 与 LLM 实际生成的文件名不一致。
**原因**LLM 根据内容自动生成 slug而不是简单从文件名转换。
**解决方案**:要求 LLM 在任务完成后输出 `SLUG: xxx`,然后 Hermes 解析并更新 manifest
```python
def parse_slug_from_output(output: str) -> str:
"""从 LLM 输出中解析实际使用的 slug"""
match = re.search(r'SLUG:\s*([^\s]+)', output)
return match.group(1) if match else None
```
### 问题 3170 条 ingest 错误记录
**现象**manifest 中有 170 条记录标记为 `ingested=true` 但实际未成功。
**原因**:早期测试时使用 `--print` 模式失败但仍标记为成功。
**解决方案**:使用 `sync.py --reset-failed` 清理错误状态。
---
## 三、自建组件
### 3.1 sync.py CLI 工具
**位置**`/Users/weishen/Git/llm-wiki-agent/tools/sync.py`
**功能**
- `--pending`:列出待摄取文件
- `--check`:预览变化
- `--reset-failed`:重置失败记录
- `--bootstrap`:从现有 wiki sources 重建 manifest
**核心函数**
```python
def get_pending_files() -> list:
"""返回所有未摄入的文件"""
def mark_ingested(file_path: str, slug: str):
"""标记文件为已摄入"""
def reset_failed():
"""重置所有失败状态"""
def parse_slug_from_output(output: str) -> str:
"""从 LLM 输出解析 SLUG"""
```
### 3.2 llm-wiki-sync skill
**位置**`~/.hermes/skills/research/llm-wiki-sync/SKILL.md`
**版本**1.4.0
**核心内容**
- **角色分工**Hermes 编排流程 → Claude Code 执行 ingest
- **关键设计**TMUX 交互模式、顺序执行、SLUG 输出要求
- **TMUX 执行流程**:完整的启动、发送任务、监控、清理流程
- **Ingest Workflow 9 步**:标准化执行步骤
- **Cron Job 自动化**:使用 Hermes 原生 cron job
### 3.3 Hermes Cron Job
**创建命令**
```bash
cronjob create \
--name wiki-sync-15min \
--skill llm-wiki-sync \
--schedule "*/15 * * * *" \
--repeat 999999 \
--deliver "telegram:5038825565" \
--prompt "使用 llm-wiki-sync 技能执行一次 wiki 文章摄入..."
```
**配置**
- Job ID`98265b6998c5`
- Schedule`*/15 * * * *`(每 15 分钟)
- 交付方式TelegramID: 5038825565
- 技能llm-wiki-sync
---
## 四、执行流程(自动化阶段)
### 4.1 Cron Job 触发
1. 每 15 分钟00, 15, 30, 45 分)触发
2. 加载 llm-wiki-sync skill
3. 执行 skill 中的 prompt
### 4.2 实际执行步骤
```
1. 加载 llm-wiki-sync 技能
2. 检查 manifestpython tools/sync.py --pending
3. 启动 TMUX session
4. 启动 Claude CodebypassPermissions
5. 发送任务指令(含 SLUG 输出要求)
6. 监控任务完成tmux capture-pane
7. 解析 SLUG 并更新 manifest.json
8. 清理 TMUX session
9. 输出结果(自动发往 Telegram
```
### 4.3 示例输出
```
## Wiki Sync 完成
| 项目 | 结果 |
|------|------|
| 摄入文件 | raw/Home Office/用Docker中安装Navidrome.md |
| Slug | 用docker中安装navidrome |
| 状态 | ✅ 已完成 |
新增页面:
- wiki/sources/用docker中安装navidrome.md
- wiki/entities/Navidrome.md
- wiki/concepts/Docker-Compose.md
manifest 已更新ingested: true
剩余待摄入165 篇
```
---
## 五、当前状态
| 指标 | 值 |
|------|-----|
| 总文件数 | 181 篇 |
| 已摄入 | 16 篇 |
| 待摄入 | 165 篇 |
| Cron Job 状态 | 运行中 |
| 下次运行 | 18:15:00 |
---
## 六、关键命令速查
```bash
# 检查待摄取文件
cd /Users/weishen/Git/llm-wiki-agent && python tools/sync.py --pending
# 预览变化
python tools/sync.py --check
# 重置失败记录
python tools/sync.py --reset-failed
# 查看 cron job 状态
cronjob --list
# 手动触发 cron job
cronjob --run <job_id>
```
---
## 七、注意事项
1. **必须使用 TMUX**:不能用 subprocess 或 --print 模式
2. **必须顺序执行**:并发会触发 529 rate limit
3. **必须解析 SLUG**LLM 输出的实际 slug 用于更新 manifest
4. **交付方式**:使用 `telegram:5038825565` 发给用户
5. **保留 orphan**:不删除任何原始数据
---
## 八、扩展方向
- [ ] 添加错误重试机制529 时等待后重试)
- [ ] 支持批量摄入(改为每次 3-5 篇)
- [ ] 添加 webhook 通知(不只是 Telegram
- [ ] 统计摄入速率和成功率
---
*End of Note*

View File

@@ -1,263 +1,263 @@
## SYSTEM PROMPT (Set this once at the start of the Gemini session)
```
You are an infographic generation assistant. Your job is to create 6 chalkboard-style
infographic cards that form a complete visual guide.
== GLOBAL STYLE RULES (apply to EVERY card, no exceptions) ==
Background:
- Dark green-black chalkboard: #1C2B1C
- Realistic chalkboard texture with subtle scratches, dust particles, faint eraser smudge marks
- Wooden frame border on all cards (hand-drawn wood grain lines in chalk brown/tan)
- NO gradients, NO perfect geometric shapes, NO photorealistic elements
Chalk Lines & Quality:
- ALL lines must be hand-drawn, imperfect, sketchy — slight wobble and variation
- Lines should look like real white/colored chalk on a blackboard
- NO clean digital vectors, NO sharp vector paths
Color Palette (strict — use ONLY these exact hex values):
- Chalk White: #F5F5F5 (main text, outlines)
- Chalk Yellow: #FFE566 (highlights, emphasis, underlines)
- Chalk Pink: #FF9999 (secondary highlights, icons)
- Chalk Blue: #66B3FF (diagrams, technical elements)
- Chalk Green: #90EE90 (success, nature, positive)
- Chalk Orange: #FFB366 (warnings, energy)
- Frame Brown: #8B6914 (wooden frame, hand-drawn)
Doodles & Decorative Elements:
- Small hand-drawn stars (5-7 points, imperfect)
- Hand-drawn underlines (slightly wavy)
- Hand-drawn arrows (sketchy shaft + arrowhead)
- Hand-drawn circles/ovals around key terms
- Hand-drawn checkmarks
- Scattered chalk dust particles near bottom/sides
Typography:
- All text hand-drawn chalk lettering style
- Imperfect baseline (letters slightly off horizontal)
- Mix of uppercase headers and lowercase body text for authenticity
- Visible chalk texture on letters
== CARD STRUCTURE (identical for all 6 cards) ==
Each card follows this layout:
┌──────────────────────────────────────────┐
│ [WOODEN FRAME BORDER] │
│ ┌────────────────────────────────────┐ │
│ │ CARD TITLE (large, chalk white) │ │
│ │ ~~underlined with accent color~~ │ │
│ ├────────────────────────────────────┤ │
│ │ [SECTION 1] │ [SECTION 2 if any] │ │
│ │ Header color │ │ │
│ │ Bullets │ │ │
│ │ Icon │ │ │
│ ├────────────────────────────────────┤ │
│ │ [Additional sections as needed] │ │
│ │ [Decorative doodles in corners] │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
== CONSISTENCY RULES ==
1. Generate Card 1 first, send it to me
2. For Card 26, EXPLICITLY include this instruction:
"Follow the exact same chalkboard style as the previous card —
same background #1C2B1C, same chalk dust texture, same hand-drawn
line quality, same color hex values (#F5F5F5, #FFE566, #FF9999,
#66B3FF, #90EE90, #FFB366), same wooden frame border, same doodle
elements. Do NOT deviate from this style."
3. Aspect ratio: 16:9 for all cards
4. Each card should visually "belong" to the same set
== HOW TO USE THESE PROMPTS ==
1. Copy the SYSTEM PROMPT above and paste it at the start of your Gemini session
2. Then copy Prompt 1 and send it to Gemini Image Gen (Card 1)
3. Once Card 1 is generated, copy Prompt 2 but FIRST include the STYLE LOCK BLOCK
4. Repeat for all 6 cards, always referencing the previous card's style
5. Review each generated image: if chalk line quality or colors deviate,
regenerate with stronger style enforcement
```
---
## STYLE LOCK BLOCK (Prepend this to Prompts 26)
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
```
---
## CARD 1 — Saleable & Security
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
---
INFOGRAPHIC CARD 1: Saleable & Security
Create a single infographic card in chalkboard style with a dark green-black
background (#1C2B1C), realistic chalk dust texture, subtle eraser smudge marks,
and a wooden frame border with hand-drawn wood grain lines.
Card is 16:9 aspect ratio. All elements must look hand-drawn with real chalk —
imperfect sketchy lines, slight wobble, no clean vectors.
TITLE SECTION:
- "Saleable & Security" in large hand-drawn chalk white (#F5F5F5) uppercase
lettering, centered at top
- Double underline in chalk yellow (#FFE566), slightly wavy hand-drawn lines
- Small hand-drawn star doodles on each side of the title
TWO-COLUMN LAYOUT below title:
LEFT COLUMN — "Saleable" (header in chalk pink #FF9999, hand-drawn rectangle bar):
• Complete product definition in Control Tower
• SKUs clearly defined
• License generation strategy complete
Bullet markers: small chalk pink circles
Icon: hand-drawn chalk sketch of a product box with a small price tag label,
in chalk yellow on white outline
RIGHT COLUMN — "Security" (header in chalk blue #66B3FF, hand-drawn rectangle bar):
• Application self-defensibility capability
• WAF rules to protect cloud apps
• Defend against incorrect usage (accidental & purposeful)
Bullet markers: small chalk blue circles
Icon: hand-drawn chalk shield with a simple checkmark inside, outline in
chalk white, fill in semi-transparent chalk blue
FOOTER DECORATION:
- A hand-drawn chalk dividing line across the card width
- Two small doodle checkmarks at bottom right in chalk green (#90EE90)
- Scattered chalk dust particles along the bottom edge
NO gradients, NO sharp geometric shapes, NO flat digital icons.
```
## ![IMG-20260418172056603.png](app://77d327b60ef00764e25eb25542b49a223773/D:/Workspace/nexus/Asset/Attachment/raw/AI/%E5%A6%82%E4%BD%95%E8%AE%A9AI%E7%94%9F%E6%88%90%E9%A3%8E%E6%A0%BC%E4%B8%80%E8%87%B4%E7%9A%84%E5%9B%BE%E7%89%87/IMG-20260418172056603.png?1776503642747)
## CARD 2 — Cloud Deployment & Configuration
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
---
INFOGRAPHIC CARD 2: Cloud Deployment & Configuration
Create a single infographic card in chalkboard style with a dark green-black
background (#1A1A1A), realistic chalk dust texture, subtle eraser smudge marks,
and a wooden frame border with hand-drawn wood grain lines.
Card is 16:9 aspect ratio. All elements must look hand-drawn with real chalk —
imperfect sketchy lines, slight wobble, no clean vectors.
TITLE SECTION:
- "Cloud Deployment & Configuration" in large hand-drawn chalk white (#F5F5F5)
uppercase lettering, centered at top
- Underline in chalk green (#90EE90), hand-drawn wavy line
- Small star doodle on left side of title
MAIN CONTENT AREA:
Header bar: "Deployment Requirements" in chalk blue (#66B3FF) hand-drawn
rectangle
Bullet list (chalk white text, chalk yellow bullet markers):
✔ Fully automated cloud platform deployment
✔ Web / API enabled configuration management
✔ All feature & functional configs through API interface
✔ Tenant capability enablement
Sub-section header: "Configuration Management" in chalk pink (#FF9999)
HAND-DRAWN FLOWCHART (center of card):
Three chalk blue boxes connected by sketchy chalk arrows:
[Cloud Platform] --arrow--> [API Gateway] --arrow--> [Tenant Config]
Each box: hand-drawn rectangle with slightly wavy edges, white #F5F5F5 outline
Text inside boxes: chalk white
Arrows: hand-drawn with slight wobble, chalk blue
ADDITIONAL ELEMENT:
Hand-drawn stick figure engineer icon (simple, chalk white) on the right side
holding a small chalk-drawn tablet/device
CORNER DOODLES:
- Top right: small hand-drawn cloud shape labeled "SaaS" in chalk orange (#FFB366)
- Bottom left: chalk pink circle with "API" text inside
- Scattered chalk dust particles near the wooden frame
NO gradients, NO sharp geometry, NO digital-looking elements.
```
## How to Use This File
```
SEQUENCE:
1. Gemini session start → paste the SYSTEM PROMPT
2. Send CARD 1 prompt → receive Card 1 image
3. Paste CARD 2 prompt (it includes STYLE LOCK BLOCK) → receive Card 2
4. Repeat for Cards 36
VERIFICATION after each card:
- Does background look like #1C2B1C dark green-black chalkboard? ✓/✗
- Do all lines look hand-drawn/sketchy? ✓/✗
- Are colors using the exact hex values? ✓/✗
- Is there a wooden frame border? ✓/✗
- Are doodles (stars, underlines, arrows) hand-drawn? ✓/✗
- Does it match the previous card visually? ✓/✗
If any check fails → regenerate with stronger style enforcement.
## SYSTEM PROMPT (Set this once at the start of the Gemini session)
```
You are an infographic generation assistant. Your job is to create 6 chalkboard-style
infographic cards that form a complete visual guide.
== GLOBAL STYLE RULES (apply to EVERY card, no exceptions) ==
Background:
- Dark green-black chalkboard: #1C2B1C
- Realistic chalkboard texture with subtle scratches, dust particles, faint eraser smudge marks
- Wooden frame border on all cards (hand-drawn wood grain lines in chalk brown/tan)
- NO gradients, NO perfect geometric shapes, NO photorealistic elements
Chalk Lines & Quality:
- ALL lines must be hand-drawn, imperfect, sketchy — slight wobble and variation
- Lines should look like real white/colored chalk on a blackboard
- NO clean digital vectors, NO sharp vector paths
Color Palette (strict — use ONLY these exact hex values):
- Chalk White: #F5F5F5 (main text, outlines)
- Chalk Yellow: #FFE566 (highlights, emphasis, underlines)
- Chalk Pink: #FF9999 (secondary highlights, icons)
- Chalk Blue: #66B3FF (diagrams, technical elements)
- Chalk Green: #90EE90 (success, nature, positive)
- Chalk Orange: #FFB366 (warnings, energy)
- Frame Brown: #8B6914 (wooden frame, hand-drawn)
Doodles & Decorative Elements:
- Small hand-drawn stars (5-7 points, imperfect)
- Hand-drawn underlines (slightly wavy)
- Hand-drawn arrows (sketchy shaft + arrowhead)
- Hand-drawn circles/ovals around key terms
- Hand-drawn checkmarks
- Scattered chalk dust particles near bottom/sides
Typography:
- All text hand-drawn chalk lettering style
- Imperfect baseline (letters slightly off horizontal)
- Mix of uppercase headers and lowercase body text for authenticity
- Visible chalk texture on letters
== CARD STRUCTURE (identical for all 6 cards) ==
Each card follows this layout:
┌──────────────────────────────────────────┐
│ [WOODEN FRAME BORDER] │
│ ┌────────────────────────────────────┐ │
│ │ CARD TITLE (large, chalk white) │ │
│ │ ~~underlined with accent color~~ │ │
│ ├────────────────────────────────────┤ │
│ │ [SECTION 1] │ [SECTION 2 if any] │ │
│ │ Header color │ │ │
│ │ Bullets │ │ │
│ │ Icon │ │ │
│ ├────────────────────────────────────┤ │
│ │ [Additional sections as needed] │ │
│ │ [Decorative doodles in corners] │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
== CONSISTENCY RULES ==
1. Generate Card 1 first, send it to me
2. For Card 26, EXPLICITLY include this instruction:
"Follow the exact same chalkboard style as the previous card —
same background #1C2B1C, same chalk dust texture, same hand-drawn
line quality, same color hex values (#F5F5F5, #FFE566, #FF9999,
#66B3FF, #90EE90, #FFB366), same wooden frame border, same doodle
elements. Do NOT deviate from this style."
3. Aspect ratio: 16:9 for all cards
4. Each card should visually "belong" to the same set
== HOW TO USE THESE PROMPTS ==
1. Copy the SYSTEM PROMPT above and paste it at the start of your Gemini session
2. Then copy Prompt 1 and send it to Gemini Image Gen (Card 1)
3. Once Card 1 is generated, copy Prompt 2 but FIRST include the STYLE LOCK BLOCK
4. Repeat for all 6 cards, always referencing the previous card's style
5. Review each generated image: if chalk line quality or colors deviate,
regenerate with stronger style enforcement
```
---
## STYLE LOCK BLOCK (Prepend this to Prompts 26)
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
```
---
## CARD 1 — Saleable & Security
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
---
INFOGRAPHIC CARD 1: Saleable & Security
Create a single infographic card in chalkboard style with a dark green-black
background (#1C2B1C), realistic chalk dust texture, subtle eraser smudge marks,
and a wooden frame border with hand-drawn wood grain lines.
Card is 16:9 aspect ratio. All elements must look hand-drawn with real chalk —
imperfect sketchy lines, slight wobble, no clean vectors.
TITLE SECTION:
- "Saleable & Security" in large hand-drawn chalk white (#F5F5F5) uppercase
lettering, centered at top
- Double underline in chalk yellow (#FFE566), slightly wavy hand-drawn lines
- Small hand-drawn star doodles on each side of the title
TWO-COLUMN LAYOUT below title:
LEFT COLUMN — "Saleable" (header in chalk pink #FF9999, hand-drawn rectangle bar):
• Complete product definition in Control Tower
• SKUs clearly defined
• License generation strategy complete
Bullet markers: small chalk pink circles
Icon: hand-drawn chalk sketch of a product box with a small price tag label,
in chalk yellow on white outline
RIGHT COLUMN — "Security" (header in chalk blue #66B3FF, hand-drawn rectangle bar):
• Application self-defensibility capability
• WAF rules to protect cloud apps
• Defend against incorrect usage (accidental & purposeful)
Bullet markers: small chalk blue circles
Icon: hand-drawn chalk shield with a simple checkmark inside, outline in
chalk white, fill in semi-transparent chalk blue
FOOTER DECORATION:
- A hand-drawn chalk dividing line across the card width
- Two small doodle checkmarks at bottom right in chalk green (#90EE90)
- Scattered chalk dust particles along the bottom edge
NO gradients, NO sharp geometric shapes, NO flat digital icons.
```
## ![IMG-20260418172056603.png](app://77d327b60ef00764e25eb25542b49a223773/D:/Workspace/nexus/Asset/Attachment/raw/AI/%E5%A6%82%E4%BD%95%E8%AE%A9AI%E7%94%9F%E6%88%90%E9%A3%8E%E6%A0%BC%E4%B8%80%E8%87%B4%E7%9A%84%E5%9B%BE%E7%89%87/IMG-20260418172056603.png?1776503642747)
## CARD 2 — Cloud Deployment & Configuration
```
== STYLE LOCK — MANDATORY ==
This card MUST follow the EXACT same chalkboard style as the previously
generated card. Do not deviate.
Checklist — verify these match the previous card BEFORE generating:
□ Background color: #1C2B1C (dark green-black chalkboard)
□ Chalk texture: subtle scratches, dust, eraser smudges
□ Line quality: hand-drawn, imperfect, sketchy wobble — NO perfect vectors
□ Color hex values: #F5F5F5 (white), #FFE566 (yellow), #FF9999 (pink),
#66B3FF (blue), #90EE90 (green), #FFB366 (orange)
□ Frame: wooden border with hand-drawn wood grain
□ Doodles: stars, underlines, arrows, circles — all chalk-drawn
□ Typography: chalk lettering, imperfect baseline, chalk texture on letters
If ANY element does not match, regenerate with corrections.
---
INFOGRAPHIC CARD 2: Cloud Deployment & Configuration
Create a single infographic card in chalkboard style with a dark green-black
background (#1A1A1A), realistic chalk dust texture, subtle eraser smudge marks,
and a wooden frame border with hand-drawn wood grain lines.
Card is 16:9 aspect ratio. All elements must look hand-drawn with real chalk —
imperfect sketchy lines, slight wobble, no clean vectors.
TITLE SECTION:
- "Cloud Deployment & Configuration" in large hand-drawn chalk white (#F5F5F5)
uppercase lettering, centered at top
- Underline in chalk green (#90EE90), hand-drawn wavy line
- Small star doodle on left side of title
MAIN CONTENT AREA:
Header bar: "Deployment Requirements" in chalk blue (#66B3FF) hand-drawn
rectangle
Bullet list (chalk white text, chalk yellow bullet markers):
✔ Fully automated cloud platform deployment
✔ Web / API enabled configuration management
✔ All feature & functional configs through API interface
✔ Tenant capability enablement
Sub-section header: "Configuration Management" in chalk pink (#FF9999)
HAND-DRAWN FLOWCHART (center of card):
Three chalk blue boxes connected by sketchy chalk arrows:
[Cloud Platform] --arrow--> [API Gateway] --arrow--> [Tenant Config]
Each box: hand-drawn rectangle with slightly wavy edges, white #F5F5F5 outline
Text inside boxes: chalk white
Arrows: hand-drawn with slight wobble, chalk blue
ADDITIONAL ELEMENT:
Hand-drawn stick figure engineer icon (simple, chalk white) on the right side
holding a small chalk-drawn tablet/device
CORNER DOODLES:
- Top right: small hand-drawn cloud shape labeled "SaaS" in chalk orange (#FFB366)
- Bottom left: chalk pink circle with "API" text inside
- Scattered chalk dust particles near the wooden frame
NO gradients, NO sharp geometry, NO digital-looking elements.
```
## How to Use This File
```
SEQUENCE:
1. Gemini session start → paste the SYSTEM PROMPT
2. Send CARD 1 prompt → receive Card 1 image
3. Paste CARD 2 prompt (it includes STYLE LOCK BLOCK) → receive Card 2
4. Repeat for Cards 36
VERIFICATION after each card:
- Does background look like #1C2B1C dark green-black chalkboard? ✓/✗
- Do all lines look hand-drawn/sketchy? ✓/✗
- Are colors using the exact hex values? ✓/✗
- Is there a wooden frame border? ✓/✗
- Are doodles (stars, underlines, arrows) hand-drawn? ✓/✗
- Does it match the previous card visually? ✓/✗
If any check fails → regenerate with stronger style enforcement.
```

View File

@@ -1,51 +1,51 @@
---
title: 养龙虾5天血泪史我的AI Agent为什么总失忆OpenClaw 记忆调试全记录
type: metaphor
palette: warm
rendering: digital
text: title-subtitle
mood: balanced
---
请为这篇文章生成封面图。
## 视觉概念
以"金鱼记忆"为核心隐喻:一条卡通金鱼游在透明的水缸中,金鱼的大脑位置是一个空白的问号框架,周围漂浮着记忆碎片(文字泡泡、对话气泡、代码片段),碎片正在逐渐消散到缸外。底部有简单的齿轮和调试工具元素,暗示"调试"过程。
## 主视觉元素
- 金鱼:卡通风格,圆形身体,大眼睛,尾巴呈波浪形游动姿态
- 空白大脑:空心问号形状,位于金鱼头部位置
- 记忆碎片5-6个椭圆形气泡包含模糊的文字轮廓、对话符号、代码片段
- 水缸:简单几何圆形边框,内部有微妙的涟漪效果
- 调试工具:底部角落有小型齿轮、螺丝刀、代码括号图标
## 配色方案warm palette
- 主色:温暖橙色 #F5A623(用于金鱼身体)
- 辅色:柔和珊瑚色 #FF8C74(用于记忆碎片)
- 强调色:深琥珀色 #C47A2B(用于鱼鳍和调试工具)
- 背景:米白色 #FFF8F0 到浅杏色 #FFE8D6 渐变
- 文字色:深棕色 #4A3728
## 渲染风格digital
- 干净的矢量线条,精确边缘
- 平滑渐变,无粗糙纹理
- 轻微阴影创造层次感
- 像专业UI插图一样现代简洁
## 文字布局
- 标题:"养龙虾5天血泪史" 使用大号粗体字(金鱼上方右侧)
- 副标题:"我的AI Agent为什么总失忆" 使用较小字号(标题下方)
- 字体:现代无衬线体,清晰易读
- 文字颜色:深棕色 #4A3728
## 氛围balanced
- 中等对比度
- 正常饱和度
- 视觉重量平衡
---
title: 养龙虾5天血泪史我的AI Agent为什么总失忆OpenClaw 记忆调试全记录
type: metaphor
palette: warm
rendering: digital
text: title-subtitle
mood: balanced
---
请为这篇文章生成封面图。
## 视觉概念
以"金鱼记忆"为核心隐喻:一条卡通金鱼游在透明的水缸中,金鱼的大脑位置是一个空白的问号框架,周围漂浮着记忆碎片(文字泡泡、对话气泡、代码片段),碎片正在逐渐消散到缸外。底部有简单的齿轮和调试工具元素,暗示"调试"过程。
## 主视觉元素
- 金鱼:卡通风格,圆形身体,大眼睛,尾巴呈波浪形游动姿态
- 空白大脑:空心问号形状,位于金鱼头部位置
- 记忆碎片5-6个椭圆形气泡包含模糊的文字轮廓、对话符号、代码片段
- 水缸:简单几何圆形边框,内部有微妙的涟漪效果
- 调试工具:底部角落有小型齿轮、螺丝刀、代码括号图标
## 配色方案warm palette
- 主色:温暖橙色 #F5A623(用于金鱼身体)
- 辅色:柔和珊瑚色 #FF8C74(用于记忆碎片)
- 强调色:深琥珀色 #C47A2B(用于鱼鳍和调试工具)
- 背景:米白色 #FFF8F0 到浅杏色 #FFE8D6 渐变
- 文字色:深棕色 #4A3728
## 渲染风格digital
- 干净的矢量线条,精确边缘
- 平滑渐变,无粗糙纹理
- 轻微阴影创造层次感
- 像专业UI插图一样现代简洁
## 文字布局
- 标题:"养龙虾5天血泪史" 使用大号粗体字(金鱼上方右侧)
- 副标题:"我的AI Agent为什么总失忆" 使用较小字号(标题下方)
- 字体:现代无衬线体,清晰易读
- 文字颜色:深棕色 #4A3728
## 氛围balanced
- 中等对比度
- 正常饱和度
- 视觉重量平衡
- 温暖友好但不强烈

View File

@@ -1,22 +1,22 @@
## 信息图任务指令
请用**claude-code-infographic-build** 这个技能为`Hermes/xingzhi/Hermes自定义技能说明` 这篇笔记生成信息图。
- 语言:英文
- 布局circular-flow
- 风格chalkboard
- 比例16:9
## 封面图任务指令
请用**claude-code-executor**技能启动Claude Code
使用 **baoyu-cover-image** 技能为以下内容生成封面图提示词:
1. 读取 Hermes/xingzhi/wiki-sync-setup-2026-04-16.md 这篇笔记
2. 用**baoyu-cover-image** 生成一张封面图
- **类型 (Type)**`hero``conceptual``typography``metaphor``scene``minimal`
- **配色 (Palette)**`warm``elegant``cool``dark``earth``vivid``pastel``mono``retro`
- **渲染 (Rendering)**`flat-vector``hand-drawn``painterly``digital``pixel``chalk`
- **文字 (Text)**`none``title-only`(默认)、`title-subtitle``text-rich`
- **氛围 (Mood)**`subtle``balanced`(默认)、`bold`
3. 不要生成图片,只输出提示词
## 信息图任务指令
请用**claude-code-infographic-build** 这个技能为`Hermes/xingzhi/Hermes自定义技能说明` 这篇笔记生成信息图。
- 语言:英文
- 布局circular-flow
- 风格chalkboard
- 比例16:9
## 封面图任务指令
请用**claude-code-executor**技能启动Claude Code
使用 **baoyu-cover-image** 技能为以下内容生成封面图提示词:
1. 读取 Hermes/xingzhi/wiki-sync-setup-2026-04-16.md 这篇笔记
2. 用**baoyu-cover-image** 生成一张封面图
- **类型 (Type)**`hero``conceptual``typography``metaphor``scene``minimal`
- **配色 (Palette)**`warm``elegant``cool``dark``earth``vivid``pastel``mono``retro`
- **渲染 (Rendering)**`flat-vector``hand-drawn``painterly``digital``pixel``chalk`
- **文字 (Text)**`none``title-only`(默认)、`title-subtitle``text-rich`
- **氛围 (Mood)**`subtle``balanced`(默认)、`bold`
3. 不要生成图片,只输出提示词
4. 输出提示词到 Hermes/xingzhi/ 新建一个笔记

View File

@@ -1,127 +1,127 @@
**副标题**:如何把每一份笔记,通过 llm-wiki-sync 自动分析与提炼成结构化的页面、实体与概念,以便长期检索与复用。
---
## 前言与来源说明
本方案借鉴并整合了几条重要线索:
- Andrej Karpathy 对“LLM Wiki”理念的阐述将知识以可被大模型直接消费的结构化方式保存https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f
- 我们的实现基于开源项目 SamurAI 的 llm-wiki-agenthttps://github.com/SamurAIGPT/llm-wiki-agent在其基础上扩展了企业化的 ingest 流程、Cron 调度与 Hermes skillllm-wiki-sync
- 最终静态化展示使用 Quartzhttps://github.com/jackyzha0/quartz把生成的 wiki 内容导出为可浏览、可分享的静态站点。
下面重点介绍 llm-wiki-sync 如何把一篇笔记做结构化分析与提炼并用仓库中的实例wiki/sources/RTO-vs-RPO-Key-Differences-for-Modern-Disaster-Recovery作为逐项说明。
---
## llm-wiki-sync 的目标与核心能力
核心目标把原始文档raw/)自动转成结构化的 wiki source 页面抽取关键要素Summary、Key Claims、Key Quotes、Key Concepts、Key Entities、Connections并记录 ingest 日志、差异与审计信息,供后续检索、合成与内容再生产使用。
关键能力:
- 文本解析与语义压缩:把长文本压缩为 24 句高密度 summary。
- 声明抽取claim extraction识别文中明确的结论与可验证断言。
- 实体与概念抽取NER + linking识别人名/公司/工具/概念,并把它们标准化为 wiki 实体页([[Name]])。
- 关系发现connections把句子级别的语义关系转成图边A → depends_on → B
- 模板化输出固定页面头frontmatter+ 标准段落Summary / Key Claims / Quotes / Concepts / Entities / Connections / Contradictions
- 审计与可回滚:每次 ingest 都写入 wiki/log.md并可通过 git/checkpoint 回滚变更。
实现技术栈要点Hermesskill 调用、Claude Code / agent可选、llm-wiki-agent 基础脚本、以及最终的静态化工具 Quartz。
---
## 从笔记到 Source Page
仓库中的源页面wiki/sources/RTO-vs-RPO-Key-Differences-for-Modern-Disaster-Recovery.md
下面逐项展示 llm-wiki-sync 针对该文档所做的提取结果(摘自生成的 source 页面):
1) SummarySummary
- 核心主题RTO恢复时间目标与 RPO恢复点目标的定义、区别及在现代持续交付中的应用
- 问题域:灾难恢复规划、发布风险管控
- 方法/机制:通过 Feature Flag 实现秒级 RTO 和低 RPO
- 结论/价值预防优于恢复Feature Flag 将部署事故从灾难转为非事件
说明Summary 由模型将整篇文章的主旨、问题背景、关键方法与结论压缩为 24 条,便于快速检索与索引。
2) Key Claims断言提取
- RTO 衡量系统恢复速度:允许的最大停机时间
- RPO 衡量数据保护:可接受的最大数据丢失量
- 传统灾备聚焦硬件故障,现代风险更多来自代码变更(部署 bug、数据库迁移、AI 模型更新等)
- Feature Flag 将 RTO 从小时级降至秒级,同时保护 RPO
- 应用分层策略Critical / Important / Nice-to-have对应不同的 RTO/RPO 指标
说明断言提取用于建立事实层fact layer后续可自动化做一致性检查与冲突检测Contradictions 段)。
3) Key Quotes关键引用
- “RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose.”
- “Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users.”
说明:保留可引用原句,便于在后续合成(如写作、演讲稿)中引用来源。
4) Key Concepts概念抽取
- RTORecovery Time Objective
- RPORecovery Point Objective
- Feature Flag特性开关
- Kill Switch紧急关闭机制
- 渐进式发布Gradual Rollout
说明:概念会被标准化为 wiki 的 concept 页面wiki/concepts/),用于聚合所有提到该概念的 source 页面。
5) Key Entities实体抽取
- LaunchDarklyFeature Flag 管理平台)
- HP示例企业
- Christian Dior示例企业 — 文档中作为案例提及)
说明:实体会被规范化为 wiki/entities/ 下的页面,并且 source 页面会在 sources 列表保留原始链接与引用。
6) Connections关系构建
- RTO ← depends_on ← Feature Flag
- RPO ← depends_on ← Feature Flag
- LaunchDarkly → provides → Feature Flag
- Feature Flag ← enables ← 渐进式发布
说明Connections 用于图谱构建graph/graph.json后续能在可视化页面graph.html展示节点与边。
7) Contradictions冲突检测
- 当前文档无明显与现有 wiki 冲突的声明若检测到冲突llm-wiki-sync 会把冲突条目列出并标注来源,供人工审查。
![[IMG-20260420160439552.png|872]]
![[IMG-20260420160439600.png]]
---
## llm-wiki-sync 的典型运行步骤(工程视角)
1. 读取 raw/<path>,解析 frontmatter/元数据(若缺失则询回填)。
2. 检索 wiki/index.md 与 overview.md获取上下文避免重复 ingest
3. 用 LLM 生成 Source PageSummary / Key Claims / Quotes / Concepts / Entities / Connections / Contradictions
4. 将结果写入 wiki/sources/<slug>.md并更新 wiki/index.md、wiki/overview.md如有新实体/概念也生成对应页面草稿。
5. 记录日志:在 wiki/log.md 追加 ingest 记录(时间、文件、摘要、状态)。
6. 可选:触发 graph 重建(增量或全量),并把 graph/graph.json、graph.html 更新到站点。
7. 通知:完成后通过 Hermes 的通知机制deliver=origin 或 Telegram 指定 chat告知负责人。
并发与配额注意:单文件 ingest 优先,批量操作分批(每批 2~3 篇);对接外部 agent/Claude Code 时避免并发超配额。
审计与回滚:每次 ingest 前执行 git branch 或 checkpoint如需回滚可用 git revert 或恢复 checkpoint。
---
## 总结与扩展
- llm-wiki-sync 把 Karpathy 关于 LLM Wiki 的理念落地为可执行的工程流程:把知识以结构化表征保存,使得大模型既是“读者”也是“执行者”。
- 在 Obsidian 中可以直接通过关系图graph view查看笔记间的关联在 llm-wiki-agent 中可以通过 wiki-graph 构建并在 graph.html / graph/graph.json 中可视化展示。
- 我们的实现基于 SamurAI 的 llm-wiki-agent并在其上加入了企业级的同步、审计与 Hermes skill 封装,最终通过 Quartz 静态站把生成的 wiki 内容对外展示与分享。
---
## Infographic Asset
![llm-wiki-sync Circular Flow Infographic](IMG-20260420160439662.png)
**Infographic**: The Knowledge Pipeline Cycle — circular flow showing how llm-wiki-sync transforms scattered notes into a reusable knowledge base.
- Layout: circular-flow (7-stage cycle)
- Style: chalkboard (dark background, hand-drawn chalk accents)
- Aspect ratio: 16:9
- Prompt file: `infographic/llm-wiki-sync-circular-flow/prompts/infographic.md`
- Image: `llm-wiki-sync-circular-flow-infographic.png`
**副标题**:如何把每一份笔记,通过 llm-wiki-sync 自动分析与提炼成结构化的页面、实体与概念,以便长期检索与复用。
---
## 前言与来源说明
本方案借鉴并整合了几条重要线索:
- Andrej Karpathy 对“LLM Wiki”理念的阐述将知识以可被大模型直接消费的结构化方式保存https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f
- 我们的实现基于开源项目 SamurAI 的 llm-wiki-agenthttps://github.com/SamurAIGPT/llm-wiki-agent在其基础上扩展了企业化的 ingest 流程、Cron 调度与 Hermes skillllm-wiki-sync
- 最终静态化展示使用 Quartzhttps://github.com/jackyzha0/quartz把生成的 wiki 内容导出为可浏览、可分享的静态站点。
下面重点介绍 llm-wiki-sync 如何把一篇笔记做结构化分析与提炼并用仓库中的实例wiki/sources/RTO-vs-RPO-Key-Differences-for-Modern-Disaster-Recovery作为逐项说明。
---
## llm-wiki-sync 的目标与核心能力
核心目标把原始文档raw/)自动转成结构化的 wiki source 页面抽取关键要素Summary、Key Claims、Key Quotes、Key Concepts、Key Entities、Connections并记录 ingest 日志、差异与审计信息,供后续检索、合成与内容再生产使用。
关键能力:
- 文本解析与语义压缩:把长文本压缩为 24 句高密度 summary。
- 声明抽取claim extraction识别文中明确的结论与可验证断言。
- 实体与概念抽取NER + linking识别人名/公司/工具/概念,并把它们标准化为 wiki 实体页([[Name]])。
- 关系发现connections把句子级别的语义关系转成图边A → depends_on → B
- 模板化输出固定页面头frontmatter+ 标准段落Summary / Key Claims / Quotes / Concepts / Entities / Connections / Contradictions
- 审计与可回滚:每次 ingest 都写入 wiki/log.md并可通过 git/checkpoint 回滚变更。
实现技术栈要点Hermesskill 调用、Claude Code / agent可选、llm-wiki-agent 基础脚本、以及最终的静态化工具 Quartz。
---
## 从笔记到 Source Page
仓库中的源页面wiki/sources/RTO-vs-RPO-Key-Differences-for-Modern-Disaster-Recovery.md
下面逐项展示 llm-wiki-sync 针对该文档所做的提取结果(摘自生成的 source 页面):
1) SummarySummary
- 核心主题RTO恢复时间目标与 RPO恢复点目标的定义、区别及在现代持续交付中的应用
- 问题域:灾难恢复规划、发布风险管控
- 方法/机制:通过 Feature Flag 实现秒级 RTO 和低 RPO
- 结论/价值预防优于恢复Feature Flag 将部署事故从灾难转为非事件
说明Summary 由模型将整篇文章的主旨、问题背景、关键方法与结论压缩为 24 条,便于快速检索与索引。
2) Key Claims断言提取
- RTO 衡量系统恢复速度:允许的最大停机时间
- RPO 衡量数据保护:可接受的最大数据丢失量
- 传统灾备聚焦硬件故障,现代风险更多来自代码变更(部署 bug、数据库迁移、AI 模型更新等)
- Feature Flag 将 RTO 从小时级降至秒级,同时保护 RPO
- 应用分层策略Critical / Important / Nice-to-have对应不同的 RTO/RPO 指标
说明断言提取用于建立事实层fact layer后续可自动化做一致性检查与冲突检测Contradictions 段)。
3) Key Quotes关键引用
- “RTO is about speed: how fast you get back online. RPO is about data: how much you can afford to lose.”
- “Deploy != Release. Feature flags change this. You can deploy code to production without releasing it to users.”
说明:保留可引用原句,便于在后续合成(如写作、演讲稿)中引用来源。
4) Key Concepts概念抽取
- RTORecovery Time Objective
- RPORecovery Point Objective
- Feature Flag特性开关
- Kill Switch紧急关闭机制
- 渐进式发布Gradual Rollout
说明:概念会被标准化为 wiki 的 concept 页面wiki/concepts/),用于聚合所有提到该概念的 source 页面。
5) Key Entities实体抽取
- LaunchDarklyFeature Flag 管理平台)
- HP示例企业
- Christian Dior示例企业 — 文档中作为案例提及)
说明:实体会被规范化为 wiki/entities/ 下的页面,并且 source 页面会在 sources 列表保留原始链接与引用。
6) Connections关系构建
- RTO ← depends_on ← Feature Flag
- RPO ← depends_on ← Feature Flag
- LaunchDarkly → provides → Feature Flag
- Feature Flag ← enables ← 渐进式发布
说明Connections 用于图谱构建graph/graph.json后续能在可视化页面graph.html展示节点与边。
7) Contradictions冲突检测
- 当前文档无明显与现有 wiki 冲突的声明若检测到冲突llm-wiki-sync 会把冲突条目列出并标注来源,供人工审查。
![[IMG-20260420160439552.png|872]]
![[IMG-20260420160439600.png]]
---
## llm-wiki-sync 的典型运行步骤(工程视角)
1. 读取 raw/<path>,解析 frontmatter/元数据(若缺失则询回填)。
2. 检索 wiki/index.md 与 overview.md获取上下文避免重复 ingest
3. 用 LLM 生成 Source PageSummary / Key Claims / Quotes / Concepts / Entities / Connections / Contradictions
4. 将结果写入 wiki/sources/<slug>.md并更新 wiki/index.md、wiki/overview.md如有新实体/概念也生成对应页面草稿。
5. 记录日志:在 wiki/log.md 追加 ingest 记录(时间、文件、摘要、状态)。
6. 可选:触发 graph 重建(增量或全量),并把 graph/graph.json、graph.html 更新到站点。
7. 通知:完成后通过 Hermes 的通知机制deliver=origin 或 Telegram 指定 chat告知负责人。
并发与配额注意:单文件 ingest 优先,批量操作分批(每批 2~3 篇);对接外部 agent/Claude Code 时避免并发超配额。
审计与回滚:每次 ingest 前执行 git branch 或 checkpoint如需回滚可用 git revert 或恢复 checkpoint。
---
## 总结与扩展
- llm-wiki-sync 把 Karpathy 关于 LLM Wiki 的理念落地为可执行的工程流程:把知识以结构化表征保存,使得大模型既是“读者”也是“执行者”。
- 在 Obsidian 中可以直接通过关系图graph view查看笔记间的关联在 llm-wiki-agent 中可以通过 wiki-graph 构建并在 graph.html / graph/graph.json 中可视化展示。
- 我们的实现基于 SamurAI 的 llm-wiki-agent并在其上加入了企业级的同步、审计与 Hermes skill 封装,最终通过 Quartz 静态站把生成的 wiki 内容对外展示与分享。
---
## Infographic Asset
![llm-wiki-sync Circular Flow Infographic](IMG-20260420160439662.png)
**Infographic**: The Knowledge Pipeline Cycle — circular flow showing how llm-wiki-sync transforms scattered notes into a reusable knowledge base.
- Layout: circular-flow (7-stage cycle)
- Style: chalkboard (dark background, hand-drawn chalk accents)
- Aspect ratio: 16:9
- Prompt file: `infographic/llm-wiki-sync-circular-flow/prompts/infographic.md`
- Image: `llm-wiki-sync-circular-flow-infographic.png`

View File

@@ -0,0 +1,65 @@
# 自动分析和完善 MP3 信息的指南
## 1. 提取音频特征和元数据
使用音频指纹技术(如 **AcoustID**)来获取 MP3 文件的特征和元数据。
- **安装 Chromaprint** 通过以下命令安装 Chromaprint。
```bash
sudo apt-get install chromaprint
```
- **生成音频指纹** 使用 `fpcalc` 命令生成 MP3 文件的指纹。
```bash
fpcalc yourfile.mp3
```
## 2. 调用 AcoustID API
使用生成的指纹调用 AcoustID API获取音频文件的音乐信息。示例请求
```bash
curl -X GET "https://api.acoustid.org/v2/lookup?client=YOUR_ACOUSTID_API_KEY&format=json&duration=DURATION&fingerprint=YOUR_FINGERPRINT"
```
## 3. 从 AcoustID 获取 MusicBrainz ID
- AcoustID 的响应将包含与该指纹匹配的 MusicBrainz IDMBID、艺术家、专辑等信息。
## 4. 使用 MusicBrainz API 获取详细信息
使用获取的 MBID 查询详细的音乐信息:
```bash
GET https://musicbrainz.org/ws/2/artist/ARTIST_MBID?fmt=json
```
## 5. 更新 MP3 的元数据
使用 Python 的 `mutagen` 库更新 MP3 文件的元数据。示例代码如下:
```python
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, TIT2, TPE1, TALB
def update_metadata(file_path, title, artist, album):
audio = MP3(file_path, ID3=ID3)
audio['TIT2'] = TIT2(encoding=3, text=title) # 标题
audio['TPE1'] = TPE1(encoding=3, text=artist) # 艺术家
audio['TALB'] = TALB(encoding=3, text=album) # 专辑
audio.save()
# 调用示例
update_metadata('yourfile.mp3', 'Correct Title', 'Correct Artist', 'Correct Album')
```
## 6. 添加专辑图片信息
可以使用 `mutagen` 库来在 MP3 文件中添加专辑图片:
```python
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC
def add_album_art(mp3_file, image_file):
audio = MP3(mp3_file, ID3=ID3)
with open(image_file, 'rb') as img_file:
img_data = img_file.read()
audio['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=img_data)
audio.save()
# 调用示例
add_album_art('yourfile.mp3', 'cover_image.jpg')
```
## 7. 结论
此方法提供了一种系统化的方式来自动分析 MP3 文件并完善文件的信息,通过音乐指纹识别、使用 API 获取数据以及更新元数据的结合,实现音乐库的维护和信息准确性。

View File

@@ -0,0 +1,193 @@
---
title: CUE + WAV 分割与高质量 MP3 转换
date: 2026-05-20
tags: [audio, cue, wav, mp3, ffmpeg, linux]
---
# CUE + WAV 分割与高质量 MP3 转换
这篇笔记记录如何在命令行下,把一张专辑常见的 `cue + wav` 形式,切分成单曲,并转换成高质量 MP3。
## 一、基本概念
- `cue`:曲目索引与标签信息,记录每首歌的开始时间、标题、歌手等。
- `wav`:整张专辑的无损音频文件。
- 典型场景:一张专辑只有一个大 WAV 文件,加一个 CUE 文件。
目标:
1. 按 CUE 的时间点切分成单轨。
2. 转成高质量 MP3。
3. 尽量保留标题、歌手、专辑等标签。
## 二、推荐工具
在 Ubuntu 上建议安装:
```bash
sudo apt update
sudo apt install cuetools shntool lame ffmpeg
```
说明:
- `cuetools`:提供 `cuebreakpoints``cuetag`
- `shntool`:负责按断点切分音频
- `ffmpeg`:负责编码成 MP3
- `lame`MP3 编码器ffmpeg 也会调用到它的能力
## 三、按 CUE 切分 WAV
### 方式 1先切分再转码
```bash
cuebreakpoints "album.cue" | shnsplit -o wav "album.wav"
```
结果:
- 生成 `01.wav``02.wav``03.wav` ……
- 每个文件对应 CUE 里的一个曲目
### 注意
- `cue` 文件中引用的 wav 文件名,必须和实际文件名一致。
- 如果文件名不一致,先修正 CUE 里的 `FILE` 行,或把 wav 文件重命名。
- 中文文件名一般没问题,但终端编码和 shell 引号要保持正确。
## 四、转换为高质量 MP3
### 方案 AVBR 高质量,推荐
```bash
for f in *.wav; do
ffmpeg -y -i "$f" -codec:a libmp3lame -q:a 0 "${f%.wav}.mp3"
done
```
说明:
- `-q:a 0` 表示最高质量的 VBR 档位之一
- 一般适合日常听歌和保留尽可能好的音质
### 方案 B固定 320k
```bash
for f in *.wav; do
ffmpeg -y -i "$f" -codec:a libmp3lame -b:a 320k "${f%.wav}.mp3"
done
```
说明:
- 码率固定,体积更可预测
- 如果你偏好统一规格,可以选这个
## 五、把 CUE 标签写回 MP3
切分并转码后,可以把 CUE 里的标签批量写进 MP3
```bash
cuetag "album.cue" *.mp3
```
通常会把以下信息写入:
- 曲名
- 专辑名
- 歌手
- 轨道号
## 六、一键脚本
下面是一个可直接使用的一键脚本:
```bash
#!/usr/bin/env bash
set -euo pipefail
CUE="$1"
WAV="$2"
OUTDIR="${3:-output}"
mkdir -p "$OUTDIR"
cd "$OUTDIR"
cuebreakpoints "$CUE" | shnsplit -o wav "$WAV"
for f in *.wav; do
ffmpeg -y -i "$f" -codec:a libmp3lame -q:a 0 "${f%.wav}.mp3"
done
cuetag "$CUE" *.mp3
```
用法:
```bash
chmod +x split_cue_to_mp3.sh
./split_cue_to_mp3.sh "album.cue" "album.wav"
```
## 七、实战建议
### 1. 先保留无损源文件
MP3 即使是高质量编码,依然是有损格式。建议保留:
- 原始 WAV
- 原始 CUE
- 转换后的 MP3
### 2. 输出目录独立管理
不要直接在源目录操作,建议每张专辑一个输出目录,便于回溯。
### 3. 中文文件名注意引号
所有路径都用双引号包裹:
```bash
"我的专辑.cue"
"我的专辑.wav"
```
### 4. 出现标签错位时先检查 CUE
如果曲名或分轨偏移不对,先检查:
- `INDEX 01` 时间点是否正确
- `FILE` 行是否指向正确的 wav 文件
- wav 是否已经被裁剪过
## 八、常见问题
### 1. cuebreakpoints 报错找不到文件
说明 CUE 中的文件名和实际文件名不一致。
### 2. shnsplit 生成的文件顺序不对
先检查 CUE 时间轴是否写错,或者原始 WAV 是否有静默开头/结尾。
### 3. ffmpeg 转码失败
检查是否安装了 `ffmpeg``libmp3lame` 支持。
### 4. cuetag 没有写入标签
确认:
- CUE 文件语法正确
- MP3 文件名和轨道对应关系未被破坏
## 九、推荐命令组合
如果只想快速执行,最常用的组合是:
```bash
cuebreakpoints "album.cue" | shnsplit -o wav "album.wav"
for f in *.wav; do ffmpeg -y -i "$f" -codec:a libmp3lame -q:a 0 "${f%.wav}.mp3"; done
cuetag "album.cue" *.mp3
```
## 十、结论
如果目标是“命令行下稳定地把 cue+wav 专辑切成高质量 mp3”推荐流程是
1. `cuebreakpoints + shnsplit` 切轨
2. `ffmpeg + libmp3lame -q:a 0` 转码
3. `cuetag` 回写标签
这套组合简单、稳定、可批处理,适合长期使用。

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,222 @@
---
title: django-tenants 完整配置指南
created: 2026-04-21
tags: [django, django-tenants, postgresql, saas, multi-tenant]
category: 技术笔记
---
# django-tenants 完整配置指南
## 一、安装依赖
pip install django-tenants psycopg2-binary django-jazzmin
## 二、项目目录结构
myproject/
├── config/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ └── wsgi.py
├── apps/
│ ├── tenants/
│ ├── subscription/
│ ├── accounts/
│ ├── listings/
│ ├── clients/
│ └── showings/
├── manage.py
└── requirements.txt
## 三、核心 Model租户与域名
- Company 继承 TenantMixin
- Domain 继承 DomainMixin
- 每一个中介公司 = 一个租户 = 一个独立 PostgreSQL Schema
- 每个公司可绑定多个域名/子域名
## 四、Settings 完整配置
关键点:
- SHARED_APPS 放公共 Schema 应用
- TENANT_APPS 放租户私有应用
- TENANT_MODEL = "tenants.Company"
- TENANT_DOMAIN_MODEL = "tenants.Domain"
- DATABASES 使用 django_tenants.postgresql_backend
- DATABASE_ROUTERS 使用 TenantSyncRouter
- TenantMainMiddleware 必须第一个
- ROOT_URLCONF / PUBLIC_SCHEMA_URLCONF 分离
- AUTH_USER_MODEL = accounts.User
## 五、URL 路由拆分
- config/urls_public.py公共域名、官网、注册、登录、超级管理后台
- config/urls_tenant.py租户子域名、租户后台、房源/客源/带看/员工模块
## 六、自定义 User Model跨租户关键
- User 继承 AbstractUser
- role 支持平台超管、公司管理员、门店经理、资深经纪人、经纪人、实习经纪人
- Branch 作为门店模型
## 七、初始化与常用命令
- createdb realestate_saas
- python manage.py migrate_schemas --shared
- python manage.py createsuperuser
- python manage.py shell 创建 Company 与 Domain
示例:
- schema_name = zuoan
- domain = zuoan.localhost
- 访问 http://zuoan.localhost:8000/admin/ 进入专属后台
## 八、本地开发配置hosts 文件)
- 127.0.0.1 localhost
- 127.0.0.1 zuoan.localhost
- 127.0.0.1 lianhe.localhost
- 127.0.0.1 xincheng.localhost
开发环境要点:
- ALLOWED_HOSTS 包含 .localhost
- 本地不用 HTTPS
## 九、数据隔离验证
使用 schema_context 切换 schema验证 Listing 等数据互相隔离。
## 下一步建议
推荐顺序:
1. 先做房源/客源/带看完整数据模型
2. 再做 Django Admin 深度定制Jazzmin 主题)
3. 最后补三级权限体系(总部/门店/经纪人)
---
# 上海房产中介 SaaS 系统规划
## 一、多租户架构选型
Django 多租户有三种主流方案,针对这个场景推荐独立 Schema 方案,也就是基于 PostgreSQL Schema 隔离的 django-tenants。
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 共享 Schema | 每张表加 tenant_id 字段 | 简单,运维成本低 | 数据隔离风险高 | 小规模、低安全需求 |
| 独立 Schema | PostgreSQL Schema 隔离 | 隔离好,性能佳 | 稍复杂 | 中介 SaaS推荐 |
| 独立数据库 | 每租户独立 DB | 最高隔离 | 运维成本极高 | 超高安全需求 |
推荐使用 django-tenants
pip install django-tenants
## 二、整体系统模块规划
SaaS 平台层:
- 租户注册
- 套餐管理
- 计费
- 超级后台
各中介公司 Tenant
- 房源管理
- 客源管理
- 员工 / 权限管理
- 带看管理
- 合同管理
- 报表 / BI 看板
- 渠道推广
- 财务佣金
- 消息 / 通知中心
核心 Django App 拆分:
- tenants租户管理公共 Schema
- accounts用户 / 员工 / 角色权限
- listings房源管理核心
- clients客源 / 客户跟进
- showings带看记录
- contracts合同管理
- commissions佣金 / 财务
- reports数据报表
- notifications消息通知
- subscription套餐订阅公共 Schema
## 三、房源模块数据模型(核心)
Listing 关键字段包括:
- 基础信息title、listing_type、status
- 上海地址结构district、street、community、building、floor、unit
- 房屋属性area、inner_area、layout、orientation、decoration、floor_total
- 价格price、price_unit
- 归属agent、source、exclusive
- 证件certificate_no
- 时间created_at、updated_at
## 四、技术栈推荐
后端:
- Django 5.x + DRF
- django-tenants
- django-guardian
- celery + redis
- django-filter
- PostgreSQL 15+
前端路径:
- 路径 ADjango Admin + 定制,最快上手,适合 MVP
- 路径 BHTMX + Alpine.js + Tailwind推荐中期方案
- 路径 CVue3 / React + DRF长期推荐
建议:先 A再 B最后 C。
## 五、租户路由设计
通过子域名区分租户:
- company-a.yourapp.com
- company-b.yourapp.com
- admin.yourapp.com
核心设置:
- TENANT_MODEL = "tenants.Company"
- TENANT_DOMAIN_MODEL = "tenants.Domain"
- SHARED_APPS 放公共应用
- TENANT_APPS 放租户私有应用
## 六、开发阶段规划
Phase 11-2 月MVP
- 租户注册 / 登录
- 房源 CRUD + 图片上传
- 客源基础管理
- Django Admin 后台
Phase 22-3 月)核心业务:
- 带看流程
- 合同模板 + 生成
- 员工角色权限
- 基础报表
Phase 33-4 月)增长功能:
- 佣金结算
- 渠道推广(链家 / 安居客对接)
- 微信小程序端
- BI 数据看板
Phase 4 商业化:
- 套餐 / 计费系统
- 多城市扩展
## 七、优先推进建议
如果要最快落地,建议优先顺序:
1. django-tenants 完整配置
2. 房源 / 客源 / 带看数据模型
3. Django Admin 深度定制
4. 权限系统设计
5. 前端升级到 HTMX 或 Vue

View File

@@ -0,0 +1,98 @@
---
title: django-tenants 完整配置指南
created: 2026-04-21
tags: [django, django-tenants, postgresql, saas, multi-tenant]
category: 技术笔记
---
# django-tenants 完整配置指南
## 一、安装依赖
pip install django-tenants psycopg2-binary django-jazzmin
## 二、项目目录结构
myproject/
├── config/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ └── wsgi.py
├── apps/
│ ├── tenants/
│ ├── subscription/
│ ├── accounts/
│ ├── listings/
│ ├── clients/
│ └── showings/
├── manage.py
└── requirements.txt
## 三、核心 Model租户与域名
- Company 继承 TenantMixin
- Domain 继承 DomainMixin
- 每一个中介公司 = 一个租户 = 一个独立 PostgreSQL Schema
- 每个公司可绑定多个域名/子域名
## 四、Settings 完整配置
关键点:
- SHARED_APPS 放公共 Schema 应用
- TENANT_APPS 放租户私有应用
- TENANT_MODEL = "tenants.Company"
- TENANT_DOMAIN_MODEL = "tenants.Domain"
- DATABASES 使用 django_tenants.postgresql_backend
- DATABASE_ROUTERS 使用 TenantSyncRouter
- TenantMainMiddleware 必须第一个
- ROOT_URLCONF / PUBLIC_SCHEMA_URLCONF 分离
- AUTH_USER_MODEL = accounts.User
## 五、URL 路由拆分
- config/urls_public.py公共域名、官网、注册、登录、超级管理后台
- config/urls_tenant.py租户子域名、租户后台、房源/客源/带看/员工模块
## 六、自定义 User Model跨租户关键
- User 继承 AbstractUser
- role 支持平台超管、公司管理员、门店经理、资深经纪人、经纪人、实习经纪人
- Branch 作为门店模型
## 七、初始化与常用命令
- createdb realestate_saas
- python manage.py migrate_schemas --shared
- python manage.py createsuperuser
- python manage.py shell 创建 Company 与 Domain
示例:
- schema_name = zuoan
- domain = zuoan.localhost
- 访问 http://zuoan.localhost:8000/admin/ 进入专属后台
## 八、本地开发配置hosts 文件)
- 127.0.0.1 localhost
- 127.0.0.1 zuoan.localhost
- 127.0.0.1 lianhe.localhost
- 127.0.0.1 xincheng.localhost
开发环境要点:
- ALLOWED_HOSTS 包含 .localhost
- 本地不用 HTTPS
## 九、数据隔离验证
使用 schema_context 切换 schema验证 Listing 等数据互相隔离。
## 下一步建议
推荐顺序:
1. 先做房源/客源/带看完整数据模型
2. 再做 Django Admin 深度定制Jazzmin 主题)
3. 最后补三级权限体系(总部/门店/经纪人)

View File

@@ -0,0 +1,63 @@
---
title: Gravity 以物理为基础的太阳系模拟器|如果家里有喜欢问“为什么”的孩子,推荐收藏
source: https://www.appinn.com/gravity-the-solar-system/
author:
- "[[青小蛙]]"
published: 2026-06-14
created: 2026-06-19
description: Gravity 是一个开源的太阳系模拟器,通过 24 个交互式动画,让小朋友了解整个太阳系,包括什么是引力?引力构建太阳/地球、为什么地球不会掉进太阳?火箭如何发射才不会掉下来等问题。
tags:
---
孩子们总会问一些看似简单、却很难解释的问题:
- 为什么地球不会掉进太阳里?
- 月亮为什么不会掉到地球上?
- 太阳那么大,为什么不会把所有行星都吸过去?
- 火箭为什么能飞上太空?
- 为什么火箭飞上去之后不会掉下来?
如果干巴巴的直接解释,似乎毫无吸引力。
但如果配合这个可以动的 Gravity
<iframe src="https://player.bilibili.com/player.html?isOutside=true&amp;aid=116747495672662&amp;bvid=BV11HJw6vEdc&amp;cid=39110510533&amp;p=1&amp;autoplay=0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
就很赞了。
## Gravity开源太阳系模拟器
![Gravity 以物理为基础的太阳系模拟器|如果家里有喜欢问“为什么”的孩子,推荐收藏 1](https://www.appinn.com/wp-content/uploads/2026/06/Copy-of-appinn-homework-2026-06-14T162924.412.jpg)
Gravity 以物理为基础的太阳系模拟器|如果家里有喜欢问“为什么”的孩子,推荐收藏 1
Gravity 是一个开源的太阳系模拟器,通过 24 个交互式动画,让小朋友了解整个太阳系,包括什么是引力?引力构建太阳/地球、为什么地球不会掉进太阳?火箭如何发射才不会掉下来等问题。
注意:一切都由真实的观测数据驱动;渲染器唯一进行“伪造”的是 **比例** ,使用了 **SpaceX 风格 UI**
![Gravity 以物理为基础的太阳系模拟器|如果家里有喜欢问“为什么”的孩子,推荐收藏 2](https://www.appinn.com/wp-content/uploads/2026/06/Screenshot-2026-06-14-09.24.57-2x.avif)
Gravity 以物理为基础的太阳系模拟器|如果家里有喜欢问“为什么”的孩子,推荐收藏 2
1. **什么是引力?** (`#what-is-gravity`) —— 展示两个质量体及其之间相等且相反的力矢量(牛顿第三定律);同样的力,产生不等的效应。
2. **引力构建太阳** (`#birth-of-sun`) —— 尘埃云坍缩并旋转形成太阳(吸积动画)。
3. **引力构建地球** (`#birth-of-earth`) —— 在剩余的圆盘中发生同样的微观过程;初生的地球在形成时闪烁着熔融的光芒。
4. **运动的物体保持运动** (`#inertia`) —— 移除太阳;地球以恒定速度沿直线漂移(牛顿第一定律)。纯粹的惯性。
5. **为什么地球不会掉进太阳** (`#why-no-fall`) —— 速度矢量 + 引力矢量 + 虚线的“无引力直线路径”。引力将直线弯曲成闭合环 —— 轨道就是持续坠落并始终错过。 …(及其他 19 个步骤)
## 真实性说明
- **大小** —— 每个天体都使用其真实的平均半径(太阳 696,340 公里 → 冥王星 1,188 公里)和质量。
- **轨道** —— 使用来自 JPL/IAU 近似根数表的真实 J2000.0 日心开普勒根数(半长轴、离心率、倾角、升交点黄经、近日点黄经、平黄经)。每帧都会求解开普勒方程。
- **日期** —— 时钟是真实的T=0 对应 J2000 历元2000-01-01 12:00
最后,还有一个自由探索功能:
## 获取
- 直接用: [https://gravity.appinn.com](https://gravity.appinn.com/)
- 代码在 [GitHub](https://github.com/scavin/Gravity/)
---
原文https://www.appinn.com/gravity-the-solar-system/
最后,青小蛙和小朋友一起看了这个 Gravity没想到他居然耐着性子把 24 个问题看完了。

353
Project/fonrey/ADR.md Normal file
View File

@@ -0,0 +1,353 @@
# Fonrey ADR 动态决策记录Architecture & Requirement Decision Records
> 目的:沉淀 Vibe Coding 过程中的**技术决策**与**需求决策**,避免跨会话后方案漂移。
> 适用范围全项目TECH_STACK / DATA_MODEL / PRD / TEST_CASES / 开发流程)。
> 维护原则:新增决策只追加,不覆盖历史;若决策被替代,必须新增 supersede 记录。
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 初始化 ADR 动态决策记录;补录当日关键技术与需求决策 |
| 2026-05-02 | Sisyphus | 新增 ADR-20260502-001合并系统管理与客户端发布两份 PRD 为统一的『平台管理后台 PRD』原文件删除 |
| 2026-05-02 | Sisyphus | 新增 ADR-20260502-003定义『PRD 与 Tech 文档职责边界』规则PRD 管 what/why、Tech 管 how首次落地于登录管理 PRD v3.0 |
| 2026-06-04 | Sisyphus | 新增 ADR-20260604-001列表分页全局采用 Keyset(cursor),允许 page-based 作过渡兼容;同步修订 `UI_DESIGN/ROUTES.md``UI_DESIGN/房源管理/房源列表_UI.md` |
| 2026-06-04 | Sisyphus | 新增 ADR-20260604-002ROUTES.md 客源条目"重复客源(成交)"与"重复客源(公客)"合并为单条 `/clients/duplicates/?tab=transacted\|public`,客源条目数稳定为 19 |
## 一、记录规范(必须遵守)
### 1.1 决策ID规则
- 格式:`ADR-YYYYMMDD-XXX`
- 例:`ADR-20260430-001`
### 1.2 决策类型
- `TECH`:技术决策(架构、接口、数据、测试、流程规范)
- `REQ`:需求决策(范围、术语、优先级、验收口径)
### 1.3 状态枚举
- `accepted`:已生效
- `superseded`:已被替代(需指向新 ADR
- `deprecated`:废弃(不再执行)
### 1.4 最小字段
每条 ADR 必须包含:
1. 决策ID
2. 日期
3. 模块
4. 类型TECH/REQ
5. 状态
6. 背景
7. 决策内容
8. 影响范围
9. 关联文档
10. 备注(可选)
---
## 二、按日期记录(主索引)
## 2026-04-30
### ADR-20260430-001
- **类型**REQ
- **模块**:测试治理(全局)
- **状态**accepted
- **背景**:测试文档与沟通中存在“业务旅程/测试用例”术语混用,易导致执行偏差。
- **决策**:全项目统一使用“测试用例”术语,不再以“业务旅程”作为执行口径。
- **影响范围**`TECH_STACK/测试规范.md``TEST_CASES/*`
- **关联文档**`TECH_STACK/测试规范.md`
### ADR-20260430-002
- **类型**TECH
- **模块**:测试治理(全局)
- **状态**accepted
- **背景**:多模块并行时测试编号易重复、不可追溯。
- **决策**:采用全局唯一测试用例编号:`TC-FON-XXXXXX`,并建立注册表,禁止复用废弃编号。
- **影响范围**:所有测试用例文档与测试报告系统
- **关联文档**
- `TEST_CASES/TEST_CASE_ID_SPEC.md`
- `TEST_CASES/TEST_CASE_REGISTRY.md`
### ADR-20260430-003
- **类型**TECH
- **模块**:测试执行与报告
- **状态**accepted
- **背景**:失败定位粒度不足,无法快速定位到具体步骤。
- **决策**:测试报告必须支持 `test_case_id + step_id` 级追踪,并记录 `expected/actual/error_message` 与证据路径。
- **影响范围**CI 报告、自动化测试框架
- **关联文档**`TECH_STACK/测试规范.md`
### ADR-20260430-004
- **类型**REQ
- **模块**:登录管理
- **状态**accepted
- **背景**:登录 PRD v2.0 与技术方案存在接口命名漂移。
- **决策**:登录模块接口路径以 PRD v2.0 为唯一权威,技术方案与测试用例全部对齐 PRD 路径。
- **影响范围**:登录模块 API 设计、自动化测试调用路径
- **关联文档**
- `PRD/登录管理/用户登录管理模块PRD.md`
- `TECH_STACK/登录管理技术方案.md`
- `TEST_CASES/TEST_CASES_LOGIN_MODULE.md`
### ADR-20260430-005
- **类型**REQ
- **模块**:登录管理
- **状态**accepted
- **背景**Story 边界存在历史遗留混淆。
- **决策**:登录模块范围冻结为:短信验证码登录为 MVP 正式能力;“找回用户名”废弃;微信扫码保留为预留。
- **影响范围**:实现范围、测试覆盖边界
- **关联文档**
- `PRD/登录管理/用户登录管理模块PRD.md`
- `TECH_STACK/登录管理技术方案.md`
### ADR-20260430-006
- **类型**TECH
- **模块**:客户端发布管理
- **状态**accepted
- **背景**:发布模块缺少可执行技术方案,无法指导实现与测试。
- **决策**:新增独立技术方案文档,明确 Electron 壳层边界、EV 签名、Heartbeat、自动更新、完整性校验、R2 版本管理、官网分发、便携版策略。
- **影响范围**`apps/release` 设计与落地
- **关联文档**
- `TECH_STACK/客户端发布管理技术方案.md`
- `PRD/发布管理/客户端发布管理模块PRD.md`
### ADR-20260430-007
- **类型**TECH
- **模块**:客户端发布管理 / 数据模型
- **状态**accepted
- **背景**:统计口径需支撑跨租户版本分布与活跃安装数。
- **决策**:以 `public.client_heartbeats` 作为心跳事实表,使用 `(tenant_id, device_id)` 为 Upsert 锚点,活跃定义为最近 24h。
- **影响范围**:统计接口、看板与聚合查询
- **关联文档**`DATA_MODEL/DATA_MODEL_PUBLIC.md`
### ADR-20260430-008
- **类型**TECH
- **模块**:客户端发布管理 / 安全
- **状态**accepted
- **背景**:下载链路存在篡改与损坏风险。
- **决策**:客户端更新包必须进行 SHA256 完整性校验,校验失败禁止安装并保留当前版本可用。
- **影响范围**:客户端更新器、发布接口响应字段
- **关联文档**
- `TECH_STACK/客户端发布管理技术方案.md`
- `PRD/发布管理/客户端发布管理模块PRD.md`
### ADR-20260430-009
- **类型**TECH
- **模块**:客户端发布管理 / API
- **状态**accepted
- **背景**:历史文档存在 `/api/client/updates/*``/api/release/updates/*` 双口径。
- **决策**:发布模块统一采用 `/api/release/...` 命名空间。
- **影响范围**:服务端路由、客户端调用、测试用例
- **关联文档**
- `PRD/发布管理/客户端发布管理模块PRD.md`
- `TECH_STACK/TECH_STACK.md`
- `TECH_STACK/客户端发布管理技术方案.md`
### ADR-20260430-010
- **类型**TECH
- **模块**:文档治理(全局)
- **状态**accepted
- **背景**:多目录文档缺少统一变更追溯入口。
- **决策**`TECH_STACK / DATA_MODEL / TEST_CASES` 下文档统一添加“变更历史”章节,并规范位置:**头部元信息之后、正文主章节(## 一/## 1之前**。
- **影响范围**:全量技术文档与测试文档
- **关联文档**
- `TECH_STACK/*.md`
- `DATA_MODEL/*.md`
- `TEST_CASES/*.md`
---
## 2026-05-02
### ADR-20260502-001
- **类型**REQ
- **模块**平台管理后台PRD 治理)
- **状态**accepted
- **背景**:原 `PRD/系统管理/系统管理模块PRD.md`v1.3)与 `PRD/发布管理/客户端发布管理模块PRD.md`v1.2)虽分别归口「系统管理」与「客户端发布」,但二者实际受众均为**平台管理员**Platform Admin / 运营人员 / 只读审计员),分别描述会导致:① 页面路由与权限矩阵在两份 PRD 中各自演化、容易割裂;② API 操作清单缺乏统一规划;③ 客户端发布 v1.1 已明确归属"平台运营后台",与系统管理同处一域。
- **决策**
1. 合并为单一文档 `PRD/平台管理后台/平台管理后台PRD.md`v1.0),定位「面向平台管理员的统一后台 PRD」以产品视角统一规划页面清单、访问权限、页面间导航逻辑、业务 API 操作清单(不绑定具体路径)。
2. 原两份 PRD 文件**直接删除**,由新文档完全取代;新文档头部声明 supersede 关系。
3. 客户端运行时与平台之间的接口(如查询最新版本、上报心跳)维持在 `TECH_STACK/客户端发布管理技术方案.md` 中描述,本 PRD 仅描述平台管理员可执行的业务操作。
4. 角色权限矩阵、租户状态机、客户端版本治理(含全平台租户活跃榜)等内容统一并入新 PRD 第 57 章。
- **影响范围**
- PRD删除 `PRD/系统管理/系统管理模块PRD.md``PRD/发布管理/客户端发布管理模块PRD.md`;新增 `PRD/平台管理后台/平台管理后台PRD.md`
- README模块入口索引同步更新原「系统管理」「客户端发布」入口指向新 PRD客户端技术方案文档保留
- TECH_STACK`客户端发布管理技术方案.md` 关联 PRD 的引用需同步指向新 PRD
- **关联文档**
- `PRD/平台管理后台/平台管理后台PRD.md`
- `README.md`
- `TECH_STACK/客户端发布管理技术方案.md`
- `TECH_STACK/系统设置技术方案.md`
- **备注**本决策不改变任何技术口径API 命名空间、`client_heartbeats` 表结构、SHA256 校验等),仅是 PRD 文档治理层面的合并与归属调整。`ADR-20260430-006/007/008/009` 全部继续生效。
### ADR-20260502-002
- **类型**TECH
- **模块**平台管理后台TECH_STACK 治理)
- **状态**accepted
- **背景**:随 `ADR-20260502-001` 完成 PRD 合并后TECH_STACK 域仍存在两份独立技术方案:`TECH_STACK/客户端发布管理技术方案.md`v1.0)与 `TECH_STACK/系统管理技术文档.md`v1.x。两者受众一致平台管理员后台却分别演化路由表、API 命名空间、Mixin 守卫链与错误码,存在以下风险:① 路由守卫与 step-up MFA 协议在两文档中各自定义,易割裂;② API 命名空间(`/admin/...` 后台 vs `/api/release/...` 客户端运行时)缺乏统一约束;③ Celery 队列、Redis Key、错误码族无法横向对齐。
- **决策**
1. 合并为单一文档 `TECH_STACK/平台管理后台技术方案.md`v1.0),覆盖 PRD 要求的三维度——**技术选型**(实现路由与 API 的框架)、**页面路由表**(路径定义、动态参数、路由守卫、懒加载)、**API 设计**RESTful 路径、HTTP 方法、请求/响应、错误码、版本控制、认证方式)。
2.`TECH_STACK/客户端发布管理技术方案.md``TECH_STACK/系统管理技术文档.md` **直接删除**,由新文档完全取代。
3. 双 API 命名空间最终落地:`/admin/...`HTMX PartialSession+CSRF+TOTP无路径版本号`/admin/api/client-releases/...`(管理端 JSON 写操作)、`/api/release/v1/...`(客户端运行时,路径版本号 `v1`)。
4. App 拆分:`apps/admin_console`(租户/备份/导出/升级/审计/管理员/Feature Flag+ `apps/release`(客户端发布 + 客户端运行时 API后者仅依赖前者的 `permissions / middleware / services.audit_service`
5. `TECH_STACK/TECH_STACK.md` §8 模块表合并「系统设置」「客户端发布」相关行为单行「平台管理后台」入口README 模块入口同步指向新文档。
- **影响范围**
- TECH_STACK新增 `平台管理后台技术方案.md`;删除 `客户端发布管理技术方案.md``系统管理技术文档.md`
- TECH_STACK`TECH_STACK.md` §8 模块表
- README模块入口索引平台管理后台一行
- **关联文档**
- `TECH_STACK/平台管理后台技术方案.md`
- `TECH_STACK/TECH_STACK.md`
- `PRD/平台管理后台/平台管理后台PRD.md`
- `ADR-20260502-001``ADR-20260430-006/007/008/009`
- **备注**本决策不变更任何技术口径API 命名空间、`/api/release/v1/...` 版本号、SHA256 校验、`client_heartbeats` Upsert + 24h 活跃口径、审计不可变约束等),仅在 TECH_STACK 文档治理层面执行合并与归属调整。
### ADR-20260502-003
- **类型**REQ
- **模块**:文档治理(全局)
- **状态**accepted
- **背景**:历史 PRD`PRD/登录管理/用户登录管理模块PRD.md` v2.0、`PRD/平台管理后台/平台管理后台PRD.md` v1.0)混杂大量实现细节——具体 API 路径与 HTTP 方法、Redis Key 格式与 TTL、Django 字段类型与中间件类名、Electron API 名、CSS 类名、Cookie 属性等。这导致:① PRD 评审被技术细节淹没,业务边界讨论失焦;② 实现口径出现"PRD 写一份、Tech 写一份"的双源头,长期产生漂移(参见 `ADR-20260430-004` 已为登录模块单独修过一次);③ 业务变更与技术调整混在同一份文档里,变更范围与责任人难以拆分。
- **决策**:自本 ADR 起,全项目 PRD 与 Tech 文档严格遵循以下职责边界,任何新建/修订必须自检通过。
**PRD 应包含("是什么 / 为什么"**
1. **页面与导航**:列出页面清单、访问权限(角色矩阵)、用户视角的页面间跳转逻辑、登录态/未登录态分支。✅ "/dashboard 需登录后访问";✅ "用户点击『详情』后跳转到订单详情页"。
2. **业务操作清单**:以业务动词描述用户/角色可执行的能力。✅ "用户可查询自己的订单列表";✅ "管理员可批量更新商品状态"。
3. **业务规则与数据约束**:抽象层面的规则与数值阈值(如『密码连续错误 5 次锁定 30 分钟』『短信验证码有效期 10 分钟』),但仅以业务语言表达,不绑定 Redis Key 或字段名。
4. **状态机**:业务状态枚举与合法跃迁。
5. **验收标准**:可由产品/QA 验证的用户可见行为。
**PRD 必须移出(移交 Tech / DATA_MODEL**
1. ❌ 具体 API 路径(如 `POST /api/auth/login/phone/`)与 HTTP 方法。
2. ❌ 请求/响应 JSON Schema、字段名、错误码常量。
3. ❌ Redis Key 格式、TTL、缓存策略、消息队列名。
4. ❌ 数据库字段名、字段类型(`CharField(30)``OneToOneField`)、表名、索引名、中间件类名。
5. ❌ 前端框架/库的 API 名(`BrowserWindow.loadURL``electron-store``HX-Request`、CSS 类名、Cookie 属性(`SameSite=Strict``HttpOnly`)。
6. ❌ 实现选型与库依赖清单(除非业务上明确强制,如"必须由具备短信资质的服务商发送")。
**PRD 引用 Tech 的标准格式**当业务规则需要技术细节落地时PRD 用以下任一方式引用:
- "(实现细节详见 `TECH_STACK/<模块>技术方案.md` §X.Y"
- "(数据结构见 `DATA_MODEL/<模块>.md`"
- "(端点契约见 `TECH_STACK/API_CONTRACT.md`"
**Tech 文档应承接**:上述被移出的全部内容;若 PRD 移出某项时 Tech 文档尚未承接,**必须在同一次提交内同步补齐 Tech**,禁止信息丢失。
**测试用例不受本 ADR 约束**:测试用例本质上需要可执行的实现细节(路径、字段、错误码),保留细节不动;但其引用 PRD 章节时应同步更新章节号。
- **影响范围**
- PRD所有 PRD 文档需按本规则审视并修订(首批落地:登录管理 PRD → v3.0;后续模块按需推进)
- TECH_STACK所有 Tech 文档需承接对应实现细节,缺口必须同步补齐
- 未来所有新建 PRD必须自检本规则
- CI/Review建议在 PR Review checklist 增加"PRD 是否含具体 API 路径/Redis Key/字段类型"的反向检查项
- **关联文档**
- `AGENTS.md` §9.1ADR 治理联动规则)
- `PRD/登录管理/用户登录管理模块PRD.md`v3.0 首批落地)
- `TECH_STACK/登录管理技术方案.md`(同步承接被移出细节)
- `ADR-20260430-004`(登录接口路径以 PRD 为准 → 本 ADR 后语义升级为:业务能力以 PRD 为准,具体路径以 Tech 为准;不冲突,互补)
- **备注**:本 ADR 不更改任何已实现的技术口径,仅约束**文档承载位置**。`ADR-20260430-004` 关于"登录接口最终路径"的权威源仍以两份文档同步为准PRD 描述业务操作、Tech 描述具体路径),二者不应再次出现漂移。
---
## 2026-06-04
### ADR-20260604-001
- **类型**TECH
- **模块**:列表分页(全局)
- **状态**accepted
- **背景**`AGENTS.md` §4.5 与 `UI_DESIGN/ROUTES.md` §1 全局约定明文要求"所有列表查询必须使用 Keyset 分页(参数禁止包含 `offset`,统一使用 `cursor`"。但 `UI_DESIGN/房源管理/房源列表_UI.md` 现存示例(行 97 query params 列表、行 741 HTMX `hx-vals='{"page": N}'`)仍是 page-based 风格与全局规范冲突。MVP 阶段一次性切到 Keyset 会拖慢前端模板与示例代码改造节奏,且页码 UI`[1][2][3]...[N]`)已被多份截图与组件清单采用。
- **决策**
1. **目标口径**:列表分页长期统一为 Keyset(cursor);新增列表页与新增分页 API 不得使用 page/offset。
2. **过渡兼容**MVP Phase 1 期间,已有 page-based 示例的模块文档与代码可保留 page UI 表现(页码导航 + 跳页),但**数据获取层必须支持 cursor 参数**;后端 API 接受 `cursor` 为主参数,`page` 仅作过渡兼容输入且不得用于 1000 条以上数据集。
3. **文档承接**`UI_DESIGN/ROUTES.md` §1 通用约定新增"过渡兼容"说明;`UI_DESIGN/房源管理/房源列表_UI.md` 示例代码改为 `cursor`,分页 UI 表现保留。
4. **退出条件**MVP GA 后 1 个版本内,所有 page-based 示例必须清理;届时新增 ADR 关闭本过渡条款。
- **影响范围**
- `UI_DESIGN/ROUTES.md` §1
- `UI_DESIGN/房源管理/房源列表_UI.md` §2.1.1、§2.1.3 分页栏
- 后续所有列表页 HTMX 模板与 API 视图
- **关联文档**
- `AGENTS.md` §4.5
- `UI_DESIGN/ROUTES.md`
- `UI_DESIGN/房源管理/房源列表_UI.md`
- **备注**B-04Keyset 分页规范缺位)由本 ADR 部分关闭;剩余的列表索引矩阵补全留待后续 Major 修订。
### ADR-20260604-002
- **类型**REQ
- **模块**:客源管理(路由汇总)
- **状态**accepted
- **背景**`UI_DESIGN/ROUTES.md` §5 客源条目数与历史 handoff 口径"客源 19 项"不一致——实际列出 20 行,多出的 1 行来自将"重复客源(成交)"与"重复客源(公客)"拆分为两条独立路由。两者复用同一列表页结构、同一权限范围、同一组件清单,仅业务子集不同,符合 ROUTES.md 全局约定中"Tab 维度统一用 `?tab=` 表达"的模式。
- **决策**:将两条重复客源路由合并为单条 `/clients/duplicates/`,子集通过 `?tab=transacted|public` 区分;客源条目总数稳定为 19。
- **影响范围**
- `UI_DESIGN/ROUTES.md` §5合并 2 行为 1 行)
- **关联文档**
- `UI_DESIGN/ROUTES.md`
- `UI_DESIGN/客源管理/客源列表_UI.md`
- **备注**:本 ADR 不变更任何业务规则,仅统一 URL 表达形式与条目计数。
---
## 三、按模块分类记录(视图索引)
## 3.1 测试治理(全局)
- `ADR-20260430-001`术语统一为“测试用例”REQ
- `ADR-20260430-002`全局唯一测试编号机制TECH
- `ADR-20260430-003`:报告粒度到 step_idTECH
## 3.2 登录管理
- `ADR-20260430-004`:接口路径以 PRD 为准REQ
- `ADR-20260430-005`MVP 范围冻结REQ
## 3.3 客户端发布管理
- `ADR-20260430-006`新增独立技术方案TECH *(superseded by `ADR-20260502-002`,技术方案已合并至『平台管理后台技术方案』)*
- `ADR-20260430-008`SHA256 完整性校验强制TECH
- `ADR-20260430-009`API 命名空间统一 `/api/release/...`TECH
## 3.4 数据模型public schema
- `ADR-20260430-007``client_heartbeats` Upsert + 24h 活跃口径TECH
## 3.5 文档治理(全局)
- `ADR-20260430-010`变更历史章节统一规则TECH
- `ADR-20260502-003`PRD 与 Tech 文档职责边界REQ
## 3.6 平台管理后台
- `ADR-20260502-001`:合并系统管理 PRD 与客户端发布 PRD 为统一的『平台管理后台 PRD』REQ
- `ADR-20260502-002`合并系统管理技术文档与客户端发布技术方案为统一的『平台管理后台技术方案』TECH
## 3.7 列表分页(全局)
- `ADR-20260604-001`Keyset(cursor) 为目标口径MVP 期允许 page-based 作过渡兼容TECH
## 3.8 客源管理(路由)
- `ADR-20260604-002`:重复客源合并为单条 `/clients/duplicates/?tab=transacted|public`,客源条目数=19REQ
---
## 四、历史记录Append-Only Log
> 说明:本节为机器可检索的历史流水,不删改旧记录。
| ADR ID | 日期 | 模块 | 类型 | 状态 | 摘要 | 关联 |
|---|---|---|---|---|---|---|
| ADR-20260430-001 | 2026-04-30 | 测试治理 | REQ | accepted | 术语统一为“测试用例” | `TECH_STACK/测试规范.md` |
| ADR-20260430-002 | 2026-04-30 | 测试治理 | TECH | accepted | 测试编号全局唯一 `TC-FON-XXXXXX` | `TEST_CASES/TEST_CASE_ID_SPEC.md` |
| ADR-20260430-003 | 2026-04-30 | 测试执行 | TECH | accepted | 报告必须定位到 `test_case_id+step_id` | `TECH_STACK/测试规范.md` |
| ADR-20260430-004 | 2026-04-30 | 登录管理 | REQ | accepted | 登录接口路径全部按 PRD v2.0 | `TECH_STACK/登录管理技术方案.md` |
| ADR-20260430-005 | 2026-04-30 | 登录管理 | REQ | accepted | 登录模块范围冻结(含废弃/预留) | `PRD/登录管理/用户登录管理模块PRD.md` |
| ADR-20260430-006 | 2026-04-30 | 客户端发布 | TECH | accepted | 新建独立技术方案文档 | `TECH_STACK/客户端发布管理技术方案.md` |
| ADR-20260430-007 | 2026-04-30 | 数据模型 | TECH | accepted | `client_heartbeats` Upsert + 24h 活跃 | `DATA_MODEL/DATA_MODEL_PUBLIC.md` |
| ADR-20260430-008 | 2026-04-30 | 客户端发布安全 | TECH | accepted | SHA256 校验失败禁止安装 | `TECH_STACK/客户端发布管理技术方案.md` |
| ADR-20260430-009 | 2026-04-30 | 客户端发布 API | TECH | accepted | 统一 `/api/release/...` 路径 | `TECH_STACK/TECH_STACK.md` |
| ADR-20260430-010 | 2026-04-30 | 文档治理 | TECH | accepted | 变更历史章节位置统一规范 | `TECH_STACK/*.md` `DATA_MODEL/*.md` `TEST_CASES/*.md` |
| ADR-20260502-001 | 2026-05-02 | 平台管理后台 | REQ | accepted | 合并系统管理 PRD + 客户端发布 PRD 为『平台管理后台 PRD』原文件删除 | `PRD/平台管理后台/平台管理后台PRD.md` |
| ADR-20260502-002 | 2026-05-02 | 平台管理后台 | TECH | accepted | 合并系统管理技术文档 + 客户端发布技术方案为『平台管理后台技术方案』(覆盖技术选型/页面路由表/API 设计三维度),原文件删除 | `TECH_STACK/平台管理后台技术方案.md` |
| ADR-20260502-003 | 2026-05-02 | 文档治理 | REQ | accepted | PRD 管 what/why、Tech 管 howPRD 必须移出 API 路径/Redis Key/字段类型/框架 API 等实现细节,由 Tech 与 DATA_MODEL 承接 | `PRD/登录管理/用户登录管理模块PRD.md` v3.0 |
| ADR-20260604-001 | 2026-06-04 | 列表分页(全局) | TECH | accepted | Keyset(cursor) 为目标口径MVP 期允许 page-based 作过渡兼容 | `UI_DESIGN/ROUTES.md``UI_DESIGN/房源管理/房源列表_UI.md` |
| ADR-20260604-002 | 2026-06-04 | 客源管理(路由) | REQ | accepted | 重复客源合并为单条 `/clients/duplicates/?tab=transacted\|public`,客源条目数=19 | `UI_DESIGN/ROUTES.md` |
---
## 五、后续维护约定
1. 每当接受新技术或需求决策,**当天必须新增 ADR 条目**。
2. 若新决策替代旧决策,在新 ADR 中写明 `supersedes: ADR-xxxx`,并将旧条目标记 `superseded`
3. PRD / TECH_STACK / DATA_MODEL / TEST_CASES 发生关键口径变化时,必须同步更新本文件。
4. 本文件是跨 Session 的决策权威输入之一,和 PRD 一起喂给 Agent。

324
Project/fonrey/AGENTS.md Normal file
View File

@@ -0,0 +1,324 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked. Every new feature or User Story implementation must be accompanied by corresponding tests as defined in this document.
# Fonrey房睿— AGENTS.md
**适用对象**:所有 AI Coding Agentvibe coding 模式)
**文档定位**:开发启动前的强制阅读清单,定义架构约定、禁止项和文档导航
**最后更新**2026-04-27
---
## 1. 项目概览
**Fonrey房睿房产经纪管理系统** —— 面向房地产经纪公司的 B2B SaaS 平台,解决房源/客源信息散乱、跟进缺失、重复录入等痛点,支撑单租户 89,000+ 房源数据量级下的高效匹配。
- **核心模块**:房源管理、客源管理、楼盘管理、组织人事、权限管理、登录管理、系统设置、客户端发布
- **目标用户**:一线经纪人(高频)、店长/经理(每日)、运营/行政(每日)、系统管理员(不定期)
- **形态**Web 端为主 + Electron 桌面客户端(壳应用);移动端为 v2 规划
- **设计哲学**:数据一致性 > 录入/筛选速度 > UI 简洁高效。优先保障多租户数据物理隔离与极速响应。
---
## 2. 核心技术栈
| 层级 | 技术选型 | 说明 |
|---|---|---|
| **Frontend** | HTMX + Alpine.js + Tailwind CSS | 无重前端框架HTMX 局刷、Alpine 管状态、Tailwind 样式 |
| **Backend** | Django 4.xASGI 模式) | 支持异步能力 |
| **Multi-tenant** | `django-tenants` | PostgreSQL Schema 隔离,租户数据物理安全 |
| **Database** | PostgreSQL 16 + PgBouncer | 连接池优化,支撑高并发 |
| **Cache** | Redis | 缓存、限流、Token、权限快照 |
| **Tasks** | Celery + Celery Beat | 异步导出、智能配房、邮件、图片转码 |
| **Storage** | Cloudflare R2S3 兼容) | 房源图片、附件、客户端安装包 |
| **CDN** | Cloudflare | 静态资源 + 客户端更新包加速 |
| **Server** | Gunicorn + Uvicorn workers + Nginx | ASGI 服务部署 |
| **Monitoring** | Sentry + Grafana | 错误追踪 + 指标监控 |
| **Deployment** | Docker Compose | 容器化部署 |
| **Desktop Client** | Electron + electron-updater | 壳应用,渲染层复用 Web 技术栈 |
---
## 3. 目录结构
代码库标准目录结构如下,**严格遵循**,不得自行创建新的顶层目录:
```
fonrey/
├── apps/
│ ├── tenant/ # django-tenants 配置shared_apps
│ ├── account/ # 登录认证
│ ├── permission/ # 权限管理
│ ├── org/ # 组织人事org_units, staff
│ ├── region/ # 区域管理districts, business_areas
│ ├── complex/ # 楼盘管理complexes, buildings, schools
│ ├── property/ # 房源核心(含 models/services/tasks 三层)
│ ├── client/ # 客源管理
│ ├── setting/ # 系统设置lookup, tags
│ └── release/ # 客户端发布管理shared_apps
├── shared/ # 公共 Schema Apppublic schema 模型)
└── core/
├── models/base.py # 抽象基类(所有业务表继承)
├── encryption.py # PII 加密AES-256-GCM
└── cache.py # Redis 工具(含 tenant 前缀)
```
**Django App 内部分层规范**(以 `property` 为典型,所有 App 参照执行):
```
apps/property/
├── models/ # 一表一文件,禁止全部堆在 models.py
├── services/ # 业务逻辑(完成度计算、重复检测、匹配等)
├── tasks.py # Celery 异步任务
├── views.py # HTMX/JSON 视图(薄视图,调 services
└── urls.py
```
---
## 4. 关键开发约定
### 4.1 多租户隔离(最高优先级)
- 所有数据库查询**必须**基于当前租户 Schema`django-tenants` 中间件已自动切换,不得绕过
- **严禁**跨租户 SQL 查询(包括 raw SQL 和 ORM 的 `using()` 指定 public schema
- `shared_apps` 仅放平台基础数据:`Tenant``ClientRelease``PermissionDef``PlatformAdmin`
- Celery 任务必须在任务参数中传入 `tenant_schema_name`,任务开头调用 `connection.set_schema(schema_name)` 切换到正确 Schema**不得依赖外部上下文传递**
### 4.2 前端交互约定
- **HTMX** 处理局部 DOM 刷新:分页、筛选、联想搜索、表单提交
- **Alpine.js** 处理前端状态:弹窗开关、多选状态、字数统计、条件显示
- **禁止**编写复杂原生 JavaScript逻辑无法用 HTMX/Alpine 覆盖时,优先提出问题而不是自行引入 JS 库
- HTMX 请求失败4xx/5xx必须触发全局 Toast 提示(通过 `HX-Trigger` 响应头)
- 所有 HTMX 局部请求后端视图必须校验 `HX-Request` header防止直接访问返回残缺 HTML
### 4.3 异步任务约定
- 所有耗时 **> 500ms** 的操作**必须**经 Celery 异步执行Excel 导出、图片处理、智能配房、邮件发送、完成度重算
- Celery Beat 定时任务:私客自动转公客(每小时)、重复客源检测(每日)
- 任务状态存 Redis前端通过轮询 HTMX 端点展示进度
### 4.4 数据模型约定
| 约定 | 规则 |
|------|------|
| 主键类型 | `UUID v4``gen_random_uuid()`),禁止自增整数 |
| 软删除 | 所有核心表含 `deleted_at TIMESTAMPTZ`,查询默认加 `WHERE deleted_at IS NULL` |
| 时间戳 | 全部使用 `TIMESTAMPTZ`(含时区),禁止 naive datetime |
| 手机号存储 | AES-256-GCM 加密,建立 SHA-256 哈希索引,通过 `core/encryption.py` 操作 |
| 审计字段 | `created_by UUID``updated_by UUID` 全表覆盖 |
| 枚举值 | 业务枚举用 `VARCHAR + CHECK CONSTRAINT`;不得在代码中硬编码中文枚举值 |
| 金额 | `NUMERIC(12,2)` 万元精度,禁止 FLOAT |
| 大文本 | `TEXT` 类型,不设长度限制 |
| 不可删除记录 | `listing_histories``price_changes``deleted_at`append-only禁止物理删除 |
### 4.5 查询性能约定
- 列表查询目标89,000 条房源 / 200 万条跟进日志下p95 响应 **< 2 秒**
- **所有列表查询必须使用 Keyset 分页**`WHERE id > :last_id ORDER BY id`),禁止 OFFSET 分页用于大数据量场景
- 新增表必须在 migration 中同步创建必要索引,不得事后补建
- 高写入表(`follow_logs``property_photos``permission_change_logs``login_attempts`)必须按月分区(`PARTITION BY RANGE`
### 4.6 安全约定
- 手机号等 PII 数据统一通过 `core/encryption.py` 加密存储,**禁止明文入库**
- 所有配置密钥、Bucket 名、外部服务 URL通过 `.env` 注入,**禁止硬编码**
- 每个受权限保护的 View 必须覆盖三个测试场景有权限200、无权限403、未登录302
- Redis Key 必须携带租户 Schema 前缀,格式:`{tenant_schema}:{module}:{key}`
### 4.7 错误处理约定
- 后端 API 返回标准 JSON 错误格式:`{"error": "...", "code": "SNAKE_CASE_CODE"}`
- View 层禁止直接抛出异常,必须捕获并返回对应 HTTP 状态码
- Celery 任务失败必须记录到 Sentry并更新任务状态表
### 4.8 文件命名约定
- Django App`snake_case`(如 `property``follow_log`
- 前端模板组件:`kebab-case`(如 `property-card.html``client-form.html`
- Migration 文件:不得手动重命名,保留 Django 自动生成的序号
---
## 5. 禁止项Do NOT — 违反视为 Bug
-**React / Vue / Angular** 等重前端框架
- ❌ 在请求线程中处理耗时 **> 500ms** 的任务(必须用 Celery
- ❌ 传统页面全刷方案(除初始页面加载外)
- ❌ 复杂原生 JavaScript优先 HTMX/Alpine 指令)
- ❌ Electron 渲染进程开启 `nodeIntegration: true`
- ❌ 客户端内嵌业务逻辑或本地数据库(壳应用原则,渲染层只加载 Web URL
- ❌ 跨租户 SQL 查询(必须经 `django-tenants` 中间件切换 Schema
- ❌ 代码中硬编码密钥、Tenant ID、URL、枚举中文字符串
- ❌ 使用 OFFSET 分页处理 1000 条以上数据集
- ❌ 手机号/身份证号明文存储
- ❌ 使用 Django 原生 `Client()` 做集成测试(必须用 `TenantClient`
- ❌ 在 MVP 阶段实现 `PRD_MVP.md §3` 中标注的 Out-of-Scope 功能(移动端、合同、财务、新房、三网发布等)
---
## 6. 测试要求
**核心原则**:每个 P0 User Story 完成后,对应测试必须同步产出,**不允许欠测试债**。
### 测试分层
| 层级 | 工具 | 覆盖目标 | 运行频率 |
|------|------|---------|---------|
| **单元测试** | `pytest-django` + `factory_boy` | `core/``services/``tasks.py` | 每次 push |
| **集成测试** | `pytest-django` + `TenantClient` | 所有 P0 User Story 的 HTTP 接口 | 每次 push |
| **E2E 测试** | `playwright` (Python) | 5 条核心用户旅程 | 每日定时 |
### 测试关键约定
- 集成测试**必须**使用 `TenantClient`,禁止使用 Django 原生 `Client()`
- HTMX 局部请求测试须携带 `HTTP_HX_REQUEST: true` header验证返回局部 HTML 而非完整页面
- Celery 任务测试使用 `CELERY_TASK_ALWAYS_EAGER = True` 同步执行
- 外部服务R2、Redis、邮件在测试中全部 Mock禁止真实调用
- 权限测试必须覆盖有权限200、无权限403、未登录302三场景
### 覆盖率基准
| 模块 | 最低目标 |
|------|---------|
| `core/` 核心基础模块 | ≥ 90% |
| `apps/*/services/` 业务逻辑层 | ≥ 80% |
| `apps/*/views.py` 视图层 | ≥ 70% |
| E2E 核心用户旅程5 条) | 100% 通过 |
---
## 7. MVP 范围Phase 1
**当前阶段**MVP Phase 1P0 功能)
实现任何功能前,先对照 `PRD/PRD_MVP.md` 确认是否在 P0 范围。**P0 范围以外的功能在 MVP 阶段禁止实现**,包括但不限于:
- 移动端适配v2 规划)
- 新房模块、合同、财务、三网发布
- 公客管理P2
- 门店分布地图P2
**当前 Phase 1 P0 Task 列表**:见 `PRD/TASK.md §Phase 1`US-ACCOUNT-001~003、US-COMPLEX-001~003、US-PROPERTY-001~008、US-CLIENT-001~017、US-ORG-001~003、US-PERMISSION-001~005、US-SETTING-001
---
## 8. 数据模型参考(实现前必读)
所有数据模型的**权威来源**如下,开发前必须阅读对应文档,不得凭印象实现:
| 模块 | 数据模型权威文档 |
|------|---------------|
| 总览 & 架构决策 | `DATA_MODEL/DATA_MODEL.md` |
| 房源管理 | `DATA_MODEL/DATA_MODEL_PROPERTY.md` |
| 客源管理 | `DATA_MODEL/DATA_MODEL_CLIENT.md` |
| 楼盘/小区/区域 | `DATA_MODEL/DATA_MODEL_COMPLEX.md` |
| 组织人事 | `DATA_MODEL/DATA_MODEL_ORG.md` |
| 权限管理 | `DATA_MODEL/DATA_MODEL_PERMISSION.md` |
| 登录认证 | `DATA_MODEL/DATA_MODEL_LOGIN.md` |
| Public Schema平台运营层 | `DATA_MODEL/DATA_MODEL_PUBLIC.md` |
| 系统配置lookup/setting | `DATA_MODEL/DATA_MODEL_SETTING.md` |
### 核心领域关系速览
```
[区域/商圈] ──────────────────────────────┐
│ │
[学校管理] │
│ ▼
[楼盘/小区] ── [楼栋] ──────────► [房源] ◄──── [挂牌历史]
│ │
│ ┌────────┼────────┐
│ │ │ │
│ [联系人] [跟进日志] [维护完成度]
[客源] ──── [配对记录] ──── [带看记录]
[员工/组织] ──── [权限]
```
---
## 9. 技术方案文档导航
| 模块 | 技术方案文档 | PRD |
|------|------------|-----|
| 总纲 & 禁止项 | `TECH_STACK/TECH_STACK.md` | `PRD/PRD_MVP.md` |
| 登录认证 | `TECH_STACK/登录管理技术方案.md` | `PRD/登录管理/` |
| 权限管理 | `TECH_STACK/权限管理系统技术方案.md` | `PRD/权限管理/` |
| 测试规范 | `TECH_STACK/测试规范.md` | — |
| 房源管理 | `TECH_STACK/房源管理技术方案.md` | `PRD/房源管理/` |
| 客源管理 | `TECH_STACK/客源管理技术方案.md` | `PRD/客源管理/` |
| 楼盘管理 | `TECH_STACK/楼盘管理技术方案.md` | `PRD/房源管理/(含楼盘)` |
| 组织人事 | `TECH_STACK/组织人事技术方案.md` | `PRD/组织人事管理/` |
| 系统设置 | `TECH_STACK/系统设置技术方案.md` | `PRD/系统配置/``PRD/系统管理/` |
| 客户端发布 | `TECH_STACK/客户端发布管理技术方案.md` | `PRD/发布管理/客户端发布管理模块PRD.md` |
| ADR 决策记录 | `ADR.md` | 全局TECH/REQ |
**设计 Review 记录**(了解当前已知技术债与阻塞项):
- `REVIEW/REVIEW_全局_2026-04-26.md`(最新)
- `REVIEW/REVIEW_全局_2026-04-25.md`
---
### 9.1 ADR 治理联动规则
- 任何跨模块规则、口径或范围边界调整,先记录 `ADR.md`,再改对应 TECH_STACK/PRD/DATA_MODEL/TEST_CASES 文档。
- 若已有结论被替代,必须在 ADR 中新增 superseded 记录并引用新 ADR ID禁止直接覆盖历史。
- 涉及上述变更的 PR/提交说明,必须包含 ADR ID`ADR-YYYYMMDD-XXX`)。
## 10. 已知待解决问题(编码前必须确认)
以下问题在开始编码前需先确认当前状态,若未解决应在实现对应模块前暂停并上报:
| 编号 | 级别 | 问题描述 | 影响模块 |
|------|------|---------|---------|
| **B-01** | 🔴 Blocker | 系统配置 PRD`PRD/系统配置/系统配置.md`为空骨架US-SETTING-001 无法启动 | 系统配置 |
| **B-02** | 🔴 Blocker | 核心枚举三方不一致PRD ↔ DDL ↔ TASK AC客源 status/grade、房源 status 互相矛盾,需先冻结 `DATA_MODEL/ENUMS.md` | 房源、客源 |
| **B-03** | 🔴 Blocker | 权限数据范围档位冲突§3 非目标"三档" vs §5.6"五档"DataScope 实现方式未统一 | 权限管理 |
| **B-04** | 🔴 Blocker | Keyset 分页规范完全缺位89k 房源列表设计错误 | 房源、客源 |
| **M-02** | 🟠 Major | 主表 `properties`/`clients` 缺乏乐观锁字段 `version` | 房源、客源 |
| **M-03** | 🟠 Major | 高写入表分区 DDL 未落地(`follow_logs` 等) | 跟进日志 |
| **M-04** | 🟠 Major | Celery 多租户 schema 切换规范、R2 文件路径前缀规范、查询索引矩阵未补充 | 全局 |
> 详细内容见 `REVIEW/REVIEW_全局_2026-04-26.md`
---
## 11. 文档根目录
所有项目文档位于:`/mnt/d/Workspace/nexus/Project/fonrey/`
```
fonrey/
├── AGENTS.md # 本文件AI Agent 开发指南)
├── PRD/
│ ├── PRD_MVP.md # MVP 范围书(必读)
│ ├── TASK.md # 全量 Task BoardUS 编号)
│ ├── 房源管理/
│ ├── 客源管理/
│ ├── 楼盘管理/
│ ├── 组织人事管理/
│ ├── 权限管理/
│ ├── 登录管理/
│ ├── 系统配置/
│ ├── 系统管理/
│ └── 发布管理/
├── DATA_MODEL/
│ ├── DATA_MODEL.md # 数据模型总览(必读)
│ ├── DATA_MODEL_PROPERTY.md
│ ├── DATA_MODEL_CLIENT.md
│ ├── DATA_MODEL_COMPLEX.md
│ ├── DATA_MODEL_ORG.md
│ ├── DATA_MODEL_PERMISSION.md
│ ├── DATA_MODEL_LOGIN.md
│ └── DATA_MODEL_PUBLIC.md
├── TECH_STACK/
│ ├── TECH_STACK.md # 技术栈总纲(必读)
│ ├── 登录管理技术方案.md
│ ├── 权限管理系统技术方案.md
│ └── 测试规范.md
└── REVIEW/
├── REVIEW_全局_2026-04-26.md # 最新 Review含阻塞问题
└── REVIEW_全局_2026-04-25.md
```

View File

@@ -0,0 +1,854 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey 房产经纪管理系统 — DATA MODEL 设计文档
> **作者**: Backend Architect
> **版本**: v1.5
> **日期**: 2026-04-30v1.1 修复 S1/S2/S4v1.2 扩展 public schemav1.3 §三 DDL 迁至 DATA_MODEL_PUBLIC.md本文改为索引v1.4 补充 LOGIN/PERMISSION 子文档引用、领域对象、租户 Schema 章节、Redis 缓存策略v1.5 新增 ADR 导航与治理联动)
> **技术栈**: Django 4.x + PostgreSQL + django-tenants + Redis
> **设计目标**: 支撑 89,000+ 房源、多租户隔离、sub-100ms 查询、合规审计
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 新增 ADR 导航与治理联动规则(数据模型变更需 ADR 可追溯) |
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、架构决策总览 (Architecture Decision Records)
### 1.1 多租户策略Schema-per-Tenant
```
┌──────────────────────────────────────────────────────────────────────┐
│ PostgreSQL Instance │
│ │
│ ┌─────────────────────────┐ ┌──────────────────┐ ┌────────────┐ │
│ │ public schema │ │ tenant_abc │ │ tenant_xyz │ │
│ │ (平台运营层) │ │ schema │ │ schema │ │
│ │ │ │ │ │ │ │
│ │ - tenants │ │ - org_units │ │ (同左) │ │
│ │ - domains │ │ - staff │ │ │ │
│ │ - tenant_status_logs │ │ - complexes │ │ │ │
│ │ - platform_admins │ │ - properties │ │ │ │
│ │ - admin_mfa_devices │ │ - clients │ │ │ │
│ │ - admin_sessions │ │ - user_accounts │ │ │ │
│ │ - ip_whitelist │ │ - login_attempts │ │ │ │
│ │ - platform_audit_logs │ │ - permission_defs│ │ │ │
│ │ - backup_schedules │ │ - roles │ │ │ │
│ │ - backup_records │ │ - staff_roles │ │ │ │
│ │ - export_tasks │ │ - lookup_items │ │ │ │
│ │ - system_versions │ │ - ... │ │ │ │
│ │ - upgrade_events │ └──────────────────┘ └────────────┘ │
│ │ - enum_labels │ │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```
**选型理由**
- `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)` 万元精度,避免浮点误差 |
---
## 二、领域概览Domain Overview
本节用业务语言描述系统的核心领域对象及其关系,作为各子模块数据模型的导读。
### 核心领域对象
#### Public Schema平台运营层
| 领域对象 | 表 | 业务说明 |
|----------|-----|----------|
| **Tenant租户** | `public.tenants` | 每家房产经纪公司一条记录含状态机creating/active/suspended/pending_delete/deleted、套餐、联系人 |
| **Domain域名** | `public.domains` | 子域名↔租户映射,支持多域名绑定,子域名创建后不可修改 |
| **TenantStatusLog** | `public.tenant_status_logs` | 租户状态变更不可变审计append-only |
| **PlatformAdmin** | `public.platform_admins` | 平台管理员账号3 种角色:超级管理员/运营人员/只读审计员 |
| **AdminMfaDevice** | `public.admin_mfa_devices` | 管理员 TOTP 设备(强制启用) |
| **AdminSession** | `public.admin_sessions` | 登录会话30 分钟超时,支持强制登出) |
| **IpWhitelist** | `public.ip_whitelist` | 管理控制台 CIDR 白名单 |
| **PlatformAuditLog** | `public.platform_audit_logs` | 所有写操作+高危操作审计append-only建议月度分区 |
| **BackupSchedule** | `public.backup_schedules` | 全局/租户级定时备份计划(频率/保留数/存储目标) |
| **BackupRecord** | `public.backup_records` | 备份任务执行记录(自动/手动/升级前/恢复前) |
| **ExportTask** | `public.export_tasks` | 数据导出异步任务CSV/JSON/SQL Dump24h 下载链接) |
| **SystemVersion** | `public.system_versions` | 平台版本历史,唯一 current 版本约束 |
| **UpgradeEvent** | `public.upgrade_events` | 升级/回滚事件,含灰度租户维度进度快照 |
| **EnumLabel** | `public.enum_labels` | 固定枚举字典(英文 Key → 中文标签),所有租户共享,供前端下拉渲染、导出报表中文标签、日志快照使用 |
#### Tenant Schema租户业务层
| 领域对象 | 表/子文档 | 业务说明 |
|----------|-----------|----------|
| **OrgUnit组织架构** | `org_units` → [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 树形组织架构(总部/区域/城市/大区/分公司/门店/团队/虚拟团队),物化路径存储,支持权限继承 |
| **Staff员工** | `staff` → [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 经纪人/店长/经理,绑定组织节点,手机号加密存储,与账号(登录)分离 |
| **District城区** | `districts` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 行政区划,如「静安区」,是区域体系的顶层节点 |
| **BusinessArea商圈** | `business_areas` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 商圈/板块,从属于城区,一个楼盘可归属多个商圈 |
| **School学校** | `schools` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 对口学校数据库,是买家购房决策的核心参考,与楼盘多对多关联 |
| **Complex楼盘/小区)** | `complexes` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 房源录入的基础底座,维护楼盘标准名称/坐标/锁定状态/别名等 |
| **Building楼栋/单元)** | `buildings` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘下的物理楼栋,区分标准结构与非标结构 |
| **RoomUnit房号** | `room_units` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼层+房间号,房源定位的最细粒度 |
| **Property房源** | `properties` → [DATA_MODEL_PROPERTY.md](./DATA_MODEL_PROPERTY.md) | 系统核心表,每套二手房源的完整档案,支持出售/出租/出售兼出租三态 |
| **Client客源** | `clients` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 买家/租客档案,分私客/公客/成交客,含活跃度评分与自动公客转换机制 |
| **Viewing带看** | `client_viewings` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 经纪人带客户看房的完整记录 |
| **Match配对** | `client_property_matches` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 系统/人工推荐的客源↔房源配对 |
| **UserAccount用户账号** | `user_accounts` → [DATA_MODEL_LOGIN.md](./DATA_MODEL_LOGIN.md) | 系统登录主体,与员工档案 1:1 绑定,含账号锁定/密码历史/登录审计 |
| **PermissionDef权限定义** | `permission_defs` → [DATA_MODEL_PERMISSION.md](./DATA_MODEL_PERMISSION.md) | 权限目录(约 300 条),驱动 Hybrid RBAC + Override 权限模型 |
| **Role业务角色** | `roles` → [DATA_MODEL_PERMISSION.md](./DATA_MODEL_PERMISSION.md) | 权限模板,含 4 大类别(置业顾问/店管/总经/运营/自定义) |
### 领域关系快速导航
```
District (城区)
└─ BusinessArea (商圈)
└─ Complex (楼盘) ─── School (对口学校)
├─ Building (楼栋)
│ └─ RoomUnit (房号)
└─ Property (房源)
├─ PropertyContact (联系人/委托方)
├─ FollowLog (跟进日志)
├─ Viewing (带看记录) ──── Client (客源)
└─ Match (配对记录) ──────┘
OrgUnit (组织架构)
└─ Staff (员工/经纪人) ─── Property / Client / Viewing / Match
```
### 子文档索引
| 子文档 | 覆盖模块 | 状态 |
|--------|----------|------|
| [DATA_MODEL_PUBLIC.md](./DATA_MODEL_PUBLIC.md) | Public schema 平台运营层tenants, domains, platform_admins, admin_sessions, audit_logs, backup, export, upgrade 共 13 张表) | ✅ 完成 |
| [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 组织人事org_units, staff, 异动/奖惩/教育/家庭等) | ✅ 完成 |
| [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘/区域districts, business_areas, complexes, buildings, room_units, schools 等) | ✅ 完成 |
| [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 客源管理clients, requirements, follow_logs, viewings, matches 等) | ✅ 完成 |
| [DATA_MODEL_PROPERTY.md](./DATA_MODEL_PROPERTY.md) | 房源管理properties 及配套 22 张表,含跟进/钥匙/委托/实勘/营销/产证/完成度/标签/收藏/保护/号码方审批等) | ✅ 完成 |
| [DATA_MODEL_LOGIN.md](./DATA_MODEL_LOGIN.md) | 登录与账号认证user_accounts, login_attempts, password_reset_tokens, password_histories + Redis 登录缓存) | ✅ 完成 |
| [DATA_MODEL_PERMISSION.md](./DATA_MODEL_PERMISSION.md) | 权限管理permission_defs, roles, role_permissions, staff_roles, staff_permission_overrides, staff_data_scopes, permission_change_logs + Redis 权限缓存) | ✅ 完成 |
| [ENUMS.md](./ENUMS.md) | 枚举字典(`public.enum_labels` 表设计 + 所有模块枚举定义 + 种子数据 SQL | ✅ 完成 |
| [ADR.md](../ADR.md) | 架构与需求决策记录(按日期/按模块/历史流水append-only | ✅ 完成 |
---
## 三、公共 SchemaShared / Public
> **权威源**:完整 DDL 已迁至 [`DATA_MODEL_PUBLIC.md`](./DATA_MODEL_PUBLIC.md),本节仅保留摘要索引。
> **覆盖范围**`public` schema 存储平台运营层数据——租户注册、管理员账号、审计日志、备份/导出任务、版本升级记录(共 13 张表)。
> **设计依据**:系统管理模块 PRD`PRD/系统管理/系统管理模块PRD.md`)。
### 表清单(开发以 DATA_MODEL_PUBLIC.md 为准)
| 表名 | 说明 | 节 |
|------|------|----|
| `public.tenants` | 租户主表django-tenants 核心,状态机 6 态) | §2.1 |
| `public.domains` | 域名↔租户映射(支持多域名,子域名不可修改) | §2.1 |
| `public.tenant_status_logs` | 租户状态变更不可变审计日志append-only | §2.1 |
| `public.platform_admins` | 平台管理员账号super_admin/ops_operator/read_only_auditor | §2.2 |
| `public.admin_mfa_devices` | 管理员 TOTP MFA 设备(强制启用) | §2.2 |
| `public.admin_sessions` | 管理员登录会话30 min 滚动超时,支持强制登出) | §2.2 |
| `public.ip_whitelist` | 管理控制台 CIDR 白名单 | §2.2 |
| `public.platform_audit_logs` | 所有写操作+高危操作审计append-only建议月度分区 | §2.3 |
| `public.backup_schedules` | 全局/租户级定时备份计划NULL tenant_id = 全局默认) | §2.4 |
| `public.backup_records` | 备份任务执行记录auto/manual/pre_upgrade/pre_restore | §2.4 |
| `public.export_tasks` | 数据导出异步任务CSV/JSON/SQL Dump24h 下载链接) | §2.4 |
| `public.system_versions` | 平台版本历史,部分唯一索引保证唯一 current | §2.5 |
| `public.upgrade_events` | 升级/回滚事件,`tenant_progress` JSONB 快照各租户状态 | §2.5 |
| `public.enum_labels` | 固定枚举字典(英文 Key → 中文标签),所有租户共享 | §2.6 |
**关键约束提示**
- `tenant_status_logs` / `platform_audit_logs` **无 deleted_at**,禁止 UPDATE/DELETEappend-only
- `public.tenants.schema_name` 创建后**不可修改**
- `public.tenants` 不再使用 `is_active` boolean改用 6 态 `status` 枚举
- `platform_admins` 与租户 `staff` **完全独立**,不共享 auth 系统
- `system_versions` 通过部分唯一索引确保全局只有一个 `status='current'`
---
<!-- §三 DDL 已迁至 DATA_MODEL_PUBLIC.md v1.02026-04-24 -->
## 四、租户 SchemaTenant Schema
以下所有表均在每个租户的独立 Schema 内创建。
---
### 3.1 组织人事模块Organization & HR
> **详细模型** → 见 [`DATA_MODEL_ORG.md`](./DATA_MODEL_ORG.md)
> 该文件为权威定义,包含完整字段、枚举、查询模式和禁止操作。
**核心表概览**(开发时以 DATA_MODEL_ORG.md 为准):
| 表名 | 说明 |
|------|------|
| `org_units` | 组织树节点(公司/事业部/大区/区域/片区/门店/店组/职能),物化路径树 |
| `staff` | 员工主表含加密手机号、角色、在职状态、Django auth 绑定 |
| `staff_personal_info` | 员工个人信息扩展证件、学历、婚育等1:1 |
| `staff_transfer_logs` | 人事异动不可变审计日志(入职/调动/离职/复职等) |
| `staff_reward_punish` | 奖惩记录 |
| `staff_work_experiences` | 工作经历 |
| `staff_educations` | 教育经历 |
| `staff_trainings` | 培训经历 |
| `staff_family_members` | 家庭成员 |
| `staff_accounts` | 第三方平台账号绑定58安居客/中国网络经纪人等) |
**关键约束提示**
- `staff.phone_enc` AES-256-GCM 加密,`staff.phone_hash` SHA-256 用于唯一索引
- `staff_transfer_logs` **无 deleted_at**,不可删除
- `org_units` 路径查询:`WHERE path LIKE '/root/{target_id}/%'`
- 员工离职:`status = 'resigned'` + `deleted_at` 软删除,记录永久保留
---
### 3.2 区域与楼盘模块Region & Complex Management
> **详细模型** → 见 [`DATA_MODEL_COMPLEX.md`](./DATA_MODEL_COMPLEX.md)
> 本节仅作概览,开发时以 DATA_MODEL_COMPLEX.md 为权威定义。
**核心表概览**(开发时以 DATA_MODEL_COMPLEX.md 为准):
| 表名 | 说明 | 关键字段 |
|------|------|----------|
| `districts` | 城区/行政区 | `city`, `name`, `short_name`, `sort_order` |
| `business_areas` | 商圈/板块(从属于城区) | `district_id`, `name`, `latitude`, `longitude` |
| `metro_lines` | 地铁线路 | `city`, `name`, `color` |
| `metro_stations` | 地铁站点 | `metro_line_id`, `name`, `latitude`, `longitude` |
| `schools` | 学校(对口学区) | `district_id`, `name`, `type`, `nature`, `level` |
| `complexes` | 楼盘/小区(房源底座) | `name`, `district_id`, `address`, `latitude/longitude`, `lock_*`, `search_vector` |
| `complex_aliases` | 楼盘别名(含系统别名/用户自定义别名) | `complex_id`, `alias`, `is_system` |
| `complex_business_areas` | 楼盘↔商圈多对多(含主商圈标识) | `complex_id`, `business_area_id`, `is_primary` |
| `complex_schools` | 楼盘↔学校关联(含学区类型) | `complex_id`, `school_id`, `zone_type` |
| `complex_metro_stations` | 楼盘↔地铁站关联(含步行距离) | `complex_id`, `station_id`, `distance_meters` |
| `buildings` | 楼栋/单元 | `complex_id`, `name`, `is_standard`, `total_floors` |
| `room_units` | 房号/结构单元(楼层+房间号) | `building_id`, `floor`, `room_no`, `is_standard` |
| `complex_photos` | 楼盘照片(楼盘图/户型图/VR | `complex_id`, `category`, `file_key`, `is_cover` |
| `complex_attachments` | 楼盘附件 | `complex_id`, `file_key`, `file_name` |
| `complex_price_trends` | 楼盘价格走势(月度) | `complex_id`, `record_month`, `avg_unit_price` |
---
### 3.3 房源模块Property Management
> **详细模型** → 见 [`DATA_MODEL_PROPERTY.md`](./DATA_MODEL_PROPERTY.md)
> 本节仅作概览,开发时以 DATA_MODEL_PROPERTY.md 为权威定义。
**核心表概览**(开发时以 DATA_MODEL_PROPERTY.md 为准):
| 表名 | 说明 | 关键字段 |
| ------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `properties` | 房源主表系统核心89,000+ 数据量) | `status`, `attribute`, `property_type`, `complex_id`, `sale_price`, `area`, `grade`, `completeness_score`, `search_vector` |
| `property_contacts` | 业主/联系人(手机号 AES 加密+哈希索引) | `property_id`, `phone_enc`, `phone_hash`, `identity`, `is_number_holder` |
| `listing_histories` | 挂牌历史快照(不可删除) | `property_id`, `listing_type`, `status`, `sale_price`, `seller_agent_snapshot` |
| `price_changes` | 调价记录(不可删除) | `property_id`, `old_sale_price`, `new_sale_price`, `change_reason`, `changed_by` |
| `follow_logs` | 跟进日志6种类型最高写入频率 | `property_id`, `log_type`, `content`, `is_deletable`, `operator_id` |
| `follow_log_attachments` | 跟进附件(图片) | `follow_log_id`, `file_key`, `file_type` |
| `follow_log_recordings` | 跟进录音 | `follow_log_id`, `file_key`, `duration_seconds` |
| `property_keys` | 钥匙管理(机械钥匙/密码) | `property_id`, `key_type`, `holder_id`, `is_active` |
| `key_attachments` | 钥匙附件 | `key_id`, `file_key` |
| `commissions` | 委托管理(独家/非独家) | `property_id`, `commission_type`, `period_start`, `status` |
| `commission_attachments` | 委托附件(身份证/产证/委托书) | `commission_id`, `category`, `file_key` |
| `field_surveys` | 实勘管理GPS 打卡) | `property_id`, `status`, `gps_latitude`, `gps_longitude`, `created_by` |
| `survey_photos` | 实勘照片(按空间分类) | `survey_id`, `category`, `file_key`, `is_vr_screenshot` |
| `property_photos` | 房源图片(经纪人管理,封面唯一约束) | `property_id`, `category`, `is_cover`, `file_key` |
| `property_attachments` | 房源附件 | `property_id`, `category`, `file_key` |
| `property_marketing` | 营销信息1:1卖点/业主心态/介绍) | `property_id`, `marketing_title`, `core_selling_points` |
| `property_certificates` | 产证信息1:1 | `property_id`, `cert_no`, `owner_name`, `land_nature` |
| `property_completeness` | 维护完成度快照1:1Celery 异步计算) | `property_id`, `total_score`, `score_survey`, `score_commission`, ... |
| `property_tags` | 标签字典(系统预置+运营自定义) | `name`, `color`, `is_system` |
| `property_tag_relations` | 房源↔标签多对多 | `property_id`, `tag_id` |
| `property_favorites` | 经纪人收藏房源 | `staff_id`, `property_id` |
| `property_protections` | 保护房设置1:1 | `property_id`, `is_protected`, `start_at`, `end_at` |
| `number_holder_approvals` | 号码方变更审批 | `property_id`, `applicant_id`, `status` |
**关键约束提示**
- `property_contacts.phone_hash` 是重复房源检测的主要依据,录入前必须查重
- `listing_histories` / `price_changes` **无 deleted_at**,不可删除
- `follow_logs``is_deletable=FALSE``sensitive_view` 类型)不可软删
- `completeness_score` 只由 Celery 任务写入Application 层禁止直接更新
- `last_followed_at` 由触发器 `trg_update_last_followed` 自动维护
- `property_photos.is_cover` 唯一约束:每套房源仅一张封面
---
### 3.4 登录与账号认证Login & Account
> **详细模型** → 见 [`DATA_MODEL_LOGIN.md`](./DATA_MODEL_LOGIN.md)
> 该文件为权威定义包含完整字段、状态机、Redis 缓存结构和禁止操作。
**核心表概览**(开发时以 DATA_MODEL_LOGIN.md 为准):
| 表名 | 说明 |
|------|------|
| `user_accounts` | 账号主表1:1 绑定 `org.Staff`),含加密手机号/哈希、状态机active/locked/disabled、初始密码标识 |
| `login_attempts` | 登录审计日志append-only成功/失败均记录,无 FK 冗余存 username 保证历史完整) |
| `password_reset_tokens` | 密码重置 Token有效期 30 分钟,使用后立即标记 `is_used` |
| `password_histories` | 历史密码记录(保留最近 3 条,含初始密码,防止重复使用) |
**关键约束提示**
- `user_accounts` 主键用 `BIGSERIAL`(非 UUID登录审计场景 BigInt 更高效
- `user_accounts.phone_enc` AES-256-GCM 加密,`phone_hash` SHA-256 用于唯一索引
- **禁止物理删除** `user_accounts`,离职员工只能 `status=disabled`
- 账号锁定5 次密码错误)→ `status=locked``locked_until=NOW()+30min`Redis 仅计数,实际锁定以 DB 为准
- Tenant Admin 的 `staff_id` 可为空(可无员工档案);普通员工 `staff_id` 必填且关联 active Staff
- 员工离职(`org.Staff.status→resigned`)→ 应用层 Service 调用触发账号 `status→disabled`**禁止循环 FK**
- `password_reset_tokens` / `login_attempts` **无 deleted_at**,不可修改/删除
**Redis 辅助状态**(非持久化):
| Key 格式 | TTL | 说明 |
|----------|-----|------|
| `captcha_token:{uuid}` | 3 分钟 | 滑块验证会话 Token |
| `captcha_pass:{uuid}` | 3 分钟 | 一次性通过凭证(验证后立即删除) |
| `login_fail:{tenant_id}:{username}` | 30 分钟 | 连续密码错误计数≥5 触发锁定 |
| `recover_email:{email}` | 1 小时 | 找回邮件发送次数上限 3 次 |
| `tenant_verify_ip:{ip}` | 1 分钟 | Tenant 验证接口 IP 限流≥10 次拒绝 |
---
### 3.5 权限管理Permission & RBAC
> **详细模型** → 见 [`DATA_MODEL_PERMISSION.md`](./DATA_MODEL_PERMISSION.md)
> 该文件为权威定义,包含完整字段、权限解析算法、`ScopeQueryBuilder` 实现和禁止操作。
**权限模型概述**Hybrid RBAC + Individual Override支持 `BOOLEAN / SCOPE / INTEGER` 三类权限值,多角色合并规则 OR / MAX。
**核心表概览**(开发时以 DATA_MODEL_PERMISSION.md 为准):
| 表名 | 说明 |
|------|------|
| `permission_defs` | 权限目录(约 300 条,`PUBLIC Schema``shared_apps` 存储,所有租户共享),含模块/分组/值类型/默认值/上限类别 |
| `roles` | 业务角色每租户独立5 种类别:`agent/store_manager/director/operator/custom`,含系统内置标识 |
| `role_permissions` | 角色↔权限值(稀疏存储,仅存与 default_value 不同的项) |
| `staff_roles` | 员工↔角色分配N:M含主角色标识 `is_primary`、有效期) |
| `staff_permission_overrides` | 员工个人权限覆盖稀疏存储仅存与角色合并值不同的项3 种 override_modeREPLACE / RESTRICT / GRANT |
| `staff_data_scopes` | 员工数据范围扩展(补充 SCOPE 权限之外的额外可读范围,如特殊跨门店授权) |
| `permission_change_logs` | 权限变更不可变审计日志append-only禁止 UPDATE/DELETE |
**关键约束提示**
- `permission_defs` 位于 **Public Schema**`shared_apps`),所有租户共享;`roles` 及其余表属租户 Schema
- **禁止硬删除** `permission_defs`,改用 `is_active=FALSE` 下线;`code` 字段不可修改
- **禁止直接构造 Q 对象绕过 `ScopeQueryBuilder`**,会导致越权漏洞
- `permission_change_logs` **无 deleted_at**,禁止 UPDATE/DELETE
- 员工权限解析:`is_system_admin=TRUE` → 短路返回全权限;否则多角色 OR/MAX 合并后叠加 Override
- `StaffPermissionOverride` 保存前必须做差异对比,**禁止存与角色合并值相同的冗余记录**(稀疏存储)
- `staff_roles.is_primary` 唯一约束通过 Signal 维护,**禁止绕过**
**权限解析缓存**
| Cache Key | TTL | 失效触发 |
|-----------|-----|---------|
| `perm:v{VER}:{schema}:{staff_id}` | 3600s | Override / StaffRole 变更 |
| `perm:v{VER}:{schema}:role:{role_id}:staff_ids` | 3600s | 角色权限变更 → Pipeline 批量失效 |
| `perm:inconsistent:{schema}:{staff_id}` | 300s | 同上 |
| `perm:defs:{schema}` | 86400s | PermissionDef 变更(低频) |
| `perm:role_applied_count:{schema}:{role_id}` | 600s | StaffRole 变更 |
> **版本号机制**`CACHE_VERSION` 在 Django settings 中,升级 PermissionDef 结构时 bump一键全局失效。
---
### 3.17 客源管理Client Management
> **详细模型** → 见 [`DATA_MODEL_CLIENT.md`](./DATA_MODEL_CLIENT.md)
> 该文件为权威定义,包含完整字段、枚举、状态机、查询模式和禁止操作。
**核心表概览**(开发时以 DATA_MODEL_CLIENT.md 为准):
| 表名 | 说明 |
|------|------|
| `clients` | 客源主表(私客/公客/成交客),含加密手机号哈希、活跃度、归属人 |
| `client_contacts` | 联系人1:N手机号加密+哈希,支持多联系人 |
| `client_requirements` | 需求信息(可多类型:二手/新房/租房),含预算/面积/商圈/朝向等偏好 |
| `client_follow_logs` | 跟进日志高写入频率5种类型敏感查看类型不可删 |
| `client_follow_log_attachments` | 跟进附件(图片/录音最大20MB |
| `client_viewings` | 带看/预约记录1:N含陪看人/合作带看人) |
| `client_property_matches` | 智能配房结果(录客配房/系统配房,匹配度评分) |
| `client_status_logs` | 状态变更不可变审计日志(改状态/改等级/转公/转成交/转无效等) |
| `client_favorite_folders` | 私客收藏夹(经纪人自定义分组) |
| `client_folder_items` | 收藏夹与客源的多对多关联 |
| `client_school_preferences` | 意向学校(拆表,支持精确查询) |
**关键约束提示**
- `client_contacts.phone_hash` 是重复客源检测的唯一依据,录入前必须查重
- `client_status_logs` **无 deleted_at**,不可删除
- 私客超时(配置天数内无跟进)→ Celery 自动转公(`transfer_to_public_type = 'auto'`
- 活跃度 `activity_level` 由 Celery 每日凌晨批量计算,不实时更新
---
### 3.18 系统设置System Settings
> **归属说明**
> - `lookup_categories` / `lookup_items` / `saved_filters` 为**跨模块**系统表,权威定义在本节。
> - `property_tags` / `property_tag_relations` / `property_favorites` / `property_protections` / `number_holder_approvals` 属房源模块配套表,**权威定义已迁至** [`DATA_MODEL_PROPERTY.md §4.19-§4.22`](./DATA_MODEL_PROPERTY.md),本节不再重复 DDL修复 S1/S2
```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 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);
```
**已迁出本节的表**(权威源见子文档):
| 表名 | 权威定义位置 |
|------|-------------|
| `property_tags` | [`DATA_MODEL_PROPERTY.md §4.19`](./DATA_MODEL_PROPERTY.md) |
| `property_tag_relations` | [`DATA_MODEL_PROPERTY.md §4.19`](./DATA_MODEL_PROPERTY.md) |
| `property_favorites` | [`DATA_MODEL_PROPERTY.md §4.20`](./DATA_MODEL_PROPERTY.md) |
| `property_protections` | [`DATA_MODEL_PROPERTY.md §4.21`](./DATA_MODEL_PROPERTY.md) |
| `number_holder_approvals` | [`DATA_MODEL_PROPERTY.md §4.22`](./DATA_MODEL_PROPERTY.md) |
---
### 3.19 枚举字典Enum Labels
> **权威定义** → 见 [`DATA_MODEL/ENUMS.md`](./ENUMS.md)
> 本节为概览,开发时以 ENUMS.md 为准。
#### 表归属
`enum_labels` 位于 **Public Schema**`shared_apps`),所有租户共享,**不属于任何租户 Schema**。
#### 核心表设计
```sql
CREATE TABLE enum_labels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
domain VARCHAR(60) NOT NULL, -- 枚举域,格式:{模块}.{字段},如 client.status
value VARCHAR(60) NOT NULL, -- 英文 Key与数据库 CHECK 约束一致)
label_zh VARCHAR(60) NOT NULL, -- 中文标签(前端展示用)
sort_order SMALLINT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
remark TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_enum_labels_domain_value ON enum_labels(domain, value);
CREATE INDEX idx_enum_labels_domain ON enum_labels(domain, sort_order);
```
#### 覆盖的枚举域domain 清单)
| domain | 说明 | 对应表字段 |
|--------|------|-----------|
| `client.status` | 客源状态8 态) | `clients.status` |
| `client.grade` | 客源等级5 档 + E | `clients.grade` |
| `client.requirement_type` | 需求类型(旧:`client.purpose_type` | `client_requirements.requirement_type` |
| `client.property_usage` | 房源用途偏好(旧:`client.usage` | `client_requirements.property_usage` |
| `client.orientation` | 朝向偏好 | `client_requirements.orientation` |
| `client.payment_method` | 付款方式 | `clients.payment_method` |
| `property.status` | 房源状态 | `properties.status` |
| `property.attribute` | 房源属性(公/私/保护) | `properties.attribute` |
| `property.property_type` | 房源类型(旧:`property.usage` | `properties.property_type` |
| `property.grade` | 房源等级5 档) | `properties.grade` |
| `property.listing_history.listing_type` | 挂牌类型(旧:`property.listing_type` | `listing_histories.listing_type` |
| `property.decoration` | 装修程度 | `properties.decoration` |
| `property.orientation` | 朝向 | `properties.orientation` |
| `property.commission.status` | 委托状态(旧:`commission.type` | `commissions.status` |
| `field_survey.status` | 实勘状态 | `field_surveys.status` |
| `follow_log.log_type` | 跟进日志类型 | `follow_logs.log_type` |
#### 重要约定
- `enum_labels.value` 必须与对应表的 `CHECK` 约束完全一致,**两者必须同步修改**
- 新增枚举值流程:① 修改 DDL `CHECK` 约束 → ② 插入 `enum_labels` 种子数据 → ③ 更新 `ENUMS.md`
- `is_active = FALSE` 仅停用前端展示,**不得修改或删除已有 `value`**(历史数据引用不可破坏)
- 前端下拉渲染**统一从 `enum_labels` 读取**,禁止在前端代码中硬编码中文标签
#### 与 `lookup_items` 的区别
| 对比维度 | `enum_labels` | `lookup_items` |
|---------|---------------|----------------|
| 用途 | 固定枚举的中文标签映射 | 运营可配置的动态选项(如跟进目的、来源渠道) |
| 修改权限 | 仅开发/DBA | 运营人员后台配置 |
| Schema 位置 | Public Schema共享 | Tenant Schema每租户独立 |
| 典型示例 | 客源状态、房源等级 | 跟进目的、客户来源渠道 |
---
## 五、关键索引汇总与查询优化策略
### 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小时)
# 登录模块(详见 DATA_MODEL_LOGIN.md §四)
captcha_token:{uuid} TTL: 180 (3分钟)
captcha_pass:{uuid} TTL: 180 (3分钟)
login_fail:{tenant_id}:{username} TTL: 1800 (30分钟连续失败计数)
recover_email:{email} TTL: 3600 (1小时发送次数限流)
recover_reset:{account_id} TTL: 3600 (1小时Token 生成次数限流)
tenant_verify_ip:{ip} TTL: 60 (1分钟IP 限流)
# 权限模块(详见 DATA_MODEL_PERMISSION.md §六)
perm:v{VER}:{schema}:{staff_id} TTL: 3600 (员工完整权限快照)
perm:v{VER}:{schema}:role:{role_id}:staff_ids TTL: 3600 (角色→员工 ID 列表,批量失效用)
perm:inconsistent:{schema}:{staff_id} TTL: 300 (权限不一致标记)
perm:defs:{schema} TTL: 86400 (权限定义全量缓存)
perm:role_applied_count:{schema}:{role_id} TTL: 600 (角色应用人数)
# 标签列表
{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万+ | 高速(最高频写入) | ✅ `PARTITION BY RANGE (created_at)` 月度分区 |
| `property_photos` | 500万+ | 高速 | ✅ `PARTITION BY RANGE (created_at)` 月度分区 |
| `permission_change_logs` | 100万+ | 中高速 | ✅ `PARTITION BY RANGE (operated_at)` 月度分区 |
| `login_attempts` | 500万+ | 高速(每次登录一条) | ✅ `PARTITION BY RANGE (attempted_at)` 月度分区 |
| `platform_audit_logs` | 10万+ | 低中速 | ✅ `PARTITION BY RANGE (created_at)` 月度分区 |
| `price_changes` | 50万 | 中速 | 无需分区 |
| `listing_histories` | 20万 | 低速 | 无需分区 |
| `clients` | 10万+ | 中速 | 暂不分区 |
| `viewings` | 100万 | 中速 | 无需分区 |
### 8.1 分区维护策略partition_maintenance_task
所有月度分区表统一由 **Celery Beat 定时任务** `partition_maintenance_task` 维护,每月 1 日凌晨 01:00UTC+8自动执行
```python
# apps/property/tasks.py及 permission/login/shared 各 App 对应任务)
@app.task(name="partition_maintenance_task")
def partition_maintenance_task():
"""
为下一个月预建所有分区表的分区。
- 检查是否已存在目标分区,幂等执行
- 失败时发送 Sentry 告警
"""
tables = [
("follow_logs", "created_at"),
("property_photos", "created_at"),
("permission_change_logs", "operated_at"),
("login_attempts", "attempted_at"),
("public.platform_audit_logs", "created_at"),
]
next_month = date.today().replace(day=1) + relativedelta(months=1)
month_start = next_month
month_end = next_month + relativedelta(months=1)
for table, _key in tables:
suffix = month_start.strftime("%Y_%m")
part_name = f"{table.replace('.', '_')}_{suffix}"
sql = f"""
CREATE TABLE IF NOT EXISTS {part_name}
PARTITION OF {table}
FOR VALUES FROM ('{month_start}') TO ('{month_end}');
"""
with connection.cursor() as cursor:
cursor.execute(sql)
```
**Celery Beat 配置**`celery.py`
```python
app.conf.beat_schedule["partition_maintenance_task"] = {
"task": "partition_maintenance_task",
"schedule": crontab(day_of_month=1, hour=1, minute=0), # 每月1日 01:00 UTC+8
}
```
> ⚠️ **注意**:每张分区表均保留一个 `_default` 默认分区作为兜底,防止任务失败时写入报错。`_default` 分区数据应在运维 SOP 中周期性检查(有数据则说明提前建分区失败)。
---
## 九、必须在开发启动前明确的数据架构决策
| 决策项 | 推荐方案 | 风险 |
|-------|---------|------|
| 小区数据来源 | 预导入基础数据(安居客/链家 API+ 支持手动新增兜底 | 高:影响录入体验 |
| 私盘可见范围 | 录入人所在门店可见(综合业务需求) | 需与权限模块约定 |
| 号码查看权限 | 角色级控制:经纪人限查自己相关房源,店长无限制 | 需合规确认 |
| 重复房源主键 | 主键:手机号 hash辅助小区+楼栋+单元+房号)组合 | 需双重校验 |
| 跟进目的枚举 | 存 lookup_items 表,运营可维护 | 初始化数据需提前收集 |
| 手机号加密算法 | AES-256-GCM密钥存 Django settings生产用 Vault | 密钥管理需单独规划 |
## 十、文档治理与 ADR 联动规则
- 任何会影响数据模型边界的变更(新增/删除表、关键约束变更、索引策略调整、枚举口径变更)必须先在 `ADR.md` 记录为 `accepted`,再修改本文件与子文档。
- 若既有数据决策被替代,必须在 `ADR.md` 增加 `superseded` 记录并指向新 ADR禁止直接覆盖旧结论。
- 提交 PR 时,凡涉及 `DATA_MODEL/*` 结构性变更PR 描述必须包含 ADR ID格式`ADR-YYYYMMDD-XXX`)。
- `DATA_MODEL.md` 只维护全局索引与规则;表级 DDL 以各子文档为权威源,避免双写漂移。
---
*本文档为 Fonrey 系统 DATA MODEL v1.5,随 PRD 与 ADR 迭代同步更新。*
*下一步建议API 接口规范URL 设计 + Request/Response Schema*

View File

@@ -0,0 +1,581 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 客源管理数据模型DATA_MODEL_CLIENT
> **所属系统**: Fonrey 房产经纪管理系统
> **版本**: v1.0
> **日期**: 2026-04-24
> **关联模块**: `apps/client/` — 私客、公客、成交客、跟进记录、带看、智能配房
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、领域概览Domain Overview
### 核心概念
- **Client客源**:有购房/租房意向或历史成交记录的客户。核心实体与房源Property是系统业务闭环的两端。
- **客源类型**
- **私客private**:经纪人独占跟进的意向客户,是本期核心。
- **公客public**:私客超时未跟进或手动转公后,进入全公司共享客源池。
- **成交客transacted**:已完成购房/租房成交的客户,用于复购/转介绍跟进。
- **ClientContact联系人**:一个客源可有多个联系人,每个联系人有独立手机号。手机号加密存储,用于重复检测(「私客与成交客重复」)。
- **ClientRequirement需求信息**:购房/租房的详细偏好。一个客源可同时有「二手」「新房」「租房」三种需求类型(分别对应独立的需求记录)。
- **ClientFollowLog跟进日志**:经纪人与客户每次沟通的书面记录,是客源活跃度计算的数据来源。
- **Viewing带看记录**:与 Property 模块共享此表,记录经纪人带客户看房的过程。见主 DATA_MODEL.md 3.17 节。
- **ClientPropertyMatch智能配房**:系统按需求自动匹配的房源列表,分「录客配房」和「系统配房」两种来源。
- **ClientFavoriteFolder收藏夹**:经纪人自定义的客源分组收藏夹。
### 关键业务规则
1. **私客手机号唯一性**:录入联系人手机号时,系统通过 `phone_hash` 检测是否与现有私客/成交客/公客重复,并在列表顶部提示重复数量。
2. **活跃度计算**:系统根据「最后跟进日期」自动计算客源活跃度,分为:新配偶(新建)/ 7日活跃 / 30日活跃 / 90日活跃 / 即将过期 / 无效。具体阈值由运营配置。
3. **私客自动转公规则**:超过配置天数(如 30 天)无跟进记录,系统自动将私客标记为公客(`transfer_to_public_type = 'auto'`)。
4. **状态机**:客源状态有严格流转规则(见第四章),不可跳过转台。
5. **跟进目的枚举**:由 `lookup_items` 表维护,运营可配置,当前已知 23 项(见 Story 8
6. **号码查看审计**:查看联系人明文号码需记录 `client_follow_logs``log_type = 'sensitive_view'`),不可删除。
7. **需求类型独立存储**:同一客源可同时有「二手购房」「租房」两类需求,分别存储在独立需求记录中,由 `client_requirements.requirement_type` 区分。
---
## 二、实体关系
```
Client (客源主表)
├── 1:N ── ClientContact (联系人,多个号码)
├── 1:N ── ClientRequirement (需求信息,可多类型)
├── 1:N ── ClientFollowLog (跟进日志,高写入频率)
├── 1:N ── ClientViewing (带看预约)
├── 1:N ── ClientPropertyMatch (智能配房结果)
├── 1:1 ── ClientActivityCache (活跃度缓存,异步计算)
├── N:M ── ClientFavoriteFolder (通过 client_folder_items 关联)
└── 1:N ── ClientStatusLog (状态变更日志,不可删)
ClientFavoriteFolder
└── 1:N ── ClientFolderItem (收藏夹中的客源)
Staff (员工)
├── first_recorder_id → Client (首录人)
└── owner_id → Client (归属人)
```
---
## 三、Schema 定义
### 3.1 clients — 客源主表
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_no | VARCHAR(30) | UNIQUE, NOT NULL | 系统生成的客源编号,格式由运营配置(如 KY20260424001 |
| client_type | VARCHAR(20) | NOT NULL DEFAULT 'private' | `private`=私客 / `public`=公客 / `transacted`=成交客 |
| status | VARCHAR(20) | NOT NULL DEFAULT 'buying' | 见下方枚举 |
| grade | VARCHAR(5) | NOT NULL DEFAULT 'C' | `A`=A急迫 / `B`=较强 / `C`=一般 / `D`=较弱 / `E`=暂不关注 |
| property_usage | VARCHAR(30) | NOT NULL DEFAULT 'residential' | `residential`=住宅 / `villa`=别墅 / `commercial_residential`=商住 / `shop`=商铺 / `office`=写字楼 / `other`=其他 |
| buying_purpose | VARCHAR(20)[] | | 购房目的多选:`rigid`=刚需 / `investment`=投资 / `school_district`=学区 / `upgrade`=改善 / `commercial`=商用 / `other`=其他 |
| payment_method | VARCHAR(30) | | `full`=全额 / `mortgage`=商业贷款 / `mortgage_fund`=商贷+公积金 / `fund`=公积金 |
| properties_owned | VARCHAR(20) | | `none`=无 / `local_none`=本地无外地有 / `local_has`=本地有 |
| has_loan_record | BOOLEAN | | 有无贷款记录 |
| id_type | VARCHAR(20) | | 证件类型:`id_card` / `passport` / `hk_macao` / `other` |
| id_number_enc | BYTEA | | 证件号码AES 加密) |
| source | VARCHAR(50) | | 客户来源lookup_items 维护) |
| remarks | TEXT | | 备注最多200字 |
| is_starred | BOOLEAN | NOT NULL DEFAULT FALSE | 是否收藏(快速标记,详细收藏夹用 client_folder_items |
| is_pinned | BOOLEAN | NOT NULL DEFAULT FALSE | 是否置顶(列表顶部置顶) |
| is_big_value | BOOLEAN | NOT NULL DEFAULT FALSE | 是否大价值客户(影响筛选展示) |
| is_protected | BOOLEAN | NOT NULL DEFAULT FALSE | 是否保护客(影响转公逻辑) |
| prefers_new_house | BOOLEAN | | 偏好新房(用于筛选) |
| transfer_to_public_type | VARCHAR(20) | | 转公客方式:`manual`=手动转公 / `auto`=自动转公(超时) / `marketing_jump`=营销客跳公 / `resource_public`=资料客素公 |
| transferred_public_at | TIMESTAMPTZ | | 进入公客池时间 |
| invalid_reason | VARCHAR(30) | | 无效原因:`invalid_phone`=号码无效 / `peer_agent`=同行 / `ad`=广告推销 / `no_intent`=无意向 / `other` |
| invalidated_at | TIMESTAMPTZ | | 标记无效时间 |
| transacted_at | DATE | | 成交日期 |
| transacted_property_id | UUID | FK→properties, SET NULL | 成交关联的房源 |
| transacted_price | NUMERIC(12,2) | | 成交价格(万元) |
| transacted_type | VARCHAR(20) | | 成交类型:`bought`=我购 / `rented`=我租 |
| transacted_property_type | VARCHAR(20) | | 成交房源类型:`second_hand`=二手 / `new_house`=新房 |
| first_recorder_id | UUID | FK→staff, SET NULL | 首录人 |
| owner_id | UUID | FK→staff, SET NULL | 归属人(私客独占跟进人) |
| org_unit_id | UUID | FK→org_units, SET NULL | 归属部门(冗余,加速筛选) |
| activity_level | VARCHAR(20) | | `new_matched`=新配偶 / `active_7d` / `active_30d` / `active_90d` / `expiring` / `frozen` / `invalid`(异步计算)|
| last_active_at | TIMESTAMPTZ | | 最后有效跟进时间(触发器维护) |
| last_follow_at | TIMESTAMPTZ | | 最后跟进时间(冗余,列表排序用) |
| commission_date | DATE | | 委托日期 |
| entrust_count | SMALLINT | NOT NULL DEFAULT 1 | 委托次数(成交后再委托则累加) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除非NULL=已软删除 |
| created_by | UUID | FK→staff, SET NULL | 创建人(操作员工) |
| updated_by | UUID | FK→staff, SET NULL | 最后修改人(操作员工) |
| version | INTEGER | NOT NULL DEFAULT 1 | 乐观锁版本号;每次 UPDATE +1应用层检测 0 行受影响时抛 ConflictError |
**关键索引**
```sql
CREATE UNIQUE INDEX idx_clients_client_no ON clients(client_no) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_type_status ON clients(client_type, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_owner ON clients(owner_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_org_unit ON clients(org_unit_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_activity ON clients(activity_level, last_active_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_grade ON clients(grade) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_transferred_at ON clients(transferred_public_at DESC) WHERE client_type = 'public';
CREATE INDEX idx_clients_last_follow ON clients(last_follow_at DESC NULLS LAST) WHERE deleted_at IS NULL;
```
---
### 3.2 client_contacts — 联系人表
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, CASCADE | 所属客源(关联 clients 表,联系人随客源级联删除) |
| sort_order | SMALLINT | NOT NULL DEFAULT 0 | 联系人1为主联系人sort_order=0 |
| name | VARCHAR(50) | NOT NULL | 联系人姓名 |
| gender | VARCHAR(10) | NOT NULL DEFAULT 'male' | `male`=先生 / `female`=女士 |
| phone_enc | BYTEA | NOT NULL | AES-256-GCM 加密手机号电话1 |
| phone_hash | VARCHAR(64) | NOT NULL | SHA-256 哈希(重复检测) |
| phone_country_code | VARCHAR(10) | NOT NULL DEFAULT '+86' | 国际区号 |
| phone_is_invalid | BOOLEAN | NOT NULL DEFAULT FALSE | 是否被标记为无效号码 |
| phone2_enc | BYTEA | | 备用电话2 |
| phone2_hash | VARCHAR(64) | | 备用电话2哈希SHA-256用于重复检测 |
| wechat | VARCHAR(100) | | 微信号 |
| qq | VARCHAR(20) | | QQ号 |
| remarks | VARCHAR(200) | | 联系人备注最多200字 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除(不影响客源本身) |
| created_by | UUID | FK→staff, SET NULL | 创建人(操作员工) |
**关键索引**
```sql
-- 关键:手机号哈希全局唯一索引(用于重复客源检测)
CREATE INDEX idx_client_contacts_phone_hash ON client_contacts(phone_hash) WHERE deleted_at IS NULL;
CREATE INDEX idx_client_contacts_phone2_hash ON client_contacts(phone2_hash) WHERE phone2_hash IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_client_contacts_client ON client_contacts(client_id) WHERE deleted_at IS NULL;
```
**业务注意**
- `sort_order = 0` 的联系人为主联系人,姓名用于客源姓名显示
- 手机号标记无效(`phone_is_invalid = TRUE`)时,不影响记录存在,但该号码不再参与重复检测
- 联系人软删除后客源仍保留,但若所有联系人均被删则客源实际上无有效号码
---
### 3.3 client_requirements — 需求信息表
一个客源可同时有多类需求(二手购房、新房、租房),每类需求独立一条记录。
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, CASCADE | 所属客源(关联 clients 表,需求随客源级联删除) |
| requirement_type | VARCHAR(20) | NOT NULL | `second_hand`=二手 / `new_house`=新房 / `rental`=租房 |
| is_primary | BOOLEAN | NOT NULL DEFAULT TRUE | 是否为主需求(用于列表展示) |
| budget_min | NUMERIC(12,2) | | 最低预算(万元/元,依据需求类型) |
| budget_max | NUMERIC(12,2) | | 最高预算 |
| area_min | NUMERIC(8,2) | | 最小面积(㎡) |
| area_max | NUMERIC(8,2) | | 最大面积 |
| bedroom_counts | SMALLINT[] | | 可接受卧室数:如 [2,3](多选) |
| floor_preferences | VARCHAR(20)[] | | 楼层偏好多选:`no_first`=不要一层 / `low`=低楼层 / `mid`=中楼层 / `high`=高楼层 / `no_top`=不要顶层 |
| orientations | VARCHAR(10)[] | | 朝向多选:`east`/`south`/`west`/`north` |
| decorations | VARCHAR(10)[] | | 装修偏好多选(枚举同 properties.decoration |
| building_age_ranges | VARCHAR(20)[] | | 楼龄多选:`within_5y`/`5_10y`/`10_15y`/`15_20y`/`over_20y` |
| intent_district_ids | UUID[] | | 意向行政区 ID 数组 |
| intent_business_area_ids | UUID[] | | 意向商圈 ID 数组 |
| intent_complex_names | TEXT | | 意向小区文本逗号分隔最多500字 |
| transportation | VARCHAR(50) | | 交通要求最多50字 |
| intent_school_names | TEXT | | 意向学校(文本,逗号分隔) |
| school_enrollment_date | DATE | | 入学时间月份精度取该月1日存储 |
| traffic_preference | TEXT | | 交通备注 |
| requirement_notes | VARCHAR(200) | | 需求备注最多200字 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
**关键索引**
```sql
CREATE INDEX idx_client_requirements_client ON client_requirements(client_id);
CREATE INDEX idx_client_requirements_type ON client_requirements(requirement_type, client_id);
-- 智能配房时按预算/面积范围查询
CREATE INDEX idx_client_requirements_budget ON client_requirements(budget_min, budget_max);
CREATE INDEX idx_client_requirements_area ON client_requirements(area_min, area_max);
```
---
### 3.4 client_follow_logs — 客源跟进日志
> 与 `follow_logs`(房源跟进)结构类似,独立存储以避免跨模块混淆。
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, CASCADE | 所属客源(关联 clients 表,跟进日志随客源级联删除) |
| log_type | VARCHAR(30) | NOT NULL | 见下方枚举 |
| purpose | VARCHAR(50) | | 跟进目的lookup_items 维护23项 |
| content | TEXT | | 跟进内容最少6字最多500字 |
| log_tag | VARCHAR(50) | | 跟进标签:`has_recording`=有录音 / `has_photo`=有图片 / `not_satisfied`=对房源不满意 / `still_considering`=还在考虑 / `ready_to_deposit`=可交定金 |
| change_detail | JSONB | | 修改跟进专用,格式:`{"field": "grade", "old": "C", "new": "B", "label": "等级"}` |
| is_public | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=仅本人及上级可见 |
| is_deletable | BOOLEAN | NOT NULL DEFAULT TRUE | 敏感信息查看类型为 FALSE不可删除 |
| operator_id | UUID | FK→staff, SET NULL | 操作人 |
| operator_snapshot | JSONB | | `{name, store_group, role}`(防止人员调动后显示异常) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳;仅 is_deletable=TRUE 时可软删 |
**log_type 枚举**
```
written = 写入跟进(经纪人主动写)
modified = 修改跟进(字段变更自动生成)
sensitive_view= 敏感信息查看(查看号码等,不可删)
other = 其他跟进(系统自动:新增私客/状态变更等)
system = 系统日志
```
**关键索引**
```sql
CREATE INDEX idx_client_follow_logs_client_time ON client_follow_logs(client_id, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_client_follow_logs_type ON client_follow_logs(client_id, log_type, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_client_follow_logs_operator ON client_follow_logs(operator_id, created_at DESC) WHERE deleted_at IS NULL;
-- 不可删记录(合规审计)
CREATE INDEX idx_client_follow_sensitive ON client_follow_logs(client_id, created_at DESC) WHERE log_type = 'sensitive_view';
```
---
### 3.5 client_follow_log_attachments — 跟进附件
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| follow_log_id | UUID | NOT NULL, FK→client_follow_logs, CASCADE | 所属跟进日志(附件随日志级联删除) |
| file_key | TEXT | NOT NULL | R2/S3 存储路径 |
| file_name | VARCHAR(255) | NOT NULL | 原始文件名(用于展示和下载) |
| file_size | INTEGER | NOT NULL | bytes最大 20MB |
| file_type | VARCHAR(10) | CHECK | `bmp`/`jpg`/`png`/`gif` |
| has_location | BOOLEAN | NOT NULL DEFAULT FALSE | 是否含 GPS 位置信息 |
| sort_order | SMALLINT | NOT NULL DEFAULT 0 | 附件排序顺序 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
---
### 3.6 client_viewings — 带看记录(客源侧视图)
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, RESTRICT | 所属客源(带看记录仅软删除,不随客源删除) |
| property_id | UUID | NOT NULL, FK→properties, RESTRICT | 带看房源(房源删除时保留带看记录) |
| viewing_type | VARCHAR(20) | NOT NULL DEFAULT 'viewing' | `appointment`=预约 / `viewing`=带看 / `revisit`=复看 / `empty`=空看 |
| agent_id | UUID | FK→staff, SET NULL | 主带看经纪人 |
| companion_ids | UUID[] | | 陪看人员 ID 数组最多5人 |
| cooperator_ids | UUID[] | | 合作带看人 ID 数组最多5人 |
| scheduled_at | TIMESTAMPTZ | | 预约时间 |
| viewing_start_at | TIMESTAMPTZ | | 实际带看开始时间 |
| viewing_end_at | TIMESTAMPTZ | | 结束时间 |
| situation | TEXT | | 带看情况必填≥6字 |
| client_intent | VARCHAR(20) | | 客户意向:`interested`=感兴趣 / `not_interested`=不感兴趣 / `negotiating`=谈判中 / `cancelled`=取消 |
| viewing_progress | SMALLINT | | 带看进度1=一看2=二看...,冗余字段,触发器维护) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳;带看记录可软删除 |
| created_by | UUID | FK→staff, SET NULL | 创建人(操作员工) |
**关键索引**
```sql
CREATE INDEX idx_client_viewings_client ON client_viewings(client_id, viewing_start_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_client_viewings_property ON client_viewings(property_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_client_viewings_agent ON client_viewings(agent_id) WHERE deleted_at IS NULL;
```
---
### 3.7 client_property_matches — 智能配房
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, CASCADE | 所属客源 |
| property_id | UUID | NOT NULL, FK→properties, CASCADE | 匹配的房源 |
| match_source | VARCHAR(20) | NOT NULL DEFAULT 'recorded' | `recorded`=录客配房(基于录入需求) / `system`=系统配房(算法推荐) |
| match_group | VARCHAR(30) | | 分组:`quality_layout`=优质户型 / `price_reduced`=降价 / `hot`=热门 / `newly_listed`=新上 |
| match_score | NUMERIC(5,2) | | 匹配度评分0-100 |
| match_reasons | JSONB | | 匹配原因详情,格式:`[{"key": "budget", "match": true}, ...]` |
| status | VARCHAR(20) | NOT NULL DEFAULT 'suggested' | `suggested`=待推送 / `shared`=已分享 / `rejected`=已反馈不合适 / `viewed`=客户已查看 |
| shared_at | TIMESTAMPTZ | | 分享时间 |
| feedback | VARCHAR(50) | | 反馈原因lookup_items 维护) |
| calculated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 配房计算时间 |
| created_by | UUID | FK→staff, SET NULL | 触发配房操作的员工录客配房时记录系统配房可为NULL |
**关键索引**
```sql
CREATE UNIQUE INDEX idx_client_matches_pair ON client_property_matches(client_id, property_id);
CREATE INDEX idx_client_matches_client ON client_property_matches(client_id, match_source, match_group);
CREATE INDEX idx_client_matches_status ON client_property_matches(client_id, status) WHERE status != 'rejected';
```
---
### 3.8 client_status_logs — 状态变更日志(不可删)
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| client_id | UUID | NOT NULL, FK→clients, RESTRICT | 所属客源状态日志永久保留RESTRICT 防止删除客源) |
| change_type | VARCHAR(30) | NOT NULL | `status_change`=改状态 / `grade_change`=改等级 / `to_public`=转公客 / `to_transacted`=转成交 / `to_invalid`=转无效 / `owner_change`=改归属人 / `source_change`=改来源 |
| old_value | JSONB | | 变更前快照,格式:`{"status": "buying", "label": "求购"}` |
| new_value | JSONB | | 变更后快照 |
| reason | TEXT | | 变更理由改状态必填最多200字 |
| operator_id | UUID | NOT NULL, FK→staff, RESTRICT | 操作人(必填,状态变更审计用) |
| operated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 操作时间(系统自动记录) |
| ⚠️ 无 deleted_at | — | — | 此表记录**不可删除** |
**关键索引**
```sql
CREATE INDEX idx_client_status_logs_client ON client_status_logs(client_id, operated_at DESC);
CREATE INDEX idx_client_status_logs_type ON client_status_logs(change_type, operated_at DESC);
```
---
### 3.9 client_favorite_folders — 私客收藏夹
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| staff_id | UUID | NOT NULL, FK→staff, CASCADE | 收藏夹所属经纪人 |
| name | VARCHAR(10) | NOT NULL | 收藏夹名称最多10字 |
| is_default | BOOLEAN | NOT NULL DEFAULT FALSE | 系统默认收藏夹 |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 收藏夹显示顺序(升序) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除 |
```sql
CREATE INDEX idx_favorite_folders_staff ON client_favorite_folders(staff_id) WHERE deleted_at IS NULL;
-- 每个经纪人只能有一个默认收藏夹
CREATE UNIQUE INDEX idx_favorite_folders_default ON client_favorite_folders(staff_id) WHERE is_default = TRUE AND deleted_at IS NULL;
```
---
### 3.10 client_folder_items — 收藏夹中的客源
| 字段 | 类型 | 约束 | 业务说明 |
| ----------- | ---------------------- | --------------------------------------------- | ---- |
| folder_id | UUID | NOT NULL, FK→client_favorite_folders, CASCADE | 所属收藏夹 |
| client_id | UUID | NOT NULL, FK→clients, CASCADE | 被收藏的客源 |
| added_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 加入收藏夹时间 |
| PRIMARY KEY | (folder_id, client_id) | | 联合主键(同一客源在同一收藏夹只能出现一次) |
```sql
CREATE INDEX idx_folder_items_client ON client_folder_items(client_id);
```
---
### 3.11 client_school_preferences — 意向学校(多对多)
> 单独拆表便于学校搜索,避免文本字段模糊查询。
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| requirement_id | UUID | NOT NULL, FK→client_requirements, CASCADE | 所属需求(意向学校随需求级联删除) |
| school_id | UUID | FK→schools, SET NULL | 从学校表选择,允许为 NULL自由输入 |
| school_name | VARCHAR(100) | NOT NULL | 学校名称(当 school_id 为 NULL 时为手动输入) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
```sql
CREATE INDEX idx_school_prefs_requirement ON client_school_preferences(requirement_id);
```
---
## 四、枚举常量
### clients.status客源状态
```
buying = 求购(私客活跃态)
renting = 求租(私客活跃态)
buy_or_rent = 租购(私客活跃态)
suspended = 暂缓(暂时无需求,不计入活跃统计)
bought = 已购(成交客:我购)
rented_done = 已租(成交客:我租)
public = 公客(已转入公客池)
invalid = 无效(号码无效/无意向等)
```
**状态流转规则**
```
buying/renting/buy_or_rent
→ suspended (改状态操作,可逆)
→ public (手动转公 or 超时自动转公,不可逆)
→ bought/rented_done (转成交,不可逆)
→ invalid (转无效,需经理审批后可恢复)
```
### clients.grade等级
```
A = A(急迫)
B = B(较强)
C = C(一般,默认值)
D = D(较弱)
E = E(暂不关注)
```
### client_status_logs.change_type变更类型
```
status_change = 改状态(含改等级时同时改状态的情况)
grade_change = 改等级
to_public = 转公客manual=手动 or auto=自动)
to_transacted = 转成交(记录成交信息)
to_invalid = 转无效(含无效原因)
owner_change = 改归属人
source_change = 改来源
merge = 合并客源(被合并的记录保留日志)
```
### clients.activity_level活跃度分层系统计算
| 值 | 含义 | 触发条件(示例,以运营配置为准) |
| ------------- | ------ | ------------------ |
| `new_matched` | 新匹配 | 录入后 3 天内 |
| `active_7d` | 7日活跃 | 最后跟进在 7 天内 |
| `active_30d` | 30日活跃 | 最后跟进在 30 天内 |
| `active_90d` | 90日活跃 | 最后跟进在 90 天内 |
| `expiring` | 即将过期 | 距自动转公还有 N 天 |
| `frozen` | 冻结(暂缓) | status = suspended |
| `invalid` | 无效 | status = invalid |
---
## 五、查询模式
### 5.1 私客列表页(求购 Tab核心查询
```sql
-- 典型:当前经纪人名下 + 求购状态 + 等级筛选 + 按最后跟进排序
SELECT c.id, c.status, c.grade, c.activity_level,
c.last_follow_at, c.commission_date, c.buying_purpose,
cc.name AS contact_name, -- JOIN 主联系人
s.name AS owner_name, ou.name AS org_unit_name,
COUNT(cpm.id) AS match_count -- 智能配房数量
FROM clients c
JOIN client_contacts cc ON cc.client_id = c.id AND cc.sort_order = 0 AND cc.deleted_at IS NULL
JOIN staff s ON s.id = c.owner_id
JOIN org_units ou ON ou.id = c.org_unit_id
LEFT JOIN client_property_matches cpm ON cpm.client_id = c.id AND cpm.status != 'rejected'
WHERE c.client_type = 'private'
AND c.owner_id = :current_staff_id -- 与我相关
AND c.status IN ('buying', 'buy_or_rent')
AND c.deleted_at IS NULL
GROUP BY c.id, cc.name, s.name, ou.name
ORDER BY c.last_follow_at DESC NULLS LAST
LIMIT 20 OFFSET :offset;
```
### 5.2 重复客源检测(录入/编辑时触发)
```sql
-- 手机号哈希碰撞检测(私客、成交客、公客三池同时检查)
SELECT c.id, c.client_type, c.status, c.client_no,
cc.name AS contact_name
FROM client_contacts cc
JOIN clients c ON cc.client_id = c.id
WHERE cc.phone_hash = :new_phone_hash
AND cc.deleted_at IS NULL
AND c.deleted_at IS NULL
AND c.status != 'invalid';
```
### 5.3 活跃度批量更新Celery 定时任务,每日凌晨执行)
```sql
-- 更新活跃度以7日活跃为例
UPDATE clients
SET activity_level = 'active_7d',
updated_at = NOW()
WHERE client_type = 'private'
AND status NOT IN ('invalid', 'public', 'bought', 'rented_done')
AND last_follow_at >= NOW() - INTERVAL '7 days'
AND deleted_at IS NULL;
```
### 5.4 私客自动转公超时无跟进Celery 定时任务)
```sql
-- 查询应自动转公的私客阈值由运营配置假设30天
SELECT id FROM clients
WHERE client_type = 'private'
AND status IN ('buying', 'renting', 'buy_or_rent')
AND last_follow_at < NOW() - INTERVAL '30 days'
AND is_protected = FALSE
AND deleted_at IS NULL;
-- 后续在 Application 层批量更新 client_type='public', transfer_to_public_type='auto'
```
---
## 六、触发器
### 6.1 last_follow_at 自动维护
```sql
-- 每次写入跟进日志时,自动更新 clients.last_follow_at
CREATE OR REPLACE FUNCTION update_client_last_follow()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.log_type = 'written' THEN
UPDATE clients
SET last_follow_at = NEW.created_at,
last_active_at = NEW.created_at,
updated_at = NOW()
WHERE id = NEW.client_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_client_last_follow
AFTER INSERT ON client_follow_logs
FOR EACH ROW EXECUTE FUNCTION update_client_last_follow();
```
### 6.2 viewing_progress 自动维护
```sql
-- 每次新增带看记录时,自动更新 clients 的带看进度冗余字段
CREATE OR REPLACE FUNCTION update_client_viewing_progress()
RETURNS TRIGGER AS $$
BEGIN
UPDATE clients
SET updated_at = NOW()
WHERE id = NEW.client_id;
-- Application 层根据 COUNT(viewings) 计算具体进度
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_client_viewing_progress
AFTER INSERT ON client_viewings
FOR EACH ROW EXECUTE FUNCTION update_client_viewing_progress();
```
---
## 七、禁止操作
-**严禁硬删除 clients 记录**:无效/转公客/成交客均通过 status 和 soft delete 处理,历史跟进/带看依赖外键
-**严禁删除 client_status_logs**:状态变更为不可变审计日志
-**严禁删除 log_type='sensitive_view' 的跟进记录**:必须通过 `is_deletable=FALSE` 约束在应用层拦截
-**严禁明文存储联系人手机号**:必须走 `EncryptedPhoneField``phone_hash` 用于索引和重复检测
-**严禁跳过状态机流转**:如私客不可直接跳过「求购」变为「无效」而不生成 status log
-**严禁在没有 `client_type` 过滤的情况下查询客源列表**:私客/公客/成交客数据量均较大,必须按类型隔离查询
-**严禁查询 clients 时不带 `deleted_at IS NULL`**:软删除过滤必须存在

View File

@@ -0,0 +1,555 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 楼盘与区域数据模型DATA_MODEL_COMPLEX
> **所属系统**: Fonrey 房产经纪管理系统
> **版本**: v1.0
> **日期**: 2026-04-24
> **关联模块**: `apps/complex/` — 楼盘/小区、楼栋、结构(楼层+房号)、区域、学校
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、领域概览Domain Overview
### 核心概念
- **Complex楼盘/小区)**:房源录入的基础底座。每套房源必须归属于某一楼盘。楼盘数据由运营/数据管理员集中维护,质量直接影响房源录入效率和搜索精度。
- **Building楼栋/单元)**楼盘下的物理楼栋是组织房源位置的第二级。一个楼盘可有多个楼栋如「1号楼」「2栋2单元」
- **RoomUnit房号/结构单元)**:楼栋内特定楼层的某个房间标识,是房源定位的最细粒度。支持「标准结构」(经运营标准化)和「非标结构」(未归一化)两类。
- **District城区/行政区)**:行政区划,如静安区、闵行区。
- **BusinessArea商圈/板块)**:商圈是区域内的细分市场区域,如「南京西路商圈」,一个楼盘可跨多个商圈。
- **School学校**:楼盘对口学校,是买家购房决策的核心关注点。一个楼盘可关联多所学校,一所学校可对口多个楼盘。
- **MetroLine / MetroStation地铁线路/站点)**:楼盘与最近地铁站的距离关系,用于通勤筛选。
### 关键业务规则
1. **楼盘名称不可在编辑页修改**:楼盘名称(`name`)变更须通过「合并楼盘」或「申请流程」处理,防止经纪人随意改名造成数据混乱。
2. **数据锁定机制**:楼盘有 4 类锁(楼栋锁/房号锁/信息锁/标准房号锁),锁定后对应数据只有管理员可解锁修改。
3. **非标结构处理**:未与标准结构关联的房号为「非标」,系统记录非标数量,引导运营逐步消除。
4. **搜索依赖全文检索**:楼盘名称、别名、地址需维护 `search_vector``tsvector`)以支持模糊搜索和联想补全。
5. **地理坐标优先级**:楼盘坐标是区域聚合展示(地图找房)的核心数据,完整度目标 ≥ 90%。
6. **学校关联影响房源**:从楼盘详情删除对口学校,会级联删除该楼盘下所有房源的对应学区标注。
---
## 二、实体关系
```
District (城区/行政区)
└── 1:N ── BusinessArea (商圈/板块)
└── N:M ── Complex (through complex_business_areas)
Complex (楼盘)
├── N:M ── BusinessArea (through complex_business_areas)
├── N:M ── School (through complex_schools)
├── N:M ── MetroStation (through complex_metro_stations, 附带距离)
├── 1:N ── Building (楼栋/单元)
│ └── 1:N ── RoomUnit (楼层+房号)
├── 1:N ── ComplexPhoto (楼盘照片:楼盘图/户型图/VR)
├── 1:N ── ComplexAttachment(附件)
├── 1:N ── ComplexPriceTrend(价格走势,月度)
└── 1:N ── ComplexAlias (别名)
MetroLine (地铁线路)
└── 1:N ── MetroStation (站点)
```
---
## 三、Schema 定义
### 3.1 districts — 城区/行政区
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| city | VARCHAR(50) | NOT NULL | 所属城市(支持多城市扩展,如「上海」「北京」) |
| name | VARCHAR(50) | NOT NULL | 行政区名称,如「静安区」 |
| short_name | VARCHAR(20) | | 简称,如「静安」 |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 列表展示排序 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用(不在筛选项中展示) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
```sql
CREATE UNIQUE INDEX idx_districts_city_name ON districts(city, name) WHERE is_active = TRUE;
```
---
### 3.2 business_areas — 商圈/板块
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| district_id | UUID | NOT NULL, FK→districts, RESTRICT | 所属城区 |
| name | VARCHAR(100) | NOT NULL | 商圈名称 |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 列表展示排序 |
| latitude | NUMERIC(10,7) | | 商圈中心坐标(纬度) |
| longitude | NUMERIC(10,7) | | 商圈中心坐标(经度) |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用(不在筛选项中展示) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
```sql
CREATE INDEX idx_business_areas_district ON business_areas(district_id) WHERE is_active = TRUE;
CREATE UNIQUE INDEX idx_business_areas_name ON business_areas(district_id, name);
```
---
### 3.3 metro_lines — 地铁线路
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| city | VARCHAR(50) | NOT NULL | 所属城市 |
| name | VARCHAR(50) | NOT NULL | 线路名如「1号线」 |
| color | VARCHAR(7) | | 线路颜色 HEX`#E3002B` |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 列表展示排序 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用(不在筛选项中展示) |
---
### 3.4 metro_stations — 地铁站点
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| metro_line_id | UUID | NOT NULL, FK→metro_lines, CASCADE | 所属线路 |
| name | VARCHAR(50) | NOT NULL | 站名 |
| latitude | NUMERIC(10,7) | | 站点坐标 |
| longitude | NUMERIC(10,7) | | 站点坐标(经度) |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 沿线排序 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用(不在筛选项中展示) |
```sql
CREATE INDEX idx_metro_stations_line ON metro_stations(metro_line_id) WHERE is_active = TRUE;
```
---
### 3.5 schools — 学校
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| district_id | UUID | FK→districts, SET NULL | 所属城区 |
| name | VARCHAR(100) | NOT NULL | 学校名称 |
| type | VARCHAR(20) | | 学校类型:`primary`=小学 / `middle`=初中 / `high`=高中 / `k9`=九年一贯制 / `k12`=十二年一贯制 |
| nature | VARCHAR(20) | | 学校性质:`public`=公立 / `private`=私立 / `international`=国际学校 |
| level | VARCHAR(20) | | 学校等级:`normal`=普通 / `key`=重点 / `top`=名校 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用(不在筛选项中展示) |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
```sql
CREATE INDEX idx_schools_district ON schools(district_id) WHERE is_active = TRUE;
CREATE INDEX idx_schools_name_trgm ON schools USING gin(name gin_trgm_ops);
```
---
### 3.6 complexes — 楼盘/小区(核心基础表)
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| name | VARCHAR(200) | NOT NULL | 标准楼盘名称,**不可在编辑页修改**(需走合并/申请流程) |
| district_id | UUID | FK→districts, SET NULL | 所属城区 |
| address | VARCHAR(500) | | 详细地址(不可在编辑页修改,需走纠错流程) |
| address_summary | VARCHAR(100) | | 概要地址如「海波路1000弄」可编辑 |
| latitude | NUMERIC(10,7) | | 楼盘坐标(纬度),完整度目标 ≥ 90% |
| longitude | NUMERIC(10,7) | | 地铁站经度坐标WGS84 |
| **物业属性** | | | |
| property_usage_types | VARCHAR(20)[] | | 物业类型多选:`residential`/`villa`/`commercial_residential`/`commercial`/`office`/`other` |
| building_structure | VARCHAR(30) | | 楼栋结构枚举(运营维护):`unit_room`=单元-房号 / `other`=其他 |
| building_type | VARCHAR(20) | | 建筑类型:`slab`=板楼 / `tower`=塔楼 / `slab_tower`=板塔结合 |
| land_use_years | VARCHAR(30) | | 土地使用年限如「70年」 |
| built_year | SMALLINT | | 竣工年份(可多选,存最早竣工年) |
| built_years | SMALLINT[] | | 竣工年份多值(楼盘分期竣工) |
| ownership_category | VARCHAR(30)[] | | 权属类别多选(运营维护枚举) |
| total_units | INTEGER | | 单元总数 |
| total_households | INTEGER | | 总户数 |
| **建设信息** | | | |
| total_floor_area | NUMERIC(12,2) | | 小区总建筑面积 |
| plot_area | NUMERIC(12,2) | | 小区占地面积 |
| plot_ratio | NUMERIC(5,2) | | 容积率 |
| green_rate | NUMERIC(5,2) | | 绿化率(% |
| developer | VARCHAR(200) | | 开发商 |
| **物业信息** | | | |
| property_company | VARCHAR(200) | | 物业公司 |
| property_fee | NUMERIC(8,2) | | 物业费(元/m²/月) |
| property_phone | VARCHAR(30) | | 物业电话 |
| **停车** | | | |
| parking_total | INTEGER | | 车位总数 |
| parking_underground | INTEGER | | 地下车位数 |
| parking_ratio | VARCHAR(20) | | 停车位配比如「100:63」 |
| **配套** | | | |
| water_type | VARCHAR(10) | | `civil`=民水 / `commercial`=商水 |
| electricity_type | VARCHAR(10) | | `civil`=民电 / `commercial`=商电 |
| has_central_heating | BOOLEAN | | 是否统一供暖 |
| has_gas | BOOLEAN | | 是否有燃气 |
| remarks | TEXT | | 备注 |
| **锁定状态** | | | |
| lock_building | BOOLEAN | NOT NULL DEFAULT FALSE | 楼栋锁(锁定后不可增删楼栋) |
| lock_room | BOOLEAN | NOT NULL DEFAULT FALSE | 房号锁 |
| lock_info | BOOLEAN | NOT NULL DEFAULT FALSE | 信息锁(锁定后基本信息只读) |
| lock_standard_room | BOOLEAN | NOT NULL DEFAULT FALSE | 标准房号锁 |
| **全文检索** | | | |
| search_vector | TSVECTOR | | 由触发器自动维护name + alias + address |
| **状态** | | | |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已停用楼盘 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除非NULL=已软删除 |
| created_by | UUID | FK→staff, SET NULL | 创建人(操作员工) |
| updated_by | UUID | FK→staff, SET NULL | 最后更新人(操作员工) |
| version | INTEGER | NOT NULL DEFAULT 1 | 乐观锁版本号;每次 UPDATE +1应用层检测 0 行受影响时抛 ConflictError |
**关键索引**
```sql
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 AND latitude IS NOT NULL;
CREATE INDEX idx_complexes_active ON complexes(is_active) WHERE deleted_at IS NULL;
```
---
### 3.7 complex_aliases — 楼盘别名
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘(别名随楼盘级联删除) |
| alias | VARCHAR(200) | NOT NULL | 别名最多20字/条,多别名多行存储) |
| is_system | BOOLEAN | NOT NULL DEFAULT FALSE | TRUE=系统/标准别名只读FALSE=用户自定义 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| created_by | UUID | FK→staff, SET NULL | 创建人(操作员工) |
```sql
CREATE INDEX idx_complex_aliases_complex ON complex_aliases(complex_id);
CREATE INDEX idx_complex_aliases_alias_trgm ON complex_aliases USING gin(alias gin_trgm_ops);
```
---
### 3.8 complex_business_areas — 楼盘与商圈多对多
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| business_area_id | UUID | NOT NULL, FK→business_areas, CASCADE | 关联商圈 |
| is_primary | BOOLEAN | NOT NULL DEFAULT FALSE | 主商圈(唯一)用于列表显示 |
| PRIMARY KEY | (complex_id, business_area_id) | | 联合主键(楼盘与商圈多对多) |
```sql
-- 主商圈只能有一个
CREATE UNIQUE INDEX idx_complex_biz_area_primary ON complex_business_areas(complex_id) WHERE is_primary = TRUE;
```
---
### 3.9 complex_schools — 楼盘与学校关联
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| school_id | UUID | NOT NULL, FK→schools, CASCADE | 对口学校 |
| zone_type | VARCHAR(30) | | 学区类型:`guaranteed`=对口 / `reference`=参考 / `lottery`=摇号 |
| PRIMARY KEY | (complex_id, school_id) | | 联合主键(楼盘与学校多对多) |
```sql
CREATE INDEX idx_complex_schools_school ON complex_schools(school_id);
```
**业务注意**删除此关联记录时需同步清理对应房源的学区标注Application 层事务处理)
---
### 3.10 complex_metro_stations — 楼盘与地铁站关联
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| station_id | UUID | NOT NULL, FK→metro_stations, CASCADE | 关联地铁站 |
| distance_meters | INTEGER | | 步行距离(米) |
| PRIMARY KEY | (complex_id, station_id) | | 联合主键(楼盘与地铁站多对多) |
```sql
CREATE INDEX idx_complex_metro_complex ON complex_metro_stations(complex_id);
CREATE INDEX idx_complex_metro_station ON complex_metro_stations(station_id);
```
---
### 3.11 buildings — 楼栋/单元
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| name | VARCHAR(50) | NOT NULL | 楼栋名如「1号楼」「A栋2单元」 |
| is_standard | BOOLEAN | NOT NULL DEFAULT FALSE | TRUE=标准结构(经运营核准) |
| property_usage_type | VARCHAR(20) | | 物业类型(可与楼盘不同,如商住楼盘内有纯商铺楼栋) |
| built_year | SMALLINT | | 竣工年份 |
| total_floors | SMALLINT | | 总层数 |
| land_use_years | VARCHAR(30) | | 土地使用年限 |
| has_elevator | BOOLEAN | | 是否有电梯 |
| school_id | UUID | FK→schools, SET NULL | 关联对口学校(楼栋级别的学区差异) |
| 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 | FK→staff, SET NULL | 创建人(操作员工) |
```sql
CREATE INDEX idx_buildings_complex ON buildings(complex_id) WHERE is_active = TRUE;
CREATE UNIQUE INDEX idx_buildings_name ON buildings(complex_id, name) WHERE is_active = TRUE;
```
---
### 3.12 room_units — 房号/结构单元(楼层+房间号)
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| building_id | UUID | NOT NULL, FK→buildings, CASCADE | 所属楼栋 |
| floor | SMALLINT | NOT NULL | 楼层(实际层数,地下为负数) |
| floor_name | VARCHAR(20) | | 楼层名称展示如「1层」「B1层」 |
| room_no | VARCHAR(30) | NOT NULL | 房号如「01」「101」 |
| display_no | VARCHAR(50) | | 展示用完整房号如「3-1-101」 |
| is_standard | BOOLEAN | NOT NULL DEFAULT FALSE | TRUE=已归一化为标准结构 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE=已拆除/不存在 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
```sql
CREATE INDEX idx_room_units_building ON room_units(building_id) WHERE is_active = TRUE;
CREATE UNIQUE INDEX idx_room_units_unique ON room_units(building_id, floor, room_no) WHERE is_active = TRUE;
```
---
### 3.13 complex_photos — 楼盘照片
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| category | VARCHAR(20) | NOT NULL | `complex`=楼盘图 / `layout`=户型图 / `vr`=VR全景 / `other`=其他 |
| file_key | TEXT | NOT NULL | R2/S3 路径 |
| thumbnail_key | TEXT | | 缩略图路径 |
| file_name | VARCHAR(255) | | 原始文件名 |
| file_size | INTEGER | | 文件大小bytes |
| width | INTEGER | | 图片宽度px |
| height | INTEGER | | 图片高度px |
| is_cover | BOOLEAN | NOT NULL DEFAULT FALSE | 楼盘封面图 |
| sort_order | SMALLINT | NOT NULL DEFAULT 0 | 同类别内的排序顺序 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| created_by | UUID | FK→staff, SET NULL | 上传人(操作员工) |
```sql
CREATE INDEX idx_complex_photos_complex ON complex_photos(complex_id);
CREATE INDEX idx_complex_photos_category ON complex_photos(complex_id, category);
CREATE UNIQUE INDEX idx_complex_photos_cover ON complex_photos(complex_id) WHERE is_cover = TRUE;
```
---
### 3.14 complex_attachments — 楼盘附件
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| file_key | TEXT | NOT NULL | R2/S3 存储路径 |
| file_name | VARCHAR(255) | NOT NULL | 原始文件名 |
| file_size | INTEGER | | 文件大小bytes |
| file_type | VARCHAR(50) | | MIME type |
| sort_order | SMALLINT | NOT NULL DEFAULT 0 | 同类别内的排序顺序 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| created_by | UUID | FK→staff, SET NULL | 上传人(操作员工) |
---
### 3.15 complex_price_trends — 楼盘价格走势(月度)
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| complex_id | UUID | NOT NULL, FK→complexes, CASCADE | 所属楼盘 |
| record_month | DATE | NOT NULL | 月份统一存为该月1日如 2026-04-01 |
| avg_sale_price | NUMERIC(12,2) | | 月均售价(万元/套) |
| avg_unit_price | NUMERIC(10,2) | | 月均单价(元/m² |
| transaction_count | INTEGER | | 成交套数 |
| listing_count | INTEGER | | 当月挂牌套数 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
```sql
CREATE UNIQUE INDEX idx_complex_price_trend_month ON complex_price_trends(complex_id, record_month);
CREATE INDEX idx_complex_price_trend_complex ON complex_price_trends(complex_id, record_month DESC);
```
---
## 四、枚举常量
### complexes.building_type建筑类型
```
slab = 板楼
tower = 塔楼
slab_tower = 板塔结合
```
### complexes.water_type / electricity_type
```
civil = 民水/民电(住宅水电费率)
commercial = 商水/商电(商业水电费率,费用较高,影响买家决策)
```
### complex_schools.zone_type学区类型
```
guaranteed = 对口(直升)
reference = 参考(可能入读)
lottery = 摇号(通过摇号入学)
```
### buildings.is_standard / room_units.is_standard
```
TRUE = 已标准化(楼栋/房号已经运营核准,可用于精准房源定位)
FALSE = 非标(用户自输入,未核准,存在歧义风险)
```
---
## 五、查询模式
### 5.1 楼盘名称联想搜索(录入房源时的自动补全)
```sql
-- 使用全文检索向量,支持中文分词近似匹配
SELECT id, name, address_summary, district_id
FROM complexes
WHERE search_vector @@ plainto_tsquery('simple', :keyword)
OR name ILIKE :keyword_prefix -- 前缀精确匹配优先
AND deleted_at IS NULL
AND is_active = TRUE
ORDER BY
ts_rank(search_vector, plainto_tsquery('simple', :keyword)) DESC,
name
LIMIT 20;
```
### 5.2 楼盘列表(含房源数量统计)
```sql
SELECT
c.id, c.name, c.address, c.latitude, c.longitude,
d.name AS district_name,
ba.name AS primary_business_area,
COUNT(DISTINCT b.id) AS building_count,
COUNT(DISTINCT p.id) FILTER (WHERE p.status IN ('for_sale','for_sale_rent')) AS sale_count,
COUNT(DISTINCT p.id) FILTER (WHERE p.status IN ('for_rent','for_sale_rent')) AS rent_count
FROM complexes c
LEFT JOIN districts d ON d.id = c.district_id
LEFT JOIN complex_business_areas cba ON cba.complex_id = c.id AND cba.is_primary = TRUE
LEFT JOIN business_areas ba ON ba.id = cba.business_area_id
LEFT JOIN buildings b ON b.complex_id = c.id AND b.is_active = TRUE
LEFT JOIN properties p ON p.complex_id = c.id AND p.deleted_at IS NULL
WHERE c.deleted_at IS NULL
AND c.district_id = ANY(:district_ids) -- 区域筛选
GROUP BY c.id, d.name, ba.name
ORDER BY c.name
LIMIT 20 OFFSET :offset;
```
### 5.3 查询楼盘下的楼层-房号矩阵(结构管理)
```sql
-- 选中单元后,加载楼层×房号矩阵
SELECT
ru.floor,
ru.floor_name,
ru.room_no,
ru.display_no,
ru.is_standard,
p.id AS property_id, -- 如果该房号已有房源,关联显示
p.status AS property_status
FROM room_units ru
LEFT JOIN properties p ON p.building_id = ru.building_id
AND p.room_no = ru.room_no
AND p.floor = ru.floor
AND p.deleted_at IS NULL
WHERE ru.building_id = :building_id
AND ru.is_active = TRUE
ORDER BY ru.floor DESC, ru.room_no;
```
---
## 六、触发器
### 6.1 楼盘全文检索向量(含别名)
```sql
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.address_summary, '')), '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, address_summary, address
ON complexes
FOR EACH ROW EXECUTE FUNCTION update_complex_search_vector();
-- 别名变更时同步更新楼盘 search_vector
CREATE OR REPLACE FUNCTION update_complex_search_on_alias()
RETURNS TRIGGER AS $$
BEGIN
UPDATE complexes
SET search_vector = (
setweight(to_tsvector('simple', COALESCE(name, '')), 'A') ||
setweight(to_tsvector('simple',
COALESCE((SELECT string_agg(alias, ' ') FROM complex_aliases WHERE complex_id = complexes.id), '')), 'B') ||
setweight(to_tsvector('simple', COALESCE(address_summary, '')), 'C') ||
setweight(to_tsvector('simple', COALESCE(address, '')), 'D')
),
updated_at = NOW()
WHERE id = COALESCE(NEW.complex_id, OLD.complex_id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_complex_alias_search
AFTER INSERT OR UPDATE OR DELETE ON complex_aliases
FOR EACH ROW EXECUTE FUNCTION update_complex_search_on_alias();
```
---
## 七、禁止操作
-**严禁直接修改 complexes.name**:楼盘名称变更必须走「楼盘合并」流程或「管理员申请」,通过 Application 层拦截任何直接 UPDATE `name` 字段的操作
-**严禁硬删除 complexes 记录**:有房源关联的楼盘不可删除(`RESTRICT` 外键),已有房源的楼盘软删除后房源仍可正常访问
-**严禁删除 complex_schools 关联而不清理房源学区标注**:必须在同一事务中清理对应 `property.school_ids` 数据
-**严禁在楼盘坐标为 NULL 时将其用于地图聚合**:坐标为空时不参与地图展示,过滤条件:`WHERE latitude IS NOT NULL`
-**严禁在 lock_info=TRUE 时绕过 Application 层直接修改楼盘信息字段**:锁定状态必须在服务层检查,不依赖数据库约束
-**严禁在没有 deleted_at IS NULL 过滤的情况下查询 complexes**:楼盘软删除过滤必须存在

View File

@@ -0,0 +1,526 @@
# Fonrey — 登录与账号认证数据模型DATA_MODEL_LOGIN
> **所属系统**: Fonrey 房产经纪管理系统
> **版本**: v1.0
> **日期**: 2026-04-24
> **关联模块**: `apps/accounts/` — 账号认证、登录安全、密码管理
> **关联 PRD**: `Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` (v2.0)
> **关联技术方案**: `Project/fonrey/TECH_STACK/登录管理技术方案.md`
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、领域概览Domain Overview
### 核心概念
- **UserAccount用户账号**:系统登录主体,必须与员工档案(`org.Staff`1:1 绑定。分为 Tenant Admin超级管理账号每租户唯一username 固定为联系人手机号和普通员工账号username 固定为员工手机号)。
- **LoginAttempt登录尝试记录**:记录每次登录行为(成功/失败),用于安全审计和账号锁定判断,保留 ≥ 90 天。
- **PasswordResetToken密码重置令牌**~~通过邮件找回密码时生成的一次性令牌~~ 已废弃,详见 `SmsOtpRecord`
- **SmsOtpRecord短信验证码记录**:找回密码和手机验证码登录时向用户手机号发送的 6 位一次性验证码。用 `scene` 字段区分场景(`password_reset` / `login`),有效期按场景不同(找回密码 10 分钟,验证码登录 5 分钟);找回密码验证通过后颁发 `sms_reset_token`(有效期 15 分钟),验证码登录验证通过后直接颁发 Session Token使用后立即作废。
- **PasswordHistory历史密码记录**:保存最近 3 次密码哈希,用于防止重复使用历史密码。
### 关键业务规则
1. **账号与员工强绑定**:每个登录账号 **必须**`org.Staff` 中的员工档案 1:1 绑定Tenant Admin 例外,可不绑定)。
2. **用户名规则统一为手机号**
- Tenant Admin**固定为该租户联系人的手机号**11 位数字),来源于 `public.tenants.contact_phone`,全局唯一,创建后不可更改
- 普通员工:**固定为员工手机号**11 位数字),同租户内唯一,创建后不可变更
3. **初始密码强制修改**:新账号及密码重置后,`is_initial_password = True`,首次登录必须修改密码,不可跳过。
4. **账号锁定机制**:同一账号连续密码错误 ≥ 5 次,状态置为 `locked`30 分钟后自动恢复Tenant Admin 可提前手动解锁。
5. **员工离职联动**:员工离职时,对应账号的 `status` 自动置为 `disabled`,不可登录,历史操作记录保留。
6. **不支持自助注册**:所有账号由有权限的管理角色创建,普通员工账号在新增员工时由系统自动生成。
---
## 二、实体关系
```
UserAccount
├── 1:1 ── org.Staff (实名绑定,普通员工必须)
├── 1:N ── LoginAttempt (登录审计记录)
├── 1:N ── SmsOtpRecord (短信验证码记录,找回密码用)
├── 1:N ── PasswordHistory (历史密码记录)
└── M:1 ── UserAccount.created_by (创建人自引用)
```
### Schema 归属
| 表 | Schema 位置 | 说明 |
|----|------------|------|
| `user_accounts` | 租户 Schema | 账号数据按租户隔离username 唯一性在 Schema 维度生效 |
| `login_attempts` | 租户 Schema | 审计记录属于租户,跨租户不可见 |
| `sms_otp_records` | 租户 Schema | 短信验证码记录,找回密码用 |
| `password_histories` | 租户 Schema | 历史密码与账号绑定 |
> **注意**Tenant Code 验证相关逻辑在 **Public Schema**`shared_apps`),使用 `django-tenants` 的 `TenantModel`,不在本文档范围内,详见 `DATA_MODEL.md` §四(公共 Schema
---
## 三、Schema 定义
### 3.1 `user_accounts` — 账号主表(租户 Schema
**表说明**:系统登录主体,每个租户内独立隔离,`username` 唯一性约束在 Schema 维度生效。
#### 字段定义
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | `BIGSERIAL` | `PRIMARY KEY` | — | 自增主键(审计场景下 BigInt 更直观;跨环境引用使用 UUID 扩展字段见下) |
| `username` | `VARCHAR(30)` | `NOT NULL` | — | 登录名普通员工为手机号11 位数字Tenant Admin 为自定义字符串;创建后不可更改 |
| `password` | `VARCHAR(128)` | `NOT NULL` | — | PBKDF2+SHA256 哈希存储,使用 Django `make_password` |
| `email` | `VARCHAR(254)` | `NULL` | `NULL` | 绑定邮箱;完全可选,在本系统无任何必须业务用途;若填写则在同租户内唯一 |
| `phone_enc` | `TEXT` | `NULL` | `NULL` | 手机号 AES-256-GCM 加密密文(`core.encryption`);普通员工必填 |
| `phone_hash` | `VARCHAR(64)` | `NULL` | `NULL` | 手机号 SHA-256 哈希;用于唯一性校验和查询;不可反推原文 |
| `staff_id` | `BIGINT` | `FK → org_staff.id`, `NULL`, `UNIQUE` | `NULL` | 员工档案绑定1:1普通员工必须有值Tenant Admin 可为空 |
| `is_tenant_admin` | `BOOLEAN` | `NOT NULL` | `FALSE` | 是否为该租户的超级管理账号;每个租户最多 1 个(应用层约束) |
| `status` | `VARCHAR(10)` | `NOT NULL`, `CHECK(status IN ('active','disabled','locked'))` | `'active'` | 账号状态;`locked` 为密码错误锁定30 分钟自动恢复 |
| `is_initial_password` | `BOOLEAN` | `NOT NULL` | `TRUE` | 初始密码标记True 时登录成功后强制跳转修改密码页,不可跳过 |
| `last_login` | `TIMESTAMPTZ` | `NULL` | `NULL` | 最后登录时间 |
| `locked_until` | `TIMESTAMPTZ` | `NULL` | `NULL` | 锁定到期时间;到期后应用层将 status 恢复 active |
| `created_at` | `TIMESTAMPTZ` | `NOT NULL` | `NOW()` | 账号创建时间 |
| `updated_at` | `TIMESTAMPTZ` | `NOT NULL` | `NOW()` | 最后更新时间(触发器维护) |
| `created_by` | `BIGINT` | `FK → user_accounts.id`, `NULL` | `NULL` | 创建人;普通员工由 Tenant Admin 创建Tenant Admin 由平台运营创建(可为 NULL |
#### 唯一性约束
```sql
UNIQUE (username) -- Schema 内唯一跨租户不冲突django-tenants 机制保障)
UNIQUE (email) -- 同租户内邮箱唯一(可为 NULLNULL 不参与唯一性校验)
UNIQUE (phone_hash) -- 同租户内手机号唯一(通过 hash 实现,不暴露原文)
UNIQUE (staff_id) -- 员工档案 1:1 绑定
```
#### 索引
```sql
CREATE UNIQUE INDEX uq_user_accounts_username ON user_accounts (username);
CREATE UNIQUE INDEX uq_user_accounts_email ON user_accounts (email) WHERE email IS NOT NULL;
CREATE UNIQUE INDEX uq_user_accounts_phone ON user_accounts (phone_hash) WHERE phone_hash IS NOT NULL;
CREATE INDEX idx_user_accounts_status ON user_accounts (status);
CREATE INDEX idx_user_accounts_staff ON user_accounts (staff_id);
```
#### Django Model 定义
```python
# apps/accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
class UserAccountManager(BaseUserManager):
def create_user(self, username, password, **extra_fields):
if not username:
raise ValueError("username 不能为空")
user = self.model(username=username, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
class UserAccount(AbstractBaseUser):
"""
租户级用户账号。
- 普通员工username 固定为员工手机号11 位数字)
- Tenant Adminusername 固定为该租户联系人手机号(来源于 public.tenants.contact_phone
注意:此表位于租户 Schemausername 唯一性约束在 Schema 维度生效。
"""
username = models.CharField(max_length=30)
email = models.EmailField(null=True, blank=True)
phone_enc = models.TextField(null=True, blank=True) # AES-256-GCM 加密密文
phone_hash = models.CharField(max_length=64, null=True, blank=True) # SHA-256 哈希索引
staff = models.OneToOneField(
'org.Staff',
null=True, blank=True,
on_delete=models.SET_NULL,
related_name='account',
)
is_tenant_admin = models.BooleanField(default=False)
status = models.CharField(
max_length=10,
choices=[('active', 'Active'), ('disabled', 'Disabled'), ('locked', 'Locked')],
default='active',
)
is_initial_password = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
locked_until = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(
'self',
null=True, blank=True,
on_delete=models.SET_NULL,
related_name='created_accounts',
)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
objects = UserAccountManager()
class Meta:
db_table = 'user_accounts'
# Schema 内唯一约束
constraints = [
models.UniqueConstraint(fields=['username'], name='uq_user_accounts_username'),
]
def __str__(self):
return f"{self.username} ({'admin' if self.is_tenant_admin else 'staff'})"
def is_locked(self) -> bool:
"""检查账号是否处于锁定状态(含自动过期判断)"""
from django.utils import timezone
if self.status == 'locked':
if self.locked_until and timezone.now() >= self.locked_until:
# 锁定已到期,应用层自动恢复(实际由 service 层处理)
return False
return True
return False
```
---
### 3.2 `login_attempts` — 登录尝试审计表(租户 Schema
**表说明**:记录每次登录请求(成功/失败),用于安全审计和锁定判断。数据保留 ≥ 90 天,不得提前清理。
#### 字段定义
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | `BIGSERIAL` | `NOT NULL` | — | 自增主键(与 attempted_at 组成复合 PK |
| `attempted_at` | `TIMESTAMPTZ` | `NOT NULL` | `NOW()` | 尝试时间(分区键,必须在复合主键中) |
| `username` | `VARCHAR(30)` | `NOT NULL` | — | 尝试登录的用户名(冗余存储,即使账号不存在也记录) |
| `ip_address` | `INET` | `NOT NULL` | — | 来源 IP 地址(支持 IPv4/IPv6 |
| `user_agent` | `TEXT` | `NULL` | `NULL` | 客户端 User-AgentElectron 版本信息) |
| `success` | `BOOLEAN` | `NOT NULL` | — | 是否登录成功 |
| `failure_reason` | `VARCHAR(30)` | `NULL` | `NULL` | 失败原因;可选值见下方枚举 |
> ⚠️ **分区说明**`login_attempts` 为高写入审计表,采用 `PARTITION BY RANGE (attempted_at)` 按月分区。主键为 `(id, attempted_at)` 复合主键(分区表规范:主键必须包含分区键)。
**DDL**
```sql
CREATE TABLE login_attempts (
id BIGSERIAL NOT NULL,
attempted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 分区键
username VARCHAR(30) NOT NULL,
ip_address INET NOT NULL,
user_agent TEXT,
success BOOLEAN NOT NULL,
failure_reason VARCHAR(30)
CHECK (failure_reason IS NULL OR failure_reason IN (
'wrong_password','wrong_captcha','account_locked',
'account_disabled','tenant_not_found',
'wrong_otp','otp_expired'
)),
PRIMARY KEY (id, attempted_at) -- 分区表主键必须包含分区键
) PARTITION BY RANGE (attempted_at);
CREATE TABLE login_attempts_2026_04 PARTITION OF login_attempts
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE login_attempts_2026_05 PARTITION OF login_attempts
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE login_attempts_default PARTITION OF login_attempts DEFAULT;
```
**`failure_reason` 枚举值**
| 值 | 含义 |
|----|------|
| `wrong_password` | 用户名或密码错误 |
| `wrong_captcha` | 行为验证码失败 |
| `account_locked` | 账号已锁定 |
| `account_disabled` | 账号已停用 |
| `tenant_not_found` | 租户不存在(理论上不应出现,防御性记录) |
| `wrong_otp` | 短信验证码错误 |
| `otp_expired` | 短信验证码已过期 |
#### 索引
```sql
CREATE INDEX idx_login_attempts_username ON login_attempts (username);
CREATE INDEX idx_login_attempts_ip ON login_attempts (ip_address);
CREATE INDEX idx_login_attempts_time ON login_attempts (attempted_at DESC);
-- 复合索引:按账号查询最近失败记录(锁定判断场景)
CREATE INDEX idx_login_attempts_fail_check ON login_attempts (username, success, attempted_at DESC);
```
#### Django Model 定义
```python
class LoginAttempt(models.Model):
"""
登录尝试审计记录。
- 合规保留周期:≥ 90 天
- 注意failure_reason 不得存储密码明文(含错误密码)
"""
FAILURE_REASONS = [
('wrong_password', '用户名或密码错误'),
('wrong_captcha', '行为验证码失败'),
('account_locked', '账号已锁定'),
('account_disabled', '账号已停用'),
('tenant_not_found', '租户不存在'),
('wrong_otp', '短信验证码错误'),
('otp_expired', '短信验证码已过期'),
]
username = models.CharField(max_length=30)
ip_address = models.GenericIPAddressField()
user_agent = models.TextField(null=True, blank=True)
success = models.BooleanField()
failure_reason = models.CharField(max_length=30, null=True, blank=True, choices=FAILURE_REASONS)
attempted_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'login_attempts'
indexes = [
models.Index(fields=['username']),
models.Index(fields=['ip_address']),
models.Index(fields=['-attempted_at']),
models.Index(fields=['username', 'success', '-attempted_at'],
name='idx_login_attempts_fail_check'),
]
def __str__(self):
return f"{self.username} @ {self.attempted_at} - {'OK' if self.success else self.failure_reason}"
```
---
### 3.3 `sms_otp_records` — 短信验证码记录表(租户 Schema
**表说明**:记录找回密码和手机验证码登录两个场景中,向用户手机号发送的短信验证码及其状态。用 `scene` 字段区分场景(`password_reset` / `login`),有效期和发送频率上限按场景独立控制。原 `password_reset_tokens`(邮件令牌)已废弃,由本表替代。
#### 字段定义
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | `BIGSERIAL` | `PRIMARY KEY` | — | 自增主键 |
| `user_id` | `BIGINT` | `FK → user_accounts.id`, `NOT NULL` | — | 关联账号 |
| `phone_hash` | `VARCHAR(64)` | `NOT NULL` | — | 收码手机号 SHA-256 哈希(与账号的 `phone_hash` 一致,冗余存储便于限频查询) |
| `scene` | `VARCHAR(20)` | `NOT NULL` | — | 使用场景:`password_reset`(找回密码)或 `login`(验证码登录) |
| `otp_hash` | `VARCHAR(128)` | `NOT NULL` | — | 验证码 PBKDF2 哈希(禁止明文存储,服务端校验时重新哈希比对) |
| `expires_at` | `TIMESTAMPTZ` | `NOT NULL` | — | 过期时间(`password_reset``created_at + 10 分钟``login``created_at + 5 分钟` |
| `is_used` | `BOOLEAN` | `NOT NULL` | `FALSE` | 是否已验证通过;通过后立即置 True防止重放攻击 |
| `verify_attempts` | `SMALLINT` | `NOT NULL` | `0` | 已尝试验证次数;≥ 5 次后本条记录强制作废(`is_used = TRUE` |
| `created_at` | `TIMESTAMPTZ` | `NOT NULL` | `NOW()` | 创建时间 |
#### 索引
```sql
CREATE INDEX idx_sms_otp_user ON sms_otp_records (user_id, created_at DESC);
CREATE INDEX idx_sms_otp_phone ON sms_otp_records (phone_hash, created_at DESC);
CREATE INDEX idx_sms_otp_active ON sms_otp_records (user_id, expires_at) WHERE is_used = FALSE;
```
#### Django Model 定义
```python
class SmsOtpRecord(models.Model):
"""
短信验证码记录(找回密码 + 验证码登录共用)。
安全约束:
- OTP 明文仅在发送短信时生成,不持久化;仅存 PBKDF2 哈希
- 单条记录验证次数 ≥ 5 次后强制作废is_used=True
- 同一手机号 1 小时内按 scene 独立限频password_reset ≤ 5 次login ≤ 10 次)
"""
SCENE_CHOICES = [
('password_reset', '找回密码'),
('login', '验证码登录'),
]
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name='sms_otp_records')
phone_hash = models.CharField(max_length=64) # SHA-256 哈希,不暴露手机号原文
scene = models.CharField(max_length=20, choices=SCENE_CHOICES) # 区分场景
otp_hash = models.CharField(max_length=128) # PBKDF2 哈希,禁止明文存储
expires_at = models.DateTimeField() # password_reset: +10min; login: +5min
is_used = models.BooleanField(default=False)
verify_attempts = models.SmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'sms_otp_records'
indexes = [
models.Index(fields=['user', '-created_at']),
models.Index(fields=['phone_hash', '-created_at']),
]
def is_valid(self) -> bool:
from django.utils import timezone
return not self.is_used and self.verify_attempts < 5 and timezone.now() < self.expires_at
def mark_used(self):
self.is_used = True
self.save(update_fields=['is_used'])
```
---
### 3.4 `password_histories` — 历史密码记录表(租户 Schema
**表说明**:保存账号最近 3 次密码哈希,用于防止重复使用历史密码(含初始密码)。
#### 字段定义
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | `BIGSERIAL` | `PRIMARY KEY` | — | 自增主键 |
| `user_id` | `BIGINT` | `FK → user_accounts.id`, `NOT NULL` | — | 关联账号 |
| `password_hash` | `VARCHAR(128)` | `NOT NULL` | — | PBKDF2+SHA256 哈希值 |
| `created_at` | `TIMESTAMPTZ` | `NOT NULL` | `NOW()` | 记录时间(密码修改时间) |
#### 索引
```sql
CREATE INDEX idx_password_histories_user ON password_histories (user_id, created_at DESC);
```
#### Django Model 定义
```python
class PasswordHistory(models.Model):
"""
历史密码记录,每个账号保留最近 N 条(默认 3 条)。
新密码不得与最近 3 条历史记录相同(含系统初始密码 Fonrey@2025
"""
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name='password_histories')
password_hash = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'password_histories'
ordering = ['-created_at']
indexes = [
models.Index(fields=['user', '-created_at']),
]
```
---
## 四、Redis 缓存结构(辅助状态,非持久化)
以下 Redis Key 不存入 PostgreSQL属于运行时状态需与数据库状态保持最终一致
| Key 格式 | 类型 | TTL | 说明 |
|----------|------|-----|------|
| `captcha_token:{uuid}` | STRING | 3 分钟 | 滑块验证会话 Token验证通过后生成 `captcha_pass_token` |
| `captcha_pass:{uuid}` | STRING | 3 分钟 | 一次性通过凭证;登录提交时校验后立即删除 |
| `login_fail:{tenant_id}:{username}` | STRING计数 | 30 分钟 | 连续密码错误次数;≥ 5 触发锁定TTL 30 分钟自动清零 |
| `sms_limit:password_reset:{phone}` | STRING计数 | 1 小时 | 找回密码场景:同一手机号短信发送次数;上限 **5 次**/小时 |
| `sms_limit:login:{phone}` | STRING计数 | 1 小时 | 验证码登录场景:同一手机号短信发送次数;上限 **10 次**/小时 |
| `sms_reset_token:{token}` | STRINGuser_id | 15 分钟 | 验证码通过后颁发的一次性重置凭证;用于步骤三提交新密码;使用后立即删除 |
| `tenant_verify_ip:{ip}` | STRING计数 | 1 分钟 | Tenant 验证接口 IP 限流;≥ 10 次拒绝请求 |
> **一致性说明**:账号锁定状态由 `user_accounts.status` 持久化Redis 仅做计数触发器。当 Redis 数据丢失(如 Redis 重启),应用层通过 `locked_until` 字段恢复锁定状态判断。
---
## 五、账号创建流程与状态机
### 5.1 账号状态机
```
┌─────────────────────────────────────┐
│ 账号生命周期状态机 │
└─────────────────────────────────────┘
[创建账号]
│ is_initial_password=True, status=active
[初始密码态] ─── 使用初始密码登录成功 ───► [强制修改密码页]
│ │ 修改成功
│ ▼
│ [正常使用态]
│ status=active
│ is_initial_password=False
├── 密码错误 ≥ 5 次 ──────────────────► [锁定态]
│ status=locked
│ locked_until = now+30min
│ │
│ ┌───────────────┤
│ │ 30分钟到期 │ 管理员手动解锁
│ ▼ ▼
│ [正常使用态] ◄─── [管理员操作]
└── 员工离职 / 管理员停用 ──► [停用态]
status=disabled
员工复职/管理员恢复
[正常使用态]
```
### 5.2 账号创建触发时机
| 账号类型 | 触发时机 | 创建者 | username 规则 | 初始密码 |
|----------|----------|--------|--------------|---------|
| Tenant Admin | 平台运营在系统后台开通租户时 | 平台运营 | **固定为该租户联系人手机号**11 位数字,来源于 `public.tenants.contact_phone` | **系统统一固定初始密码**(如 `Fonrey@2025`,由平台部署配置设定) |
| 普通员工 | Tenant Admin 在「新增员工」时系统自动生成 | 系统Tenant Admin 触发) | 固定为员工手机号11 位) | 系统统一初始密码(部署配置) |
---
## 六、关联约束与数据完整性
### 6.1 与 `org.Staff` 的关联规则
```
org_staff (1) ──── (0..1) user_accounts
```
- 普通员工账号:`staff_id` **必须**有值,且在 `org.Staff` 中对应记录的 `status` 为 active
- Tenant Admin`staff_id` **可为空**(平台运营账号可不绑定实名档案)
- 员工离职时(`org.Staff.status``resigned`),触发账号 `status``disabled`(由 `org` App Service 层调用 `accounts` 服务执行,避免循环依赖)
- 账号删除:**不允许物理删除**,仅允许 `status=disabled`,审计记录永久保留
### 6.2 跨 App 依赖方向
```
accounts ──► org (单向依赖accounts.UserAccount.staff_id → org.Staff)
org ──► accounts (反向触发,通过 Service 层调用,不通过 FK 反查)
```
> **设计原则**:避免循环 FK 依赖,跨 App 的状态联动通过 Service 层的显式调用完成,不在 Model 层建立反向 FK。
---
## 七、迁移说明Django Migrations
### 初始迁移顺序
```
0001_initial_user_accounts.py # UserAccount 表(依赖 org.Staff 表已存在)
0002_login_attempts.py # LoginAttempt 表
0003_sms_otp_records.py # SmsOtpRecord 表(替代原 password_reset_tokens
0004_password_histories.py # PasswordHistory 表
```
### 注意事项
- `accounts` App 的迁移依赖 `org` App`org.Staff` 表须先创建),需在 `INSTALLED_APPS` 中确保 `org``accounts` 之前
- 所有迁移均在**租户 Schema** 下执行(`django-tenants``migrate_schemas` 命令)
- 不得为 `email` 字段设置 `NOT NULL` 约束(允许为空,是否绑定邮箱属于用户选择)
---
## 八、设计决策说明ADR
| 决策 | 选择 | 理由 |
|------|------|------|
| 主键类型 | `BIGSERIAL` (BigInt) | 登录审计场景下 BigInt 主键更简洁高效;跨环境引用场景少,无需 UUID 的随机性 |
| `phone` 字段拆分为 `phone_enc` + `phone_hash` | 是 | 与 `org.Staff` 保持一致;`phone_enc` 保存原文用于展示,`phone_hash` 用于唯一性校验和快速查询,避免加密字段全表扫描 |
| 不扩展 Django `User` | 使用 `AbstractBaseUser` | 避免 `django.contrib.auth.User` 的全局唯一性限制(多租户下同一 username 在不同租户是允许的) |
| `LoginAttempt` 不设外键到 `UserAccount` | 是(冗余存储 username | 即使账号被删除(停用),审计记录仍需保留;使用 username 字符串字段保证审计完整性 |
| 找回密码机制 | 短信验证码(`SmsOtpRecord` | 经纪人普遍无邮箱手机号是本系统唯一已知联系方式短信核验更直接废弃邮件找回路径减少外部依赖SMTP |
| 历史密码单独建表 | `PasswordHistory` 独立表 | 而非在 `UserAccount` 中存 JSON 数组,便于查询和维护,支持未来扩展保留次数 |
| 锁定到期时间持久化 | `locked_until` 字段 | Redis 可能重启丢失数据,持久化 `locked_until` 保证 Redis 故障时锁定状态不丢失 |

View File

@@ -0,0 +1,348 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 组织人事数据模型DATA_MODEL_ORG
> **所属系统**: Fonrey 房产经纪管理系统
> **版本**: v1.0
> **日期**: 2026-04-24
> **关联模块**: `apps/org/` — 组织架构、员工档案、人事异动、账号体系
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、领域概览Domain Overview
### 核心概念
- **OrgUnit组织节点**:公司组织树的节点,类型涵盖事业部 / 大区 / 区域 / 片区 / 门店 / 店组 / 职能。所有业务数据(房源、客源)最终归属到门店或店组级节点。
- **Staff员工**:系统的核心操作人员,与 Django `auth_user` 绑定登录账号,与 `org_units` 绑定岗位归属。员工的组织归属直接影响数据可见范围。
- **StaffTransferLog人事异动记录**:记录员工从入职到离职的全生命周期状态变化。每次异动(入职/调动/离职/复职)自动生成一条不可删除的日志。
- **StaffAccount账号信息**:员工的多平台登录账号体系,包括 Fonrey 主账号 / 58安居客 / 中国网络经纪人等。
### 关键业务规则
1. **组织层级约束**:店组级部门 **必须** 挂在门店下;经纪人/店管的所属部门 **只能** 是门店或店组。
2. **经纪人定义**:职务类别为「置业顾问」的员工即为经纪人,受业务规则特殊约束。
3. **人员异动强制日志**:入职、调动、离职、复职等操作均自动生成 `staff_transfer_logs` 记录,不可删除。
4. **账号与员工联动**:员工离职后,对应的 `auth_user.is_active` 设为 `False`,不可登录;复职后由管理员手动恢复。
5. **手机号敏感字段**:员工手机号 AES-256-GCM 加密存储SHA-256 哈希用于唯一性校验,通讯录展示脱敏格式。
6. **数据归属继承**:员工调动时,名下房源/客源默认跟随员工到新部门;离职时可选择转移给指定账号。
---
## 二、实体关系
```
OrgUnit (树形自引用,物化路径)
├── 1:N ── Staff (员工归属一个部门)
│ │
│ ├── 1:1 ── auth_user (Django 登录账号)
│ ├── 1:N ── StaffTransferLog (人事异动记录)
│ ├── 1:N ── StaffRewardPunish (奖惩记录)
│ ├── 1:N ── StaffAccount (第三方账号绑定)
│ └── 1:N ── StaffRemark (管理员备注)
└── 1:1 ── OrgUnit.parent_id (自引用)
```
---
## 三、Schema 定义
### 3.1 org_units — 组织节点表
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| name | VARCHAR(100) | NOT NULL | 部门/组织名称 |
| type | VARCHAR(20) | NOT NULL, CHECK | 枚举:`company` / `division`(事业部) / `region`(大区) / `area`(区域) / `district`(片区) / `store`(门店) / `group`(店组) / `functional`(职能) |
| parent_id | UUID | FK→self, RESTRICT | 父节点,根节点为 NULL |
| path | TEXT | NOT NULL | 物化路径:`/root_id/.../self_id/`,用于子树查询 |
| depth | SMALLINT | NOT NULL DEFAULT 0 | 节点深度(根=0最大支持 8 层 |
| sort_order | INTEGER | NOT NULL DEFAULT 0 | 同级排序 |
| attribute | VARCHAR(10) | | 直营/加盟,枚举:`direct` / `franchise` |
| address_city | VARCHAR(50) | | 部门所在城市 |
| address_district | VARCHAR(50) | | 部门所在县区 |
| address_detail | VARCHAR(200) | | 详细地址 |
| latitude | NUMERIC(10,7) | | 坐标(部门定位针) |
| longitude | NUMERIC(10,7) | | 坐标 |
| manager_id | UUID | FK→staff.id, SET NULL | 部门负责人循环依赖Application 层维护) |
| established_at | DATE | | 成立时间 |
| phone | VARCHAR(30) | | 部门联系电话 |
| ext_start | INTEGER | | 分机号范围:起始 |
| ext_end | INTEGER | | 分机号范围:结束 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE = 已关闭部门,仍可在筛选中显示 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除非NULL=已软删除 |
**关键索引**
```sql
CREATE INDEX idx_org_units_parent ON org_units(parent_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_org_units_path_prefix ON org_units(path text_pattern_ops); -- 路径前缀查询
CREATE INDEX idx_org_units_type ON org_units(type) WHERE deleted_at IS NULL AND is_active = TRUE;
```
**业务注意**
- 查询某部门及所有下级:`WHERE path LIKE '/root_id/{target_id}/%'`
- 店组(`group`)的 `parent_id` 必须指向一个 `store` 节点,新增前需校验
---
### 3.2 staff — 员工表
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| org_unit_id | UUID | NOT NULL, FK→org_units | 当前所属组织节点(门店或店组) |
| user_id | INTEGER | UNIQUE, FK→auth_user | Django auth 登录账号 ID |
| name | VARCHAR(50) | NOT NULL | 真实姓名 |
| nickname | VARCHAR(50) | | 昵称(通讯录/显示名) |
| employee_no | VARCHAR(30) | UNIQUE | 员工工号,系统自动生成或手动录入 |
| role | VARCHAR(30) | NOT NULL, CHECK | 系统角色枚举:`agent`(经纪人) / `store_manager` / `area_manager` / `admin` / `operator` / `system` |
| job_title | VARCHAR(100) | | 职务名称,如「高级业务员」 |
| job_category | VARCHAR(50) | | 职务类别,如「置业顾问」(经纪人判定字段) |
| job_level | SMALLINT | | 职级(数字) |
| supervisor_id | UUID | FK→staff.id, SET NULL | 直属上级 |
| status | VARCHAR(20) | NOT NULL DEFAULT 'active' | `active`(在职) / `probation`(试用) / `resigned`(离职) / `frozen`(冻结) |
| phone_enc | BYTEA | | AES-256-GCM 加密手机号 |
| phone_hash | VARCHAR(64) | | SHA-256 哈希,用于唯一性索引 |
| phone_hide | BOOLEAN | NOT NULL DEFAULT FALSE | 通讯录是否隐藏手机号 |
| email | VARCHAR(255) | | 邮箱 |
| extension | VARCHAR(20) | | 分机号 |
| avatar_key | TEXT | | R2/S3 头像路径 |
| is_active | BOOLEAN | NOT NULL DEFAULT TRUE | FALSE 时账号不可登录(联动 auth_user.is_active |
| is_system_admin | BOOLEAN | NOT NULL DEFAULT FALSE | 是否为系统管理员(影响权限上限) |
| first_joined_at | DATE | | 首次入职日期(计算工龄起点) |
| rejoined_at | DATE | | 最近复职日期 |
| resigned_at | DATE | | 最近离职日期 |
| joined_count | SMALLINT | NOT NULL DEFAULT 1 | 累计入职次数 |
| industry_exp_years | SMALLINT | | 行业经验(年) |
| mentor_id | UUID | FK→staff.id, SET NULL | 师傅(带教员工) |
| business_type | VARCHAR(50) | | 业务类型 |
| bank_name | VARCHAR(100) | | 银行名称 |
| bank_account | VARCHAR(50) | | 银行卡号(内部财务用) |
| partner_no | VARCHAR(50) | | 联号 |
| recruit_by_id | UUID | FK→staff.id, SET NULL | 招聘人 |
| recruit_source | VARCHAR(50) | | 招聘来源 |
| referrer_id | UUID | FK→staff.id, SET NULL | 转介人 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳;离职员工不可硬删除,保留档案 |
**关键索引**
```sql
CREATE UNIQUE INDEX idx_staff_employee_no ON staff(employee_no) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_staff_phone_hash ON staff(phone_hash) WHERE deleted_at IS NULL;
CREATE INDEX idx_staff_org_unit ON staff(org_unit_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_staff_supervisor ON staff(supervisor_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_staff_status ON staff(status) WHERE deleted_at IS NULL;
```
**业务注意**
- `is_active = FALSE` 时对应 `auth_user.is_active` 同步设为 False通过 Django signal 实现
- 离职员工(`status = 'resigned'`)不可硬删除,保留档案以便房源/客源历史关联查询
- 经纪人判定:`job_category = '置业顾问'`,部分权限逻辑基于此字段
---
### 3.3 staff_personal_info — 员工个人信息扩展表
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| staff_id | UUID | UNIQUE, NOT NULL, FK→staff | 1:1 关系 |
| gender | VARCHAR(10) | | `male` / `female` / `unknown` |
| id_type | VARCHAR(20) | | 证件类型:`id_card`(身份证) / `passport` / `other` |
| id_number_enc | BYTEA | | 证件号码AES 加密) |
| id_number_hash | VARCHAR(64) | | SHA-256 哈希(实名认证比对用) |
| id_verified | BOOLEAN | NOT NULL DEFAULT FALSE | 是否实名认证通过 |
| id_verified_at | TIMESTAMPTZ | | 认证时间 |
| birthdate | DATE | | 出生日期 |
| native_place | VARCHAR(100) | | 籍贯 |
| domicile_type | VARCHAR(20) | | 户籍性质 |
| marital_status | VARCHAR(20) | | 婚姻状况 |
| political_status | VARCHAR(20) | | 政治面貌 |
| has_children | BOOLEAN | | 有无子女 |
| education_level | VARCHAR(20) | | 最高学历 |
| ethnicity | VARCHAR(20) | | 民族 |
| domicile_address | VARCHAR(200) | | 户口所在地 |
| residence_address | VARCHAR(200) | | 居住地址 |
| work_start_date | DATE | | 参加工作时间 |
| emergency_contact | VARCHAR(50) | | 紧急联系人 |
| emergency_phone_enc | BYTEA | | 紧急联系人电话(加密) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| updated_by | UUID | FK→staff.id, SET NULL | 最后修改人(操作员工) |
---
### 3.4 staff_transfer_logs — 人事异动记录
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| staff_id | UUID | NOT NULL, FK→staff, RESTRICT | 被操作员工 |
| transfer_type | VARCHAR(30) | NOT NULL, CHECK | 枚举见下方 |
| old_value | JSONB | | 变动前的值快照,格式:`{"field": "org_unit_id", "value": "...", "label": "门店A"}` |
| new_value | JSONB | | 变动后的值快照 |
| transfer_date | DATE | NOT NULL | 异动生效日期(可以是过去日期) |
| remarks | VARCHAR(50) | | 备注最多50字 |
| operator_id | UUID | NOT NULL, FK→staff, RESTRICT | 操作人(必填,异动审计必须记录) |
| operated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 系统操作时间 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动,等同 operated_at |
| ⚠️ 无 deleted_at | — | — | 异动记录**不可删除** |
**transfer_type 枚举**
```
onboard = 入职
transfer = 调动(含平调/晋升/降职)
resign = 离职
rejoin = 复职
supervisor_change = 上级变动
role_change = 角色变更
freeze = 账号冻结
unfreeze = 账号恢复
```
**关键索引**
```sql
CREATE INDEX idx_transfer_logs_staff ON staff_transfer_logs(staff_id, transfer_date DESC);
CREATE INDEX idx_transfer_logs_type ON staff_transfer_logs(transfer_type, operated_at DESC);
CREATE INDEX idx_transfer_logs_operator ON staff_transfer_logs(operator_id);
```
---
### 3.5 staff_reward_punish — 奖惩记录
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| staff_id | UUID | NOT NULL, FK→staff | 被奖惩员工 |
| rp_date | DATE | NOT NULL | 奖惩日期 |
| category | VARCHAR(50) | NOT NULL | 奖惩类别(枚举由 lookup 表维护) |
| name | VARCHAR(100) | NOT NULL | 奖惩名称(与类别联动) |
| remarks | TEXT | | 备注 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
| created_by | UUID | FK→staff.id, SET NULL | 录入人(操作员工) |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录最后更新时间(系统自动) |
| deleted_at | TIMESTAMPTZ | | 软删除时间戳NULL=未删除非NULL=已软删除 |
---
### 3.6 staff_work_experiences / staff_educations / staff_trainings / staff_family_members
这四张表结构类似,均为 1:N 附属于 `staff`,存储员工档案中「工作经历」「教育经历」「培训经历」「家庭主要成员」信息。详见下方汇总:
| 表名 | 关键字段 |
|------|---------|
| `staff_work_experiences` | staff_id, company, job_title, start_date, end_date, reason, reference_name, reference_phone |
| `staff_educations` | staff_id, stage, school, major, start_date, end_date, enrollment_status, degree |
| `staff_trainings` | staff_id, training_name, training_date, certificate |
| `staff_family_members` | staff_id, relation(称谓), name, birthdate, occupation, work_unit, phone_enc |
---
### 3.7 staff_accounts — 员工第三方账号绑定
| 字段 | 类型 | 约束 | 业务说明 |
|------|------|------|----------|
| id | UUID | PK | 主键(系统生成,业务无关) |
| staff_id | UUID | NOT NULL, FK→staff | 所属员工(必填,证件信息随员工关联) |
| platform | VARCHAR(30) | NOT NULL, CHECK | `fonrey`(主账号) / `58anjuke` / `cnreic`(中国网络经纪人) / `wechat_mp`(微信公众号) |
| account_no | VARCHAR(100) | | 账号/手机号 |
| is_real_name_match | BOOLEAN | | 实名信息一致性(中国网络经纪人专用) |
| is_bound | BOOLEAN | NOT NULL DEFAULT FALSE | 是否已绑定 |
| bound_at | TIMESTAMPTZ | | 绑定时间 |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录创建时间(系统自动) |
---
## 四、枚举常量
### Staff.role系统角色
| 值 | 含义 | 数据可见范围默认 |
|----|------|----------------|
| `agent` | 一线经纪人 | 本人/本组 |
| `store_manager` | 店长 | 本门店 |
| `area_manager` | 区域经理 | 本区域 |
| `admin` | 系统管理员 | 全公司 |
| `operator` | 运营/行政 | 全公司(只读为主) |
| `system` | 系统账号(定时任务用) | — |
### Staff.status员工状态
```
active = 正式在职
probation = 试用期
resigned = 已离职(不可删除,保留档案)
frozen = 账号冻结(在职但无法登录)
```
### OrgUnit.type组织类型
```
company = 公司根节点(每个租户唯一)
division = 事业部
region = 大区
area = 区域
district = 片区
store = 门店(经纪人最小归属单位)
group = 店组(门店下的业务小组)
functional = 职能部门(行政/财务等)
```
---
## 五、查询模式
### 5.1 查询某部门及所有下级的在职员工
```sql
-- 利用物化路径高效查询子树
SELECT s.*
FROM staff s
JOIN org_units ou ON s.org_unit_id = ou.id
WHERE ou.path LIKE '/root_id/{target_org_unit_id}/%'
OR ou.id = '{target_org_unit_id}'
AND s.deleted_at IS NULL
AND s.status != 'resigned';
```
### 5.2 查询员工完整异动历史
```sql
SELECT stl.*,
s.name as operator_name,
ou.name as operator_org
FROM staff_transfer_logs stl
JOIN staff s ON stl.operator_id = s.id
JOIN org_units ou ON s.org_unit_id = ou.id
WHERE stl.staff_id = :staff_id
ORDER BY stl.transfer_date DESC, stl.operated_at DESC;
```
### 5.3 获取员工的直接上下级链
```sql
-- 直属上级
SELECT supervisor.* FROM staff
JOIN staff supervisor ON staff.supervisor_id = supervisor.id
WHERE staff.id = :staff_id AND supervisor.deleted_at IS NULL;
```
---
## 六、禁止操作
-**严禁硬删除 staff 记录**:离职员工需通过 `deleted_at + status = 'resigned'` 软删除,历史房源/跟进日志依赖 `staff.id` 外键
-**严禁删除 staff_transfer_logs**:异动记录为不可变审计日志
-**严禁直接修改 staff.user_id**:账号绑定关系变更需走专门的账号管理流程
-**严禁绕过组织层级约束**:店组不在门店下的数据操作需在 Application 层校验并拒绝
-**严禁明文存储员工手机号和证件号**:必须走 `EncryptedPhoneField` / `EncryptedIDField`

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 系统配置模块数据模型DATA_MODEL_SETTING
> **定位**:本文件是 `apps/setting/` 模块的数据模型权威来源。
> **版本**v1.0 | **日期**2026-04-27
> **关联 PRD**`PRD/系统配置/系统配置模块PRD.md`
> **关联文档**`DATA_MODEL/ENUMS.md`、`DATA_MODEL/DATA_MODEL.md`
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、模块定位与架构边界
### 1.1 系统配置模块职责
系统配置模块(`apps/setting/`)负责管理三类性质不同的配置数据:
| 类型 | 说明 | 表 | Schema |
| -------------- | ---------------- | --------------------------------------------- | -------------- |
| **A. 固定系统枚举** | 平台级固定值域,所有租户共享 | `enum_labels` | Publicshared |
| **B. 可配置枚举** | 各租户选项不同,管理员可增删排序 | `lookup_groups` + `lookup_items` | Tenant |
| **C. 行为规则与开关** | 标量配置开关 + 字段必填规则 | `tenant_settings` + `field_requirement_rules` | Tenant |
> **重要区分**
> - 类型 A (`enum_labels`) 已在 `DATA_MODEL/ENUMS.md` 完整定义,**本文件不重复**
> - 类型 B/C 均存于 **租户 Schema**,由租户管理员通过界面维护
> - `apps/setting/` 是 `tenant_apps`**非** `shared_apps`
### 1.2 依赖关系
```
apps/setting/
├── 依赖 → core.cacheRedis统一租户前缀
├── 依赖 → org.Staffcreated_by / updated_by FK
└── 被依赖 ← apps/property读取字段规则、枚举选项
← apps/client读取查重范围、枚举选项
```
---
## 二、可配置枚举表(类型 B
### 2.1 `lookup_groups`(枚举分组)
每个分组代表一类可配置枚举(如「客源来源」「跟进目的」),由研发预置,租户管理员**不可新增或删除分组**,仅可管理分组内的选项。
```sql
-- ============================================================
-- 可配置枚举分组Tenant Schema
-- 研发预置,租户不可修改分组本身
-- ============================================================
CREATE TABLE lookup_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
module VARCHAR(50) NOT NULL, -- 'client' | 'property'
key VARCHAR(100) NOT NULL, -- 'source' | 'follow_purpose'
label_zh VARCHAR(50) NOT NULL, -- 界面显示名称,如「客源来源」
description TEXT, -- 说明文案(前端 tooltip 使用)
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(数值越小越靠前)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
UNIQUE (module, key)
);
```
**MVP 预置分组(种子数据)**
| module | key | label_zh | description |
|--------|-----|----------|-------------|
| `client` | `source` | 客源来源 | 客源从何处获取,用于来源渠道分析 |
| `client` | `follow_purpose` | 跟进目的 | 客源跟进时选择的目的分类 |
| `property` | `source` | 房源来源 | 房源从何处获取 |
---
### 2.2 `lookup_items`(枚举选项)
```sql
-- ============================================================
-- 可配置枚举选项Tenant Schema
-- 租户管理员可增删排序is_system=True 的预制项不可物理删除
-- ============================================================
CREATE TABLE lookup_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
group_id UUID NOT NULL REFERENCES lookup_groups(id) ON DELETE CASCADE, -- 所属枚举分组(关联 lookup_groups
value VARCHAR(100) NOT NULL, -- 存储值,英文 snake_case如 'door_to_door'
label_zh VARCHAR(50) NOT NULL, -- 显示文本(如「上门」)
is_system BOOLEAN NOT NULL DEFAULT FALSE, -- True=系统预制,不可删除,仅可停用
is_active BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用FALSE 后前端下拉不展示,历史数据保留并追加「(已停用)」后缀
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(数值越小越靠前)
created_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 系统预制时为 NULL
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
UNIQUE (group_id, value)
);
CREATE INDEX idx_lookup_items_group_active
ON lookup_items(group_id, is_active, sort_order);
```
**关键约束**
- `is_system = TRUE` 的记录不允许物理删除Service 层强制拦截)
- `is_active = FALSE` 后:前端录入下拉不展示;历史已选该值的记录保留原值,展示时追加「(已停用)」后缀
- `value` 一旦写入不允许修改(历史数据依赖);如需改名,停用旧项、新增新项
---
### 2.3 MVP 预置种子数据(`is_system = TRUE`
以下选项在租户初始化时自动写入:
#### 客源来源(`client.source`
| value | label_zh | sort_order |
|-------|----------|------------|
| `store_reception` | 门店接待 | 1 |
| `old_client_referral` | 老客户转介绍 | 2 |
| `stationed_dispatch` | 驻守派单 | 3 |
| `walk_in` | 上门 | 4 |
| `online_58` | 网络-58同城 | 5 |
| `online_anjuke` | 网络-安居客 | 6 |
| `wechat` | 微信 | 7 |
| `friend_referral` | 朋友介绍 | 8 |
#### 跟进目的(`client.follow_purpose`
| value | label_zh | sort_order |
|-------|----------|------------|
| `callback` | 回拨 | 1 |
| `push_property` | 推房 | 2 |
| `showing` | 带看 | 3 |
| `maintain` | 维护 | 4 |
| `other` | 其他 | 5 |
#### 房源来源(`property.source`
| value | label_zh | sort_order |
|-------|----------|------------|
| `proactive_development` | 主动开发 | 1 |
| `owner_walk_in` | 业主上门 | 2 |
| `old_client_referral` | 老客户转介绍 | 3 |
| `online_inquiry` | 网络来电 | 4 |
---
## 三、行为规则与开关(类型 C
### 3.1 `tenant_settings`(标量配置键值表)
存储开关bool、阈值int、单选枚举string等标量类型配置项。
```sql
-- ============================================================
-- 租户标量配置键值对Tenant Schema
-- ============================================================
CREATE TABLE tenant_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
category VARCHAR(50) NOT NULL, -- 配置分类:'client' | 'property' | 'showroom'
key VARCHAR(100) NOT NULL, -- 配置 key如 'duplicate_check_scope'
value JSONB NOT NULL, -- 存储任意类型bool/int/str如 {"v": "self"}
value_type VARCHAR(20) NOT NULL -- 'bool' | 'int' | 'string' | 'enum'(用于前端渲染控件)
CHECK (value_type IN ('bool', 'int', 'string', 'enum')),
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 最后修改人(关联 staff 表)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
UNIQUE (category, key)
);
CREATE INDEX idx_tenant_settings_category ON tenant_settings(category);
```
**存储格式约定**
- `bool``{"v": true}``{"v": false}`
- `int``{"v": 30}`
- `string``{"v": "some_value"}`
- `enum``{"v": "self", "choices": ["self", "dept", "company"]}``choices` 由代码硬编码,不存 DB
**MVP 阶段预置 key**
| category | key | value_type | 默认值 | 说明 |
|----------|-----|-----------|--------|------|
| `client` | `duplicate_check_scope` | `enum` | `{"v": "self"}` | 新增私客查重范围:`self`(本人)/ `dept`(本部门)/ `company`(全公司) |
> 未来 P1 阶段可按需追加 key无需修改表结构。
---
### 3.2 `field_requirement_rules`(字段必填规则表)
按「模块 × 房源用途 × 交易状态 × 字段」四元组确定一条规则,控制录入界面的字段显示状态。
```sql
-- ============================================================
-- 字段必填/隐藏规则Tenant Schema
-- MVP 仅支持 module='property'
-- ============================================================
CREATE TABLE field_requirement_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
module VARCHAR(20) NOT NULL, -- 'property' | 'client'MVP 只用 'property'
entity_type VARCHAR(50) NOT NULL, -- 与 property.property_type CHECK 约束值对齐
-- 'residential'|'villa'|'commercial_residential'|'shop'|'office'|'other'
trade_status VARCHAR(20) NOT NULL, -- 交易大类:'sale'|'rent'|'sale_rent'|'*'(全部)
CHECK (trade_status IN ('sale', 'rent', 'sale_rent', '*')),
field_key VARCHAR(50) NOT NULL, -- 字段 key如 'orientation'|'decoration'|'floor'
requirement VARCHAR(10) NOT NULL -- 规则值
CHECK (requirement IN ('required', 'optional', 'hidden')),
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 最后修改人(关联 staff 表)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
UNIQUE (module, entity_type, trade_status, field_key)
);
CREATE INDEX idx_field_req_lookup
ON field_requirement_rules(module, entity_type, trade_status);
```
**与 `property.property_type` 对齐说明**
`entity_type` 的值域与 `property.property_type` 的 CHECK 约束完全一致:
| entity_type | property_type label_zh |
|-------------|----------------------|
| `residential` | 住宅 |
| `villa` | 别墅 |
| `commercial_residential` | 商住 |
| `shop` | 商铺 |
| `office` | 写字楼 |
| `other` | 其他 |
**`trade_status``property.status` 的映射关系**
| trade_status | 对应 property.status 值 |
|--------------|------------------------|
| `sale` | `for_sale` |
| `rent` | `for_rent` |
| `sale_rent` | `for_sale_rent` |
| `*` | 所有状态通用规则fallback |
> **重要**`trade_status` 是录入场景的交易意图分类,不是 `property.status` 的完整枚举。规则匹配逻辑:先查精确匹配(`entity_type + trade_status`),不存在则查 `*` 通配规则。
**MVP 初始规则(研发预置,管理员可覆盖)**
| module | entity_type | trade_status | field_key | requirement |
|--------|-------------|--------------|-----------|-------------|
| `property` | `residential` | `sale` | `orientation` | `optional` |
| `property` | `residential` | `sale` | `decoration` | `optional` |
| `property` | `residential` | `sale` | `floor` | `optional` |
| `property` | `residential` | `sale` | `building_area` | `required` |
| `property` | `residential` | `sale` | `inner_area` | `optional` |
| `property` | `residential` | `sale` | `room_layout` | `required` |
| `property` | `residential` | `rent` | `decoration` | `optional` |
| `property` | `residential` | `rent` | `floor` | `optional` |
| `property` | `residential` | `rent` | `building_area` | `required` |
| `property` | `residential` | `rent` | `room_layout` | `required` |
**MVP 可配置字段范围(对应 PRD AC-4**
| field_key | 说明 | 字段类型 |
|-----------|------|---------|
| `orientation` | 朝向(`property.orientation` 枚举) | 枚举 |
| `decoration` | 装修情况(`property.decoration` 枚举) | 枚举 |
| `floor` | 所在楼层/总楼层 | 数值 |
| `building_area` | 建筑面积(㎡) | 数值 |
| `inner_area` | 套内面积(㎡) | 数值 |
| `room_layout` | 房型(室/厅/卫) | 数值组 |
| `ownership_years` | 产权年限(年) | 数值 |
| `parking_count` | 车位数 | 数值 |
---
## 四、服务层设计
所有业务模块**禁止直接查询配置表**,必须通过统一服务层读取:
```python
# apps/setting/services/tenant_settings_service.py
class TenantSettingsService:
"""
系统配置统一读取服务。
所有配置均经 Redis 缓存TTL 5min写入时主动 invalidate。
Redis Key 规范:{tenant_schema}:setting:{type}:{key}
"""
def get(self, category: str, key: str, default=None):
"""
读取标量配置tenant_settings 表)
Cache Key: {tenant_schema}:setting:kv:{category}.{key}
返回 JSONB value 字段中 'v' 的值(已解包)
"""
def set(self, category: str, key: str, value, updated_by_id) -> None:
"""
写入标量配置 + 主动 invalidate 缓存
"""
def get_lookup_items(self, module: str, key: str) -> list[dict]:
"""
获取可配置枚举选项lookup_items 表)
仅返回 is_active=True 的项,按 sort_order 排序
Cache Key: {tenant_schema}:setting:lookup:{module}.{key}
返回格式:[{"value": "walk_in", "label_zh": "上门", "is_system": True}, ...]
"""
def get_field_requirements(
self, module: str, entity_type: str, trade_status: str
) -> dict[str, str]:
"""
获取字段必填规则
匹配顺序:精确匹配(entity_type + trade_status) > 通配规则(trade_status='*')
Cache Key: {tenant_schema}:setting:field_req:{module}.{entity_type}.{trade_status}
返回格式:{"orientation": "optional", "decoration": "required", ...}
"""
```
---
## 五、Redis 缓存键规范
| 用途 | Cache Key | TTL | 失效触发 |
|------|-----------|-----|---------|
| 标量配置 | `{schema}:setting:kv:{category}.{key}` | 300s | `TenantSettingsService.set()` |
| 可配置枚举 | `{schema}:setting:lookup:{module}.{key}` | 300s | 管理员保存 lookup_items |
| 字段规则 | `{schema}:setting:field_req:{module}.{entity_type}.{trade_status}` | 300s | 管理员保存 field_requirement_rules |
| 客源规则(整体) | `{schema}:setting:client_rules` | 300s | 任意客源规则变更 |
> TTL 300s5 分钟)对应 PRD 成功指标「配置变更生效时延 ≤ 5 分钟」。
---
## 六、目录结构
```
apps/setting/
├── models/
│ ├── lookup.py # LookupGroup, LookupItem
│ └── setting.py # TenantSetting, FieldRequirementRule
├── services/
│ └── tenant_settings_service.py # 统一配置读取服务(禁止直接查表)
├── views/
│ ├── lookup_views.py # 参数配置页面US-SETTING-001-A
│ ├── field_rule_views.py # 房源字段规则US-SETTING-001-B
│ └── client_rule_views.py # 客源规则US-SETTING-001-C
├── templates/setting/
├── fixtures/
│ ├── lookup_groups.json # 分组种子数据3 组)
│ ├── lookup_items.json # 选项种子数据is_system=True
│ ├── tenant_settings.json # 默认配置种子数据
│ └── field_requirement_rules.json # 默认字段规则
├── migrations/
│ ├── 0001_lookup_groups.py
│ ├── 0002_lookup_items.py
│ ├── 0003_tenant_settings.py
│ └── 0004_field_requirement_rules.py
└── urls.py
```
---
## 七、迁移执行顺序
```
0001_lookup_groups # 先建分组表(无外键依赖)
0002_lookup_items # 再建选项表(依赖 lookup_groups + staff
0003_tenant_settings # 独立,无外键依赖
0004_field_requirement_rules # 独立,仅依赖 staff
```
迁移执行后,通过 `call_command('loaddata', 'lookup_groups')` 等方式加载 fixtures 种子数据。
---
## 八、关键约束与禁止项
| 约束 | 规则 |
|------|------|
| 不可删除系统预制项 | `lookup_items.is_system = True` 的记录Service 层硬拦截物理删除请求 |
| 不可修改已有 value | `lookup_items.value` 写入后只读;修改请停用旧项 + 新增新项 |
| 不可直接查询配置表 | 业务模块property/client**必须**通过 `TenantSettingsService` 读取,禁止 ORM 直查 |
| entity_type 值域 | 必须与 `property.property_type` CHECK 约束完全一致(见第三章) |
| Redis Key 前缀 | 必须携带租户 schema 前缀,格式:`{tenant_schema}:setting:{type}:{key}` |
---
## 九、设计决策ADR
| 决策 | 选择 | 理由 |
|------|------|------|
| 枚举两级架构 | `enum_labels`Public/固定)+ `lookup_items`Tenant/可配置)分离 | 保障系统一致性的同时给租户灵活度 |
| `lookup_groups` 由研发预置 | 是 | 防止租户随意创建不规范分组,控制可配置范围边界 |
| `tenant_settings` 使用 JSONB | 是 | 支持 bool/int/string 等多类型,无需为每类型单独建列 |
| `field_requirement_rules` 不新增字段 | 是 | 规则层只控制「必填/选填/隐藏」,字段存在性由 DATA_MODEL_PROPERTY 决定 |
| 服务层统一读取 | `TenantSettingsService` | 统一缓存管理,业务层与配置存储解耦 |
| trade_status 不复用 property.status | 独立 `sale/rent/sale_rent/*` | 录入场景的交易意图与房源全生命周期状态不同,避免耦合 |

View File

@@ -0,0 +1,264 @@
`DATA_MODEL.md` 的核心目标是:**让 AI 在触碰任何数据相关代码时,都能理解业务语义,而不只是看到一堆字段名。**
---
### 必须包含的内容
#### 1. 领域概览Domain Overview
用业务语言(不是技术语言)描述核心概念和它们的关系。这是 AI 理解"为什么"的基础。
md
```md
## Domain Overview
This is a **multi-tenant** project management tool.
Core concepts:
- **Workspace**: The top-level container, maps to a paying customer (company)
- **Project**: Lives inside a Workspace, has a lifecycle (draft → active → archived)
- **Task**: The atomic unit of work, always belongs to a Project
- **Member**: A User's role within a specific Workspace (not global)
Key business rules:
- A User can belong to multiple Workspaces with different roles
- Deleting a Project soft-deletes all its Tasks (never hard delete)
- Only Workspace `owner` or `admin` can invite new Members
```
---
#### 2. 实体关系图Entity Relationship
用文字或 ASCII 图表达清楚关系,不要依赖读者去脑补。
md
```md
## Entity Relationships
Workspace (1) ──── (N) Project
Workspace (1) ──── (N) Member
Member (N) ──── (1) User ← same User, different roles per Workspace
Project (1) ──── (N) Task
Task (N) ──── (1) Member ← assignee
Task (1) ──── (N) Comment
Task (N) ──── (N) Tag ← via task_tags join table
```
---
#### 3. 完整 Schema 定义Schema Definition
每个表都需要:字段 + 类型 + 约束 + **业务注释**。注释是给 AI 的关键上下文。
md
```md
## Schema
### workspaces
| Column | Type | Constraints | Notes |
|--------------|-------------|-------------------|--------------------------------|
| id | uuid | PK, default gen | |
| slug | text | UNIQUE, NOT NULL | URL identifier, immutable after creation |
| name | text | NOT NULL | |
| plan | text | NOT NULL | enum: 'free' | 'pro' | 'enterprise' |
| created_at | timestamptz | NOT NULL, default now() | |
| deleted_at | timestamptz | nullable | soft delete, filter WHERE deleted_at IS NULL |
### members
| Column | Type | Constraints | Notes |
|--------------|-------------|-------------------|--------------------------------|
| id | uuid | PK | |
| workspace_id | uuid | FK → workspaces.id, CASCADE DELETE | |
| user_id | uuid | FK → users.id | |
| role | text | NOT NULL | enum: 'owner' \| 'admin' \| 'member' \| 'viewer' |
| invited_by | uuid | FK → members.id, nullable | null = founding owner |
| joined_at | timestamptz | nullable | null = invitation pending |
-- UNIQUE(workspace_id, user_id) — one membership per workspace per user
```
---
#### 4. 枚举与常量Enums & Constants
把所有枚举值集中在一处AI 就不会自己发明状态值。
md
```md
## Enums & Constants
### Task Status (ordered, represents workflow progression)
pending → in_progress → in_review → done → cancelled
Rules:
- Only forward transitions are allowed (no reopening cancelled tasks)
- `done` and `cancelled` are terminal states
- Changing status logs an entry in task_activity
### Member Role Permissions
| Action | owner | admin | member | viewer |
|---------------------|-------|-------|--------|--------|
| Invite members | ✅ | ✅ | ❌ | ❌ |
| Delete project | ✅ | ❌ | ❌ | ❌ |
| Create tasks | ✅ | ✅ | ✅ | ❌ |
| Comment on tasks | ✅ | ✅ | ✅ | ✅ |
```
---
#### 5. 索引策略Indexes
告诉 AI 哪些查询是热路径,避免它写出全表扫描的代码。
md
```md
## Indexes
-- tasks is the most queried table, optimize for these patterns:
CREATE INDEX idx_tasks_project_status ON tasks(project_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_assignee ON tasks(assignee_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_members_user ON members(user_id); -- "get all workspaces for a user"
-- Avoid: never query tasks without a project_id filter
-- Avoid: never do SELECT * on tasks, always select specific columns
```
---
#### 6. 软删除约定Soft Delete Convention
如果使用软删除,必须明确说明规则,否则 AI 会写出漏掉过滤条件的查询。
md
```md
## Soft Delete Convention
Tables with soft delete: workspaces, projects, tasks
Rules:
- ALL queries MUST include `WHERE deleted_at IS NULL` unless explicitly retrieving deleted records
- Use `deletedAt: new Date()` to soft delete, never use DELETE SQL
- Cascading: deleting a Project sets deleted_at on all its Tasks in the same transaction
- Deleted records are purged by a cron job after 30 days (hard delete)
⚠️ AI Note: Never generate a query on these tables without the soft delete filter.
```
---
#### 7. 多租户隔离规则Multi-tenancy Rules
这是安全的核心,必须让 AI 理解每次查询都要带租户过滤。
md
```md
## Multi-tenancy & Data Isolation
This is a **workspace-scoped** multi-tenant system.
Rules:
- EVERY query involving projects, tasks, members MUST be scoped by workspace_id
- Never query tasks directly by id alone — always join through project → workspace
- The workspace_id must come from the authenticated session, never from user input
- Row Level Security (RLS) is enabled on Supabase for all tenant tables
Correct pattern:
SELECT * FROM tasks
JOIN projects ON tasks.project_id = projects.id
WHERE projects.workspace_id = :workspaceId ← always present
AND tasks.id = :taskId
Wrong pattern (security hole):
SELECT * FROM tasks WHERE id = :taskId ← missing tenant scope
```
---
#### 8. 核心查询模式Common Query Patterns
把最常用的查询写出来AI 会直接复用,而不是重新发明。
md
````md
## Common Query Patterns
### Get tasks for a project (with assignee info)
```sql
SELECT t.*, m.user_id, u.name as assignee_name
FROM tasks t
LEFT JOIN members m ON t.assignee_id = m.id
LEFT JOIN users u ON m.user_id = u.id
WHERE t.project_id = :projectId
AND t.deleted_at IS NULL
ORDER BY t.created_at DESC;
```
### Check if user has permission in workspace
```sql
SELECT role FROM members
WHERE workspace_id = :workspaceId
AND user_id = :userId
AND joined_at IS NOT NULL; -- pending invitations don't count
```
````
---
#### 9. 明确禁止的操作Forbidden Operations
md
```md
## Forbidden Operations
- ❌ Never hard DELETE on workspaces, projects, or tasks — use soft delete
- ❌ Never UPDATE a task's status by skipping intermediate states
- ❌ Never expose internal UUIDs in URLs — use slugs for workspaces/projects
- ❌ Never store user PII (email, name) in tasks or comments — always join from users table
- ❌ Never write a migration that drops a column without a deprecation period
```
---
#### 10. 数据迁移约定Migration Convention
md
```md
## Migration Convention
- Migration tool: Drizzle Kit
- File naming: `0001_create_workspaces.ts`, sequential, never rename
- Every migration must be reversible (include `down` function)
- Never edit a migration that has been merged to main
- Backfill scripts go in /scripts, not in migrations
```
---
### 内容优先级总结
|优先级|内容|为什么关键|
|---|---|---|
|🔴 必须|领域概览 + 业务规则|AI 需要理解"为什么"才能做正确取舍|
|🔴 必须|完整 Schema + 字段注释|防止字段命名歧义和类型错误|
|🔴 必须|软删除 + 多租户规则|安全漏洞和数据污染的高发区|
|🟡 重要|枚举常量 + 状态机|防止 AI 自己发明状态值|
|🟡 重要|常用查询模式|复用 > 重新发明|
|🟢 加分|索引策略|引导 AI 写出性能友好的查询|
|🟢 加分|迁移约定|保持 schema 演进的可控性|
---
**核心原则**`DATA_MODEL.md` 写的不是给数据库看的 DDL而是给 AI 看的**业务契约**。每一个"显而易见"的业务规则,在 AI 眼里都是需要被明确告知的约束。没写的,它都会自己猜。

View File

@@ -0,0 +1,786 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 统一枚举字典ENUMS
> **定位**:本文件是 Fonrey 全局枚举标准Public + Tenant的统一实现基线。
> **版本**v2.2
> **日期**2026-04-28
> **适用范围**`DATA_MODEL_PUBLIC.md`、`DATA_MODEL_LOGIN.md`、`DATA_MODEL_ORG.md`、`DATA_MODEL_COMPLEX.md`、`DATA_MODEL_PROPERTY.md`、`DATA_MODEL_CLIENT.md`、`DATA_MODEL_PERMISSION.md`、`DATA_MODEL_SETTING.md`
> **⚠️ 枚举值命名规范**:所有枚举值统一使用 **`lower_snake_case`**(全小写+下划线)。
> 历史遗留大写值(`SUCCESS`/`FAILED`、`BOOLEAN`/`SCOPE`/`INTEGER`、`REPLACE`/`RESTRICT`/`GRANT`、`A_urgent`/`A_app`/`B_schema`/`C_feature`)在 v2.2 中已统一迁移为小写。
> **DB `CHECK` 约束、`enum_labels` 种子数据、前端代码须同步更新为新值**;迁移脚本须包含历史数据 UPDATE。
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、枚举分层标准(必须遵守)
Fonrey 采用两层枚举体系:
1. **固定枚举Fixed Enum**
- 值域固定,受 `CHECK` 或强业务约束保护
- 作为系统契约,不能随意改值
- 可落地到 `public.enum_labels`(用于统一标签)
2. **可配置枚举Configurable Enum**
- 值域由租户自行维护
- 存储在 Tenant Schema`lookup_groups` + `lookup_items`
- **禁止**对业务表字段加固定 `CHECK IN (...)`
---
## 二、全局固定枚举Public / 平台级)
### 2.1 tenant 生命周期
**domain**: `public.tenant.plan`
- `basic`:基础版
- `professional`:专业版
- `enterprise`:企业版
**domain**: `public.tenant.status`
- `creating`:创建中
- `active`:正常
- `suspended`:已挂起
- `pending_delete`:待删除
- `deleted`:已删除
- `failed`:创建/初始化失败
**domain**: `public.tenant.suspended_reason`
- `overdue`:欠费
- `violation`:违规
- `requested`:客户申请
- `other`:其他
### 2.2 平台管理员
**domain**: `public.platform_admin.role`
- `super_admin`:超级管理员
- `ops_operator`:运营管理员
- `read_only_auditor`:只读审计员
### 2.3 平台审计与备份导出
**domain**: `public.platform_audit.result`
- `success`:成功
- `failed`:失败
**domain**: `public.backup_schedule.frequency`
- `hourly`:每小时
- `daily`:每日
- `weekly`:每周
**domain**: `public.backup_schedule.storage_target`
- `local`:本地存储
- `s3`Amazon S3
- `r2`Cloudflare R2
- `gcs`Google Cloud Storage
**domain**: `public.backup_record.trigger_type`
- `auto`:自动触发
- `manual`:手动触发
- `pre_upgrade`:升级前触发
- `pre_restore`:恢复前触发
**domain**: `public.backup_record.status`
- `pending`:待执行
- `in_progress`:执行中
- `success`:成功
- `failed`:失败
**domain**: `public.export_task.format`
- `csv`CSV
- `json`JSON
- `sql_dump`SQL 导出
**domain**: `public.export_task.status`
- `pending`:待执行
- `in_progress`:执行中
- `success`:成功(语义等价原 `done`,与 `backup_record.status` 对齐)
- `failed`:失败
### 2.4 升级与发布Public
**domain**: `public.upgrade_event.event_type`
- `upgrade`:升级
- `rollback`:回滚
**domain**: `public.upgrade_event.upgrade_type`
- `app`A类-应用升级(原 `A_app`
- `schema`B类-数据库结构升级(原 `B_schema`
- `feature`C类-功能开关升级(原 `C_feature`
**domain**: `public.upgrade_event.strategy`
- `full`:全量发布
- `canary`:灰度发布
**domain**: `public.upgrade_event.status`
- `draft`:草稿
- `pre_check`:预检查
- `pre_backup`:预备份
- `batch_running`:批次执行中
- `batch_done`:批次完成
- `halted`:已暂停
- `succeeded`:已成功
- `failed`:失败
- `rollback_running`:回滚中
- `rolled_back`:已回滚
**domain**: `public.upgrade_event.failure_policy`
- `halt_batch`:失败即停止批次
- `continue`:失败继续
**domain**: `public.client_release.platform`
- `win32`Windows 客户端
**domain**: `public.client_release.arch`
- `x64`x64 架构
- `arm64`ARM64 架构
**domain**: `public.client_release.release_type`
- `normal`:普通更新
- `force`:强制更新
**domain**: `public.client_release.status`
- `draft`:草稿
- `published`:已发布
- `archived`:已归档
---
## 三、Tenant 固定枚举(模块级,值域统一)
> 说明:以下字段在 Tenant Schema 中存储,但值域为系统统一标准,属于“全局实现标准”。
## 3.1 登录认证account/login
**domain**: `login.user_account.status`
- `active`:启用
- `disabled`:停用
- `locked`:锁定
**domain**: `login.login_attempt.failure_reason`
- `wrong_password`:用户名或密码错误
- `wrong_captcha`:验证码错误
- `account_locked`:账号锁定
- `account_disabled`:账号停用
- `tenant_not_found`:租户不存在
---
## 3.2 组织人事org
**domain**: `org.org_unit.type`
- `company`:公司
- `division`:事业部
- `region`:大区
- `area`:区域
- `district`:片区
- `store`:门店
- `group`:店组
- `functional`:职能部门
**domain**: `org.org_unit.attribute`
- `direct`:直营
- `franchise`:加盟
**domain**: `org.staff.role`
- `agent`:经纪人
- `store_manager`:店长
- `area_manager`:区域经理
- `admin`:系统管理员
- `operator`:运营/行政
- `system`:系统账号
**domain**: `org.staff.status`
- `active`:在职
- `probation`:试用
- `resigned`:离职
- `frozen`:冻结
**domain**: `org.staff_personal_info.gender`
- `male`:男
- `female`:女
- `unknown`:未知
**domain**: `org.staff_personal_info.id_type`
- `id_card`:身份证
- `passport`:护照
- `other`:其他
**domain**: `org.staff_transfer.transfer_type`
- `onboard`:入职
- `transfer`:调动
- `resign`:离职
- `rejoin`:复职
- `supervisor_change`:上级变更
- `role_change`:角色变更
- `freeze`:冻结账号
- `unfreeze`:恢复账号
**domain**: `org.staff_account.platform`
- `fonrey`:房睿主账号
- `58anjuke`58安居客
- `cnreic`:中国网络经纪人
- `wechat_mp`:微信公众号
---
## 3.3 权限系统permission
**domain**: `permission.module`
- `home`:首页
- `property`:房源
- `new_house`:新房
- `client`:客源
- `transaction`:交易
- `data`:数据
- `marketing`:营销
- `hr`人事OA
- `contract`:合同
- `trinet`:三网
- `system`:系统
- `mobile`:移动端
- `smart_store`:智能门店
- `recharge`:在线充值
**domain**: `permission.value_type`
- `boolean`:开关型(原 `BOOLEAN`
- `scope`:范围型(原 `SCOPE`
- `integer`:数值型(原 `INTEGER`
**domain**: `permission.role_category`
- `agent`:置业顾问
- `store_manager`:店管
- `director`:总经
- `operator`:运营/行政
- `custom`:自定义
**domain**: `permission.scope_level`
- `none`:无
- `self`:本人
- `group`:本组
- `store`:本门店
- `area`:本区域
- `region`:本大区
- `company`:全公司
**domain**: `permission.override_mode`
- `replace`:覆盖(原 `REPLACE`
- `restrict`:限制(原 `RESTRICT`
- `grant`:授予(原 `GRANT`
**domain**: `permission.data_scope_type`
- `self`:本人
- `group`:本组
- `store`:本门店
- `area`:本区域
- `region`:本大区
- `company`:全公司
- `custom_unit`:自定义组织单元
**domain**: `permission.change_log.target_type`
- `role`:角色
- `role_permission`:角色权限
- `staff_role`:员工角色
- `staff_override`:员工权限覆盖
- `staff_scope`:员工数据范围
**domain**: `permission.change_log.action`
- `create`:创建
- `update`:更新
- `delete`:删除
- `assign`:分配
- `revoke`:撤销
---
## 3.4 楼盘区域complex
**domain**: `complex.school.type`
- `primary`:小学
- `middle`:初中
- `high`:高中
- `k9`:九年一贯制
- `k12`:十二年一贯制
**domain**: `complex.school.nature`
- `public`:公立
- `private`:私立
- `international`:国际
**domain**: `complex.school.level`
- `normal`:普通
- `key`:重点
- `top`:名校
**domain**: `complex.building_type`
- `slab`:板楼
- `tower`:塔楼
- `slab_tower`:板塔结合
**domain**: `complex.water_type`
- `civil`:民水
- `commercial`:商水
**domain**: `complex.electricity_type`
- `civil`:民电
- `commercial`:商电
**domain**: `complex.school_zone_type`
- `guaranteed`:对口
- `reference`:参考
- `lottery`:摇号
**domain**: `complex.photo.category`
- `complex`:楼盘图
- `layout`:户型图
- `vr`VR图
- `other`:其他
---
## 3.5 房源property
**domain**: `property.property_type`
- `residential`:住宅
- `villa`:别墅
- `commercial_residential`:商住
- `shop`:商铺
- `office`:写字楼
- `other`:其他
**domain**: `property.status`
- `for_sale`:出售
- `for_rent`:出租
- `for_sale_rent`:租售
- `suspended`:暂缓
- `sold_elsewhere`:他售
- `rented_elsewhere`:他租
- `sold`:成交
- `unlisted`:未挂牌
**domain**: `property.attribute`
- `public`:公盘
- `private`:私盘
- `special`:特盘
- `sealed`:封盘
**domain**: `property.orientation`
- `east`:东
- `south`:南
- `west`:西
- `north`:北
- `southeast`:东南
- `northeast`:东北
- `east_west`:东西
- `south_north`:南北
- `northwest`:西北
- `southwest`:西南
**domain**: `property.decoration`
- `rough`:毛坯
- `plain`:清水
- `simple`:简装
- `medium`:中装
- `fine`:精装
- `luxury`:豪装
**domain**: `property.house_status`
- `owner_occupied`:业主自住
- `vacant`:空置
- `tenant_occupied`:租客在住
- `unknown`:未知
**domain**: `property.viewing_time`
- `anytime`:随时看房
- `by_appointment`:预约看房
- `inconvenient`:不便看房
**domain**: `property.grade`
- `a`A急迫
- `b`B较强
- `c`C一般
- `d`D较弱
**domain**: `property.contact.gender`
- `male`:先生
- `female`:女士
**domain**: `property.contact.identity`
- `owner`:业主
- `contact`:联系人
- `subletter`:转租人
- `tenant`:租客
- `agent`:代理人
- `corporate`:企业法人
**domain**: `property.listing_history.listing_type`
- `for_sale`:出售挂牌
- `for_rent`:出租挂牌
**domain**: `property.listing_history.status`
- `active`:生效中
- `ended`:已结束
**domain**: `property.follow_log.log_type`
- `written`:手写跟进
- `modified`:修改跟进
- `sensitive_op`:敏感操作
- `sensitive_view`:敏感查看
- `other`:其他
- `system`:系统
**domain**: `property.follow_log.ai_tag`
- `ai_for_sale`AI判断可售
- `ai_not_for_sale`AI判断不可售
**domain**: `property.follow_attachment.file_type`
- `bmp`BMP
- `jpg`JPG
- `png`PNG
- `svg`SVG
- `gif`GIF
**domain**: `property.key.key_type`
- `mechanical`:机械钥匙
- `password`:密码钥匙
**domain**: `property.commission.owner_type`
- `owner`:产权人本人
- `authorized_third`:授权第三方
**domain**: `property.commission.status`
- `active`:有效
- `expired`:过期
- `cancelled`:取消
**domain**: `property.commission_attachment.category`
- `id_card`:身份证件
- `property_cert`:产权证明
- `commission_letter`:委托书
- `other`:其他
**domain**: `property.field_survey.status`
- `draft`:草稿
- `submitted`:已提交
**domain**: `property.survey_photo.category`
- `layout`:户型图
- `living_room`:客厅
- `dining_room`:餐厅
- `bedroom`:卧室
- `bathroom`:卫生间
- `kitchen`:厨房
- `entrance`:入户
- `balcony`:阳台
- `study`:书房
- `indoor_other`:室内其他
- `outdoor`:室外
**domain**: `property.photo.category`
- `cover`:封面
- `entrance`:入户
- `living_room`:客厅
- `dining_room`:餐厅
- `bedroom`:卧室
- `bathroom`:卫生间
- `kitchen`:厨房
- `balcony`:阳台
- `study`:书房
- `indoor_other`:室内其他
- `outdoor`:室外
- `panorama`:全景
**domain**: `property.attachment.category`
- `id_card`:身份证件
- `property_cert`:产权证明
- `commission_letter`:委托书
- `other`:其他
**domain**: `property.number_holder_approval.status`
- `pending`:待审批
- `approved`:已通过
- `rejected`:已驳回
---
## 3.6 客源client
**domain**: `client.client_type`
- `private`:私客
- `public`:公客
- `transacted`:成交客
**domain**: `client.status`
- `buying`:求购
- `renting`:求租
- `buy_or_rent`:租购
- `suspended`:暂缓
- `bought`:已购
- `rented_done`:已租
- `public`:公客
- `invalid`:无效
> **⚠️ 合法组合约束**`client_type` × `client.status`
> `client_type` 表示客源的"身份类别"`status` 表示客源的"当前业务状态",两者是不同维度。
> 合法组合矩阵如下:
>
> | `client_type` | 允许的 `status` 值 | 禁止的 `status` 值 |
> |---|---|---|
> | `private`(私客) | `buying` / `renting` / `buy_or_rent` / `suspended` / `invalid` | `public`、`bought`、`rented_done`(未成交不可用终态) |
> | `public`(公客) | `public` / `buying` / `renting` / `buy_or_rent` / `suspended` / `invalid` | `bought`、`rented_done` |
> | `transacted`(成交客) | `bought` / `rented_done` | 其他所有值(终态不可逆) |
>
> **实施要求**
> - 服务层(`ClientService`)在状态变更时必须校验组合合法性
> - DB 侧可用触发器或 `CHECK` 约束覆盖最关键禁止项(如 `transacted` + 非终态)
> - `private → public` 转换须调用专用方法 `transfer_to_public()`,同时修改 `client_type` 和 `status`
**domain**: `client.grade`
- `A`A急迫
- `B`B较强
- `C`C一般
- `D`D较弱
- `E`E暂不关注
**domain**: `client.property_usage`
- `residential`:住宅
- `villa`:别墅
- `commercial_residential`:商住
- `shop`:商铺
- `office`:写字楼
- `other`:其他
**domain**: `client.buying_purpose`
- `rigid`:刚需
- `investment`:投资
- `school_district`:学区
- `upgrade`:改善
- `commercial`:商用
- `other`:其他
**domain**: `client.payment_method`
- `full`:全额
- `mortgage`:商业贷款
- `mortgage_fund`:商贷+公积金
- `fund`:公积金
**domain**: `client.properties_owned`
- `none`:无
- `local_none`:本地无/外地有
- `local_has`:本地有
**domain**: `client.id_type`
- `id_card`:身份证
- `passport`:护照
- `hk_macao`:港澳通行证
- `other`:其他
**domain**: `client.transfer_to_public_type`
- `manual`:手动转公
- `auto`:自动转公
- `marketing_jump`:营销客跳公
- `resource_public`:资料客素公
**domain**: `client.invalid_reason`
- `invalid_phone`:号码无效
- `peer_agent`:同行
- `ad`:广告推销
- `no_intent`:无意向
- `other`:其他
**domain**: `client.transacted_type`
- `bought`:我购
- `rented`:我租
**domain**: `client.transacted_property_type`
- `second_hand`:二手
- `new_house`:新房
**domain**: `client.activity_level`
- `new_matched`:新配对
- `active_7d`7日活跃
- `active_30d`30日活跃
- `active_90d`90日活跃
- `expiring`:即将过期
- `frozen`:暂缓中
- `invalid`:无效
**domain**: `client.contact.gender`
- `male`:先生
- `female`:女士
**domain**: `client.requirement_type`
- `second_hand`:二手
- `new_house`:新房
- `rental`:租房
**domain**: `client.floor_preference`
- `no_first`:不要一楼
- `low`:低楼层
- `mid`:中楼层
- `high`:高楼层
- `no_top`:不要顶楼
**domain**: `client.orientation`
- `east`:东
- `south`:南
- `west`:西
- `north`:北
**domain**: `client.decoration`
- `rough`:毛坯
- `plain`:清水
- `simple`:简装
- `medium`:中装
- `fine`:精装
- `luxury`:豪装
**domain**: `client.building_age_range`
- `within_5y`5年内
- `5_10y`5-10年
- `10_15y`10-15年
- `15_20y`15-20年
- `over_20y`20年以上
**domain**: `client.follow_log.log_type`
- `written`:写入跟进
- `modified`:修改跟进
- `sensitive_view`:敏感查看
- `other`:其他
- `system`:系统
**domain**: `client.viewing.viewing_type`
- `appointment`:预约
- `viewing`:带看
- `revisit`:复看
- `empty`:空看
**domain**: `client.viewing.client_intent`
- `interested`:感兴趣
- `not_interested`:不感兴趣
- `negotiating`:谈判中
- `cancelled`:取消
**domain**: `client.property_match.match_source`
- `recorded`:录客配房
- `system`:系统配房
**domain**: `client.property_match.match_group`
- `quality_layout`:优质户型
- `price_reduced`:降价
- `hot`:热门
- `newly_listed`:新上
**domain**: `client.property_match.status`
- `suggested`:待推送
- `shared`:已分享
- `rejected`:已反馈不合适
- `viewed`:客户已查看
**domain**: `client.status_log.change_type`
- `status_change`:改状态
- `grade_change`:改等级
- `to_public`:转公客
- `to_transacted`:转成交
- `to_invalid`:转无效
- `owner_change`:改归属人
- `source_change`:改来源
- `merge`:合并客源
---
## 3.7 系统配置setting
**domain**: `setting.tenant_setting.value_type`
- `bool`:布尔
- `int`:整数
- `string`:字符串
- `enum`:枚举
**domain**: `setting.field_rule.module`
- `property`:房源
- `client`:客源(预留)
**domain**: `setting.field_rule.entity_type`(与 `property.property_type` 对齐)
- `residential`:住宅
- `villa`:别墅
- `commercial_residential`:商住
- `shop`:商铺
- `office`:写字楼
- `other`:其他
**domain**: `setting.field_rule.trade_status`
- `sale`:出售
- `rent`:出租
- `sale_rent`:租售
- `all`:全部(原设计为 `*`,因 SQL/URL/权限表达式通配误解风险已统一改为 `all`DB CHECK 约束、前端筛选器须同步)
**domain**: `setting.field_rule.requirement`
- `required`:必填
- `optional`:选填
- `hidden`:隐藏
---
## 四、Tenant 可配置枚举字段清单lookup_items 权威)
> 以下字段值域由 `lookup_items` 维护,属于租户级配置,不在业务表中写死 `CHECK`。
| domain统一命名 | 对应字段 | 当前状态 | 说明 |
|---|---|---|---|
| `client.source` | `clients.source` | ✅ 已落地 | 客源来源 |
| `client.follow_purpose` | `client_follow_logs.purpose` | ✅ 已落地 | 客源跟进目的 |
| `property.source` | `properties.source` | ✅ 已落地 | 房源来源 |
| `property.follow_purpose` | `follow_logs.purpose` | 🔄 建议统一 | 房源跟进目的(建议与 `client.follow_purpose` 共享或独立分组) |
| `property.commission_type` | `commissions.commission_type` | 🔄 待入组 | 委托类型(独家/非独家等) |
| `client.match_feedback` | `client_property_matches.feedback` | 🔄 待入组 | 配房反馈原因 |
| `org.reward_punish_category` | `staff_reward_punish.category` | 🔄 待入组 | 人事奖惩类别 |
### 4.1 lookup_groups 规范Tenant Schema
- `module`: 业务模块标识(如 `client` / `property` / `org`
- `key`: 领域键(如 `source` / `follow_purpose`
- 同一组内 `value` 不可重复(`UNIQUE(group_id, value)`
- `is_system = TRUE` 的项禁止物理删除(仅可停用)
---
## 五、统一实现约束
1. **固定枚举值不可改名**:只能新增或停用,禁止修改既有 value。
2. **中文展示从字典取值**:前端/UI 不得硬编码中文。
3. **可配置枚举不得加固定 CHECK**:防止租户自定义被数据库约束阻断。
4. **跨模块同名枚举必须复用语义**:如 `status` 在不同领域必须使用 domain 区分,不允许混用。
5. **缓存规范**
- 固定枚举:`public:enum_labels:{domain}`(建议 TTL 24h
- 可配置枚举:`{schema}:setting:lookup:{module}.{key}`TTL 300s
---
## 六、变更流程(必须同步)
新增或修改任一枚举时,必须同时更新:
1. 本文档 `ENUMS.md`
2. 对应 `DATA_MODEL_*.md` 字段定义CHECK / 注释 / 业务规则)
3. `enum_labels` 种子数据(若为固定枚举)或 `lookup_groups/items` fixture若为可配置枚举
4. 服务层缓存失效逻辑Redis key
---
## 七、与 ADR 的一致性说明
本文件已对齐以下冻结决策:
- 固定枚举与可配置枚举双轨并存
- 状态机和值域以文档为权威来源
- Tenant 级可配置枚举统一由 `setting` 模块托管
- Agent 编码前先读枚举标准,禁止各模块自行定义“影子枚举”

View File

@@ -0,0 +1,322 @@
> For AI assistants: Read this entire file before writing any code. All decisions here are final.
# Fonrey — 统一状态机规范 (STATE_MACHINE)
**定位**:本文档是 Fonrey 项目所有实体生命周期与状态流转的唯一权威定义。
**变更历史**
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-06-04 | Sisyphus | 初始版本:整合各模块状态机逻辑,对齐 ENUMS v2.2 |
---
## 1. 通用约定
- **状态值命名**:统一采用 `lower_snake_case`,必须与 `DATA_MODEL/ENUMS.md` 完全一致。
- **触发动作**状态迁移必须由显式的动作Action或事件Event触发。
- **操作日志**:所有关键状态迁移(除系统内部中间态外)必须记录对应的 `status_log``audit_log` 表。
- **不可逆规则**:标注为“终态”的状态禁止迁回初始态或中间态。
- **权限要求**:状态迁移接口必须校验操作者的 DataScope 与 Function Permission。
---
## 2. 租户生命周期 (Public.Tenant)
**实体**: `public.tenants` | **字段**: `status`
**参考**: `DATA_MODEL_PUBLIC.md` §2.1, §4.1
```mermaid
stateDiagram-v2
[*] --> creating
creating --> active: 初始化完成
creating --> failed: 初始化失败
active --> suspended: 逾期/违规/申请
suspended --> active: 恢复条件满足
active --> pending_delete: 申请注销
suspended --> pending_delete: 申请注销
pending_delete --> deleted: 30天后/人工确认
deleted --> [*]
```
### 状态枚举值
(domain: `public.tenant.status`)
- `creating`: 创建中
- `active`: 正常
- `suspended`: 已挂起
- `pending_delete`: 待删除
- `deleted`: 已删除
- `failed`: 创建/初始化失败
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `creating` | `active` | 租户 Schema 初始化成功 | System | 写 `tenant_status_logs` | 否 |
| `creating` | `failed` | 初始化脚本执行失败 | System | 记录错误日志 | 否 |
| `active` | `suspended` | 欠费/违规/License过期 | System/SuperAdmin | 设置 `suspended_until`, 写日志 | 是 |
| `suspended` | `active` | 缴费成功/解封/到期恢复 | System/SuperAdmin | 清除挂起原因 | 是 |
| `active`/`suspended` | `pending_delete` | 提交注销申请 | TenantAdmin | 设置 `deleted_at` | 是 |
| `pending_delete` | `deleted` | 确认注销/静默期结束 | SuperAdmin/System | 物理删除或深度标记 | 否 |
**来源章节**: `DATA_MODEL_PUBLIC.md` §4.1
---
## 3. 客源状态机 (Client)
**实体**: `client.clients` | **字段**: `status`
**参考**: `DATA_MODEL_CLIENT.md` §3.1, §4 | `ENUMS.md` §3.6
```mermaid
stateDiagram-v2
[*] --> buying
[*] --> renting
[*] --> buy_or_rent
state "活跃态" as Active {
buying
renting
buy_or_rent
}
Active --> suspended: 暂时无需求
suspended --> Active: 重新激活
Active --> public: 手动转公/超时自动
suspended --> public: 手动转公
Active --> bought: 转成交(我购)
Active --> rented_done: 转成交(我租)
Active --> invalid: 转无效
suspended --> invalid: 转无效
invalid --> Active: 经理审批恢复
bought --> [*]
rented_done --> [*]
public --> [*]
```
### 状态枚举值
(domain: `client.status`)
- `buying`: 求购
- `renting`: 求租
- `buy_or_rent`: 租购
- `suspended`: 暂缓
- `bought`: 已购 (我购)
- `rented_done`: 已租 (我租)
- `public`: 公客
- `invalid`: 无效
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `buying`/`renting`/`buy_or_rent` | `suspended` | 修改状态为暂缓 | 经纪人 | 写 `client_status_logs` | 是 |
| `suspended` | `buying`/`renting`/`buy_or_rent` | 重新激活需求 | 经纪人 | 更新 `last_active_at` | 是 |
| `Active`/`suspended` | `public` | 手动转公/超时自动转公 | 经纪人/System | 修改 `client_type='public'` | 否 |
| `Active` | `bought`/`rented_done` | 录入成交合同 | 经纪人 | 修改 `client_type='transacted'` | 否 |
| `Active`/`suspended` | `invalid` | 标记无效 | 经纪人 | 记录 `invalid_reason` | 需审批 |
### 禁迁规则
- `transacted` (bought/rented_done) 状态为绝对终态,禁止迁往任何其他状态。
- `public` 状态客源禁止直接修改为 `private` 状态下的需求状态,必须走领用流程。
**来源章节**: `DATA_MODEL_CLIENT.md` §4, `ENUMS.md` §3.6
---
## 4. 房源状态机 (Property)
**实体**: `property.properties` | **字段**: `status`
**参考**: `DATA_MODEL_PROPERTY.md` §3, §4.1
```mermaid
stateDiagram-v2
[*] --> unlisted
unlisted --> for_sale: 挂牌出售
unlisted --> for_rent: 挂牌出租
state "在盘态" as Listed {
for_sale
for_rent
for_sale_rent
}
Listed --> suspended: 业主暂缓
suspended --> Listed: 重新上架
Listed --> sold: 本司成交
Listed --> sold_elsewhere: 他售
Listed --> rented_elsewhere: 他租
Listed --> unlisted: 下架
sold --> [*]
sold_elsewhere --> [*]
```
### 状态枚举值
(domain: `property.status`)
- `for_sale`: 出售
- `for_rent`: 出租
- `for_sale_rent`: 租售
- `suspended`: 暂缓
- `sold_elsewhere`: 他售
- `rented_elsewhere`: 他租
- `sold`: 成交
- `unlisted`: 未挂牌
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `unlisted` | `for_sale`/`for_rent` | 房源挂牌 | 经纪人 | 新增 `listing_histories` | 是 |
| `Listed` | `suspended` | 业主通知暂缓 | 经纪人 | 写跟进日志 | 是 |
| `Listed` | `sold` | 本司录入成交 | 经纪人 | 自动结束挂牌历史 | 否 |
| `Listed` | `sold_elsewhere`/`rented_elsewhere` | 核实他司已成交 | 经纪人 | | 否 |
| `Listed` | `unlisted` | 强制下架 | 管理员 | | 是 |
**来源章节**: `DATA_MODEL_PROPERTY.md` §3
---
## 5. 客户/房源等级 (Grade)
**实体**: `client.clients` / `property.properties` | **字段**: `grade`
**参考**: `DATA_MODEL_CLIENT.md` §3.1 | `DATA_MODEL_PROPERTY.md` §4.1
### 状态枚举值
(domain: `client.grade` / `property.grade`)
- `A`: A (急迫)
- `B`: B (较强)
- `C`: C (一般)
- `D`: D (较弱)
- `E`: E (暂不关注) - 仅客源
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
| ---- | ------------------- | ------- | ---- | ----------------------------- | ---- |
| 任意状态 | `A`/`B`/`C`/`D`/`E` | 手动调整等级 | 经纪人 | 写 `status_log` / `follow_log` | 是 |
**来源章节**: `DATA_MODEL_CLIENT.md` §4, `DATA_MODEL_PROPERTY.md` §3
---
## 6. 账号状态机 (Account)
**实体**: `login.user_accounts` | **字段**: `status`
**参考**: `DATA_MODEL_LOGIN.md` §5.1
```mermaid
stateDiagram-v2
[*] --> active: 创建(初始密码)
active --> locked: 密码错误≥5次
locked --> active: 30min到期/手动解锁
active --> disabled: 离职/手动停用
disabled --> active: 复职/手动恢复
```
### 状态枚举值
(domain: `login.user_account.status`)
- `active`: 启用
- `disabled`: 停用
- `locked`: 锁定
### 状态迁移表
| 当前状态 | 下一状态 | 触发动作/事件 | 触发角色 | 副作用 | 是否可逆 |
|---|---|---|---|---|---|
| `active` | `locked` | 密码连续错误5次 | System | 设置 `locked_until` | 是 |
| `locked` | `active` | 时间到期/管理员解锁 | System/Admin | | 是 |
| `active` | `disabled` | 员工离职/手动停用 | Admin/System | 踢出所有 Session | 是 |
**来源章节**: `DATA_MODEL_LOGIN.md` §5.1
---
## 7. 升级事件状态机 (Upgrade Event)
**实体**: `public.upgrade_events` | **字段**: `status`
**参考**: `DATA_MODEL_PUBLIC.md` §4.2
```mermaid
stateDiagram-v2
[*] --> draft
draft --> pre_check: 提交
pre_check --> pre_backup: 健康检查通过
pre_backup --> batch_running: 备份完成
batch_running --> batch_done: 批次租户成功
batch_done --> batch_running: 下一批次
batch_done --> succeeded: 全部完成
batch_running --> halted: 失败/门控拦截
halted --> batch_running: 人工继续
halted --> rollback_running: 人工回滚
rollback_running --> rolled_back: 回滚完成
batch_running --> failed: 严重故障
```
### 状态枚举值
(domain: `public.upgrade_event.status`)
- `draft`: 草稿
- `pre_check`: 预检查
- `pre_backup`: 预备份
- `batch_running`: 批次执行中
- `batch_done`: 批次完成
- `halted`: 已暂停
- `succeeded`: 已成功
- `failed`: 失败
- `rollback_running`: 回滚中
- `rolled_back`: 已回滚
**来源章节**: `DATA_MODEL_PUBLIC.md` §4.2
---
## 8. 其他辅助状态
### 8.1 备份/导出任务 (Backup/Export)
**字段**: `status` | **参考**: `ENUMS.md` §2.3
- `pending` -> `in_progress` -> `success` / `failed`
### 8.2 客户端发布 (Client Release)
**字段**: `status` | **参考**: `ENUMS.md` §2.4
- `draft` -> `published` -> `archived`
- **约束**: 同平台架构下仅允许一个 `published`
### 8.3 员工状态 (Staff)
**字段**: `status` | **参考**: `DATA_MODEL_ORG.md` §4
- `probation` -> `active` -> `resigned`
- `active` <-> `frozen`
### 8.4 审批流 (Approval)
**实体**: `number_holder_approvals` | **字段**: `status`
- `pending` -> `approved` / `rejected`
---
## X. 状态机一致性校验清单
1. **枚举同步**: 状态字段值是否在 `ENUMS.md` 中定义且拼写一致?
2. **日志完备**: 每次 `status` 变更是否伴随对应的 `status_log` 插入动作?
3. **并发控制**: 状态变更是否包含 `version` 乐观锁检查?
4. **终态拦截**: 业务逻辑是否显式禁止从终态(如 `sold`, `deleted`, `rolled_back`)向外迁移?
5. **副作用触发**: 状态变更相关的异步任务(如转公客通知、缓存清理)是否已在 Service 层注册?
---
## 待澄清问题
1. **Client Invalid 恢复路径**: `DATA_MODEL_CLIENT.md` §4 提到“需经理审批后可恢复”,但未定义恢复后的目标状态(是恢复到 `suspended` 还是之前的活跃态?)。
- *涉及文件*: `DATA_MODEL_CLIENT.md`
2. **Property Unlisted 属性**: 房源在 `unlisted` 状态下,其 `attribute` (公/私盘) 是否有意义?若从 `private` 挂牌变为 `unlisted`,再次挂牌时是否应默认保留 `private` 属性?
- *涉及文件*: `DATA_MODEL_PROPERTY.md`
3. **Tenant Deleted 物理性**: `DATA_MODEL_PUBLIC.md` §4.1 提到“硬删除直接到 deleted”但在 §2.1 又说“硬删除直接物理删除行”。需要明确 `deleted` 状态是代表软删除的终点还是物理删除前的标记。
- *涉及文件*: `DATA_MODEL_PUBLIC.md`
4. **Staff Resigned 恢复**: 离职员工复职(`rejoin`)后,其 `status` 是回到 `probation` 还是 `active`
- *涉及文件*: `DATA_MODEL_ORG.md`

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1,645 @@
<mxfile host="drawio.ishenwei.online" agent="OpenCode">
<diagram name="Fonrey ER Diagram" id="fonrey-er-v1">
<mxGraphModel dx="9516" dy="5600" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3300" pageHeight="2340" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="region-org" parent="1" style="swimlane;startSize=30;fillColor=#0d3349;strokeColor=#22d3ee;fontColor=#22d3ee;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" value="ORG / HR" vertex="1">
<mxGeometry height="760" width="380" x="-860" y="60" as="geometry">
<mxRectangle height="30" width="100" x="40" y="60" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="org-units" parent="region-org" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;org_units&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;parent_id: uuid (FK → self)&#xa;type: varchar(20)&#xa;name: varchar(100)&#xa;path: varchar(500) [物化路径]&#xa;depth: smallint&#xa;sort_order: int&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;deleted_at: timestamptz" vertex="1">
<mxGeometry height="185" width="280" x="30" y="60" as="geometry" />
</mxCell>
<mxCell id="staff" parent="region-org" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;staff&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;FK org_unit_id → org_units&#xa;name: varchar(50)&#xa;phone_enc: text [AES-256-GCM]&#xa;phone_hash: varchar(64) [SHA-256]&#xa;id_no_enc: text [AES]&#xa;user_id: uuid [FK → auth_user]&#xa;entry_date: date&#xa;status: active/resigned/...&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;deleted_at: timestamptz" vertex="1">
<mxGeometry height="215" width="280" x="30" y="280" as="geometry" />
</mxCell>
<mxCell id="e-org-self" edge="1" parent="region-org" source="org-units" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="org-units">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="340" y="153" />
<mxPoint x="340" y="108" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-org-self-lbl" connectable="0" parent="e-org-self" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" value="自引用 parent_id" vertex="1">
<mxGeometry relative="1" x="0.1" as="geometry" />
</mxCell>
<mxCell id="e-org-staff" edge="1" parent="region-org" source="org-units" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="staff">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-org-staff-lbl" connectable="0" parent="e-org-staff" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="region-complex" parent="1" style="swimlane;startSize=30;fillColor=#063b2f;strokeColor=#34d399;fontColor=#34d399;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" value="REGION &amp; COMPLEX" vertex="1">
<mxGeometry height="1830" width="1200" x="-440" y="60" as="geometry" />
</mxCell>
<mxCell id="districts" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;districts&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;city: varchar(50)&#xa;name: varchar(50)&#xa;short_name: varchar(20)&#xa;sort_order: int&#xa;is_active: bool&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="150" width="280" x="50" y="60" as="geometry" />
</mxCell>
<mxCell id="business-areas" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;business_areas&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK district_id → districts&#xa;name: varchar(100)&#xa;latitude: numeric(10,7)&#xa;longitude: numeric(10,7)&#xa;sort_order: int&#xa;is_active: bool" vertex="1">
<mxGeometry height="155" width="280" x="50" y="265" as="geometry" />
</mxCell>
<mxCell id="schools" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;schools&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK district_id → districts&#xa;name: varchar(100)&#xa;type: primary/middle/high/k9/k12&#xa;nature: public/private/international&#xa;level: normal/key/top&#xa;is_active: bool" vertex="1">
<mxGeometry height="155" width="290" x="490" y="60" as="geometry" />
</mxCell>
<mxCell id="complexes" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;complexes&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK district_id → districts&#xa;🔗 FK created_by → staff&#xa;name: varchar(200) [⚠ 不可直接修改]&#xa;address: varchar(500) [只读]&#xa;address_summary: varchar(100)&#xa;latitude: numeric(10,7)&#xa;longitude: numeric(10,7)&#xa;property_usage_types: varchar[]&#xa;building_structure: varchar(30)&#xa;building_type: slab/tower/slab_tower&#xa;land_use_years: varchar(30)&#xa;built_years: smallint[]&#xa;total_units: int&#xa;total_households: int&#xa;total_floor_area: numeric(12,2)&#xa;plot_area: numeric(12,2)&#xa;plot_ratio: numeric(5,2)&#xa;green_rate: numeric(5,2)&#xa;developer: varchar(200)&#xa;property_company: varchar(200)&#xa;property_fee: numeric(8,2)&#xa;property_phone: varchar(30)&#xa;parking_total: int&#xa;parking_underground: int&#xa;parking_ratio: varchar(20)&#xa;water_type: civil/commercial&#xa;electricity_type: civil/commercial&#xa;has_central_heating: bool&#xa;has_gas: bool&#xa;lock_building: bool&#xa;lock_room: bool&#xa;lock_info: bool&#xa;lock_standard_room: bool&#xa;search_vector: tsvector&#xa;remarks: text&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;updated_at: timestamptz&#xa;deleted_at: timestamptz" vertex="1">
<mxGeometry height="570" width="340" x="30" y="660" as="geometry" />
</mxCell>
<mxCell id="complex-aliases" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;complex_aliases&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK complex_id → complexes&#xa;alias: varchar(200)&#xa;is_system: bool [系统别名只读]&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff" vertex="1">
<mxGeometry height="130" width="290" x="475" y="450" as="geometry" />
</mxCell>
<mxCell id="complex-biz-areas" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" value="&lt;b&gt;complex_business_areas&lt;/b&gt; [N:M join]&#xa;&lt;hr/&gt;&#xa;🔗 FK complex_id → complexes&#xa;🔗 FK business_area_id → business_areas&#xa;is_primary: bool [UNIQUE where TRUE]" vertex="1">
<mxGeometry height="110" width="380" x="30" y="480" as="geometry" />
</mxCell>
<mxCell id="complex-schools" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" value="&lt;b&gt;complex_schools&lt;/b&gt; [N:M join]&#xa;&lt;hr/&gt;&#xa;🔗 FK complex_id → complexes&#xa;🔗 FK school_id → schools&#xa;zone_type: guaranteed/reference/lottery" vertex="1">
<mxGeometry height="110" width="310" x="480" y="270" as="geometry" />
</mxCell>
<mxCell id="complex-price-trends" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;complex_price_trends&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK complex_id → complexes&#xa;record_month: date [存月份1日]&#xa;avg_sale_price: numeric(12,2)&#xa;avg_unit_price: numeric(10,2)&#xa;transaction_count: int&#xa;listing_count: int&#xa;created_at: timestamptz&#xa;UNIQUE(complex_id, record_month)" vertex="1">
<mxGeometry height="185" width="380" x="500" y="980" as="geometry" />
</mxCell>
<mxCell id="complex-photos" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;complex_photos&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK complex_id → complexes&#xa;category: complex/layout/vr/other&#xa;file_key: text [R2/S3]&#xa;thumbnail_key: text&#xa;file_name: varchar(255)&#xa;file_size: int&#xa;width, height: int&#xa;is_cover: bool [UNIQUE where TRUE]&#xa;sort_order: smallint&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff" vertex="1">
<mxGeometry height="205" width="300" x="470" y="650" as="geometry" />
</mxCell>
<mxCell id="e-dist-biz" edge="1" parent="region-complex" source="districts" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="business-areas">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dist-biz-lbl" connectable="0" parent="e-dist-biz" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dist-school" edge="1" parent="region-complex" source="districts" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="schools">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dist-school-lbl" connectable="0" parent="e-dist-school" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dist-complex" edge="1" parent="region-complex" source="districts" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="complexes">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dist-complex-lbl" connectable="0" parent="e-dist-complex" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-biz-join" edge="1" parent="region-complex" source="business-areas" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complex-biz-areas">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-join-complex" edge="1" parent="region-complex" source="complex-biz-areas" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complexes">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-school-join" edge="1" parent="region-complex" source="schools" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complex-schools">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-school-join2" edge="1" parent="region-complex" source="complex-schools" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complexes">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="420" y="325" />
<mxPoint x="420" y="690" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-complex-alias" edge="1" parent="region-complex" source="complexes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="complex-aliases">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="820" y="945" />
<mxPoint x="820" y="515" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-complex-alias-lbl" connectable="0" parent="e-complex-alias" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-photos" edge="1" parent="region-complex" source="complexes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="complex-photos">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-photos-lbl" connectable="0" parent="e-complex-photos" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-trend" edge="1" parent="region-complex" source="complexes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="complex-price-trends">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="400" y="1072" />
<mxPoint x="400" y="1072" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-complex-trend-lbl" connectable="0" parent="e-complex-trend" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="room-units" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;room_units&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK building_id → buildings&#xa;floor: smallint&#xa;floor_name: varchar(20)&#xa;room_no: varchar(30)&#xa;display_no: varchar(50)&#xa;is_standard: bool&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;updated_at: timestamptz&#xa;UNIQUE(building_id, floor, room_no)" vertex="1">
<mxGeometry height="200" width="310" x="860" y="1402.5" as="geometry" />
</mxCell>
<mxCell id="metro-lines" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;metro_lines&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;city: varchar(50)&#xa;name: varchar(50)&#xa;color: varchar(7) [HEX]&#xa;sort_order: int&#xa;is_active: bool" vertex="1">
<mxGeometry height="130" width="260" x="10" y="1680" as="geometry" />
</mxCell>
<mxCell id="metro-stations" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;metro_stations&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK metro_line_id → metro_lines&#xa;name: varchar(50)&#xa;latitude: numeric(10,7)&#xa;longitude: numeric(10,7)&#xa;sort_order: int&#xa;is_active: bool" vertex="1">
<mxGeometry height="150" width="280" x="300" y="1670" as="geometry" />
</mxCell>
<mxCell id="e-metro-line-station" edge="1" parent="region-complex" source="metro-lines" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="metro-stations">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-metro-lbl" connectable="0" parent="e-metro-line-station" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="complex-metro-stations" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" value="&lt;b&gt;complex_metro_stations&lt;/b&gt; [N:M join]&#xa;&lt;hr/&gt;&#xa;🔗 FK complex_id → complexes&#xa;🔗 FK station_id → metro_stations&#xa;distance_meters: int [步行距离]" vertex="1">
<mxGeometry height="130" width="330" x="10" y="1520" as="geometry" />
</mxCell>
<mxCell id="e-metro-join1" edge="1" parent="region-complex" source="metro-stations" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complex-metro-stations">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-metro-join2" edge="1" parent="region-complex" source="complex-metro-stations" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" target="complexes">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="buildings" parent="region-complex" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;buildings&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK complex_id → complexes&#xa;🔗 FK school_id → schools [楼栋级学区]&#xa;name: varchar(50)&#xa;is_standard: bool&#xa;property_usage_type: varchar(20)&#xa;built_year: smallint&#xa;total_floors: smallint&#xa;land_use_years: varchar(30)&#xa;has_elevator: bool&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff" vertex="1">
<mxGeometry height="225" width="310" x="500" y="1390" as="geometry" />
</mxCell>
<mxCell id="e-bldg-room" edge="1" parent="region-complex" source="buildings" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="room-units">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-bldg-room-lbl" connectable="0" parent="e-bldg-room" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-bldg" edge="1" parent="region-complex" source="complexes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="buildings">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="270" y="1502" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-complex-bldg-lbl" connectable="0" parent="e-complex-bldg" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="region-property" parent="1" style="swimlane;startSize=30;fillColor=#2d1a5e;strokeColor=#a78bfa;fontColor=#a78bfa;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" value="PROPERTY" vertex="1">
<mxGeometry height="1700" width="1380" x="800" y="60" as="geometry" />
</mxCell>
<mxCell id="properties" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;properties&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK complex_id → complexes&#xa;🔗 FK building_id → buildings&#xa;🔗 FK room_unit_id → room_units&#xa;🔗 FK agent_id → staff&#xa;listing_type: sale/rent/both&#xa;status: varchar(20)&#xa;sale_price: numeric(12,2) [万元]&#xa;rent_price: numeric(10,2) [元/月]&#xa;area: numeric(8,2) [m²]&#xa;floor: smallint&#xa;total_floors: smallint&#xa;bedroom: smallint&#xa;living_room: smallint&#xa;bathroom: smallint&#xa;orientation: varchar(30)&#xa;decoration: varchar(20)&#xa;has_elevator: bool&#xa;built_year: smallint&#xa;ownership_years: varchar(20)&#xa;is_exclusive: bool [独家委托]&#xa;completeness_score: int&#xa;search_vector: tsvector&#xa;source: varchar(30)&#xa;remarks: text&#xa;created_at: timestamptz&#xa;updated_at: timestamptz&#xa;deleted_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;🔗 FK updated_by → staff&#xa;&lt;i&gt;[89,000+ rows · 复合索引 · 分区预留]&lt;/i&gt;" vertex="1">
<mxGeometry height="560" width="380" x="30" y="60" as="geometry" />
</mxCell>
<mxCell id="property-contacts" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_contacts&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;name: varchar(50)&#xa;phone_enc: text [AES-256-GCM]&#xa;phone_hash: varchar(64) [SHA-256]&#xa;role: owner/agent/tenant&#xa;is_primary: bool&#xa;created_at: timestamptz&#xa;deleted_at: timestamptz" vertex="1">
<mxGeometry height="170" width="310" x="460" y="350" as="geometry" />
</mxCell>
<mxCell id="property-follow-logs" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_follow_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;🔗 FK staff_id → staff&#xa;log_type: call/visit/price_change/note/...&#xa;content: text&#xa;phone_no_viewed: bool [敏感操作]&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;⚠ NO DELETE — append-only audit log" vertex="1">
<mxGeometry height="185" width="380" x="980" y="50" as="geometry" />
</mxCell>
<mxCell id="listing-histories" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;listing_histories&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;listed_at: timestamptz&#xa;delisted_at: timestamptz&#xa;list_price: numeric(12,2)&#xa;reason: varchar(50)&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="155" width="310" x="980" y="250" as="geometry" />
</mxCell>
<mxCell id="property-photos" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_photos&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;category: listing/vr/layout/other&#xa;file_key: text [R2/S3]&#xa;thumbnail_key: text&#xa;is_cover: bool&#xa;sort_order: smallint&#xa;width, height: int&#xa;file_size: int&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff" vertex="1">
<mxGeometry height="205" width="310" x="30" y="740" as="geometry" />
</mxCell>
<mxCell id="property-keys" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_keys&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;🔗 FK holder_id → staff&#xa;key_no: varchar(50)&#xa;status: held/returned&#xa;taken_at: timestamptz&#xa;returned_at: timestamptz&#xa;notes: text" vertex="1">
<mxGeometry height="165" width="310" x="980" y="430" as="geometry" />
</mxCell>
<mxCell id="property-commissions" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_commissions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;commission_type: exclusive/open&#xa;rate: numeric(5,4)&#xa;amount: numeric(12,2)&#xa;start_date: date&#xa;end_date: date&#xa;signed_at: timestamptz&#xa;document_key: text&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="185" width="330" x="980" y="620" as="geometry" />
</mxCell>
<mxCell id="property-inspections" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_inspections&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties&#xa;🔗 FK staff_id → staff&#xa;inspected_at: timestamptz&#xa;status: pending/done/cancelled&#xa;notes: text&#xa;attachments: jsonb&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="165" width="320" x="450" y="575" as="geometry" />
</mxCell>
<mxCell id="property-marketing" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_marketing&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties [UNIQUE 1:1]&#xa;title: varchar(200)&#xa;highlights: text[]&#xa;description: text&#xa;tags: varchar[]&#xa;platforms: jsonb&#xa;published_at: timestamptz&#xa;updated_at: timestamptz" vertex="1">
<mxGeometry height="175" width="340" x="980" y="870" as="geometry" />
</mxCell>
<mxCell id="property-certificates" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;property_certificates&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties [UNIQUE 1:1]&#xa;cert_no: varchar(50)&#xa;owner_name: varchar(100)&#xa;ownership_type: varchar(30)&#xa;area_registered: numeric(8,2)&#xa;issue_date: date&#xa;document_key: text" vertex="1">
<mxGeometry height="165" width="330" x="450" y="790" as="geometry" />
</mxCell>
<mxCell id="completeness-scores" parent="region-property" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;completeness_scores&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK property_id → properties [UNIQUE 1:1]&#xa;score: int [0-100]&#xa;missing_fields: text[]&#xa;calculated_at: timestamptz&#xa;version: int" vertex="1">
<mxGeometry height="135" width="310" x="980" y="1090" as="geometry" />
</mxCell>
<mxCell id="e-prop-contact" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-contacts">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="435" y="280" />
<mxPoint x="435" y="435" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-contact-lbl" connectable="0" parent="e-prop-contact" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-follow" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-follow-logs">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="840" y="80" />
<mxPoint x="840" y="80" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-follow-lbl" connectable="0" parent="e-prop-follow" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-listing" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="listing-histories">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="960" y="110" />
<mxPoint x="960" y="328" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-listing-lbl" connectable="0" parent="e-prop-listing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-photos" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-photos">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="50" y="672" />
<mxPoint x="50" y="672" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-photos-lbl" connectable="0" parent="e-prop-photos" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-keys" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-keys">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="920" y="140" />
<mxPoint x="920" y="512" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-keys-lbl" connectable="0" parent="e-prop-keys" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-comm" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-commissions">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="880" y="180" />
<mxPoint x="880" y="712" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-comm-lbl" connectable="0" parent="e-prop-comm" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-insp" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="property-inspections">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="440" y="500" />
<mxPoint x="440" y="680" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-insp-lbl" connectable="0" parent="e-prop-insp" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-marketing" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" target="property-marketing">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="830" y="220" />
<mxPoint x="830" y="958" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-marketing-lbl" connectable="0" parent="e-prop-marketing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:1" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-cert" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" target="property-certificates">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="670" />
<mxPoint x="410" y="670" />
<mxPoint x="410" y="872" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-cert-lbl" connectable="0" parent="e-prop-cert" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:1" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-score" edge="1" parent="region-property" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" target="completeness-scores">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="790" y="260" />
<mxPoint x="790" y="1158" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-score-lbl" connectable="0" parent="e-prop-score" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:1" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="region-client" parent="1" style="swimlane;startSize=30;fillColor=#3d1f06;strokeColor=#fbbf24;fontColor=#fbbf24;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" value="CLIENT" vertex="1">
<mxGeometry height="1380" width="1060" x="2280" y="60" as="geometry" />
</mxCell>
<mxCell id="clients" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;clients&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK agent_id → staff&#xa;client_type: private/public/closed&#xa;status: active/inactive/converted&#xa;name: varchar(50)&#xa;phone_enc: text [AES-256-GCM]&#xa;phone_hash: varchar(64) [SHA-256]&#xa;budget_min/max: numeric&#xa;activity_level: 1-5 [Celery每日计算]&#xa;is_protected: bool [防自动转公客]&#xa;transfer_to_public_type: auto/manual&#xa;last_follow_at: timestamptz&#xa;source: varchar(30)&#xa;remarks: text&#xa;created_at: timestamptz&#xa;deleted_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;&lt;i&gt;[私客/公客/成交客 三态状态机]&lt;/i&gt;" vertex="1">
<mxGeometry height="360" width="370" x="30" y="60" as="geometry" />
</mxCell>
<mxCell id="client-requirements" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_requirements&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;req_type: second_hand/new/rent&#xa;district_ids: uuid[]&#xa;business_area_ids: uuid[]&#xa;price_min: numeric&#xa;price_max: numeric&#xa;area_min: numeric&#xa;area_max: numeric&#xa;bedrooms: int[]&#xa;school_ids: uuid[]&#xa;has_elevator: bool&#xa;is_active: bool&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="260" width="350" x="40" y="507.5" as="geometry" />
</mxCell>
<mxCell id="client-follow-logs" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_follow_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;🔗 FK staff_id → staff&#xa;log_type: call/visit/match/note/status_change&#xa;content: text&#xa;next_follow_date: date&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;⚠ NO DELETE — append-only audit log" vertex="1">
<mxGeometry height="200" width="380" x="660" y="50" as="geometry" />
</mxCell>
<mxCell id="client-viewings" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_viewings&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;🔗 FK property_id → properties&#xa;🔗 FK agent_id → staff&#xa;viewed_at: timestamptz&#xa;feedback: text&#xa;rating: smallint [1-5]&#xa;status: planned/done/cancelled&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="195" width="360" x="660" y="310" as="geometry" />
</mxCell>
<mxCell id="client-matches" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_property_matches&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;🔗 FK property_id → properties&#xa;🔗 FK agent_id → staff&#xa;match_type: system/manual&#xa;score: numeric(5,2)&#xa;status: pending/sent/viewed/dismissed&#xa;sent_at: timestamptz&#xa;viewed_at: timestamptz&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="205" width="380" x="30" y="800" as="geometry" />
</mxCell>
<mxCell id="client-status-logs" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_status_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;from_status: varchar(20)&#xa;to_status: varchar(20)&#xa;transfer_type: auto/manual&#xa;reason: text&#xa;created_at: timestamptz&#xa;🔗 FK created_by → staff&#xa;⚠ NO DELETE — append-only audit log" vertex="1">
<mxGeometry height="195" width="370" x="655" y="540" as="geometry" />
</mxCell>
<mxCell id="client-fav-folders" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_favorite_folders&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK client_id → clients&#xa;name: varchar(100)&#xa;sort_order: int&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="130" width="300" x="655" y="980" as="geometry" />
</mxCell>
<mxCell id="client-folder-items" parent="region-client" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;client_folder_items&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK folder_id → client_favorite_folders&#xa;🔗 FK property_id → properties&#xa;sort_order: int&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="130" width="320" x="655" y="780" as="geometry" />
</mxCell>
<mxCell id="e-client-req" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-requirements">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="460" />
<mxPoint x="290" y="460" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-req-lbl" connectable="0" parent="e-client-req" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-client-follow" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-follow-logs">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="600" y="80" />
<mxPoint x="600" y="80" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-follow-lbl" connectable="0" parent="e-client-follow" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-client-viewing" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-viewings">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="640" y="110" />
<mxPoint x="640" y="408" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-viewing-lbl" connectable="0" parent="e-client-viewing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-client-match" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-matches">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="440" y="300" />
<mxPoint x="440" y="902" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-match-lbl" connectable="0" parent="e-client-match" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-client-statuslog" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-status-logs">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="620" y="140" />
<mxPoint x="620" y="638" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-statuslog-lbl" connectable="0" parent="e-client-statuslog" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-client-fav" edge="1" parent="region-client" source="clients" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-fav-folders">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="530" y="170" />
<mxPoint x="530" y="1045" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-client-fav-lbl" connectable="0" parent="e-client-fav" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-fav-items" edge="1" parent="region-client" source="client-fav-folders" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-folder-items">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-fav-items-lbl" connectable="0" parent="e-fav-items" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-prop" edge="1" parent="1" source="complexes" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=0;endArrow=ERmany;startArrow=ERone;fontSize=9;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" target="properties">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-complex-prop-lbl" connectable="0" parent="e-complex-prop" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N complex_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-bldg-prop" edge="1" parent="1" source="buildings" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="properties">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="215" y="1390" />
<mxPoint x="1020" y="1390" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-bldg-prop-lbl" connectable="0" parent="e-bldg-prop" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N building_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-room-prop" edge="1" parent="1" source="room-units" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="properties">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="575" y="1710" />
<mxPoint x="1060" y="1710" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-room-prop-lbl" connectable="0" parent="e-room-prop" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" value="1:N room_unit_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-staff-prop" edge="1" parent="1" source="staff" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="properties">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-530" y="448" />
<mxPoint x="-530" y="300" />
<mxPoint x="380" y="300" />
<mxPoint x="380" y="400" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-staff-prop-lbl" connectable="0" parent="e-staff-prop" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" value="agent_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-staff-client" edge="1" parent="1" source="staff" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="clients">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="700" y="490" />
<mxPoint x="700" y="300" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-staff-client-lbl" connectable="0" parent="e-staff-client" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" value="agent_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-viewing" edge="1" parent="1" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-viewings">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="2340" y="530" />
<mxPoint x="2340" y="530" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-viewing-lbl" connectable="0" parent="e-prop-viewing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" value="property_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-match" edge="1" parent="1" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-matches">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="2690" y="400" />
<mxPoint x="2690" y="990" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-match-lbl" connectable="0" parent="e-prop-match" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" value="property_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-prop-folder" edge="1" parent="1" source="properties" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="client-folder-items">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="2800" y="310" />
<mxPoint x="2800" y="905" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-prop-folder-lbl" connectable="0" parent="e-prop-folder" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" value="property_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="region-public" parent="1" style="swimlane;startSize=36;fillColor=#0c1a2e;strokeColor=#7dd3fc;fontColor=#7dd3fc;fontSize=13;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=2;" value="PUBLIC SCHEMA平台运营层" vertex="1">
<mxGeometry height="560" width="3400" x="-860" y="-1200" as="geometry" />
</mxCell>
<mxCell id="pub-tenants" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.tenants&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;schema_name: varchar(63) [UNIQUE, immutable]&#xa;name: varchar(255)&#xa;short_name: varchar(100)&#xa;contact_name / contact_email&#xa;region: varchar(100)&#xa;plan: basic/professional/enterprise&#xa;status: creating/active/suspended/&#xa; pending_delete/deleted/failed&#xa;suspended_until: timestamptz&#xa;suspended_reason: varchar(50)&#xa;deleted_at: timestamptz&#xa;paid_until: date&#xa;on_trial: bool&#xa;is_canary: bool&#xa;created_at / updated_at: timestamptz&#xa;created_by: uuid&#xa;extra: jsonb" vertex="1">
<mxGeometry height="310" width="290" x="30" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-domains" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.domains&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;domain: varchar(253) [UNIQUE]&#xa;is_primary: bool [UNIQUE partial]&#xa;created_at: timestamptz&#xa;⚠ domain 创建后不可修改" vertex="1">
<mxGeometry height="135" width="250" x="340" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-tenant-status-logs" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.tenant_status_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;from_status: varchar(20) [NULL=初始]&#xa;to_status: varchar(20)&#xa;reason: text&#xa;operator_id: uuid [NULL=Celery]&#xa;operator_name: varchar(100) [快照]&#xa;created_at: timestamptz&#xa;⚠ append-only — NO UPDATE/DELETE" vertex="1">
<mxGeometry height="175" width="270" x="340" y="200" as="geometry" />
</mxCell>
<mxCell id="pub-admins" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.platform_admins&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;username: varchar(150) [UNIQUE]&#xa;email: varchar(254) [UNIQUE]&#xa;display_name: varchar(100)&#xa;password_hash: varchar(255)&#xa;role: super_admin/ops_operator/&#xa; read_only_auditor&#xa;is_active: bool&#xa;mfa_enabled: bool [TOTP 确认后→TRUE]&#xa;last_login_at: timestamptz&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="225" width="270" x="640" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-mfa" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.admin_mfa_devices&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;device_name: varchar(100)&#xa;totp_secret: varchar(255) [加密]&#xa;is_confirmed: bool&#xa;created_at: timestamptz&#xa;last_used_at: timestamptz" vertex="1">
<mxGeometry height="150" width="250" x="930" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-sessions" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.admin_sessions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK admin_id → platform_admins&#xa;session_token: varchar(255) [UNIQUE]&#xa;ip_address: inet&#xa;user_agent: text&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;expires_at: timestamptz [30min rolling]&#xa;revoked_at: timestamptz&#xa;🔗 FK revoked_by → platform_admins" vertex="1">
<mxGeometry height="190" width="255" x="930" y="220" as="geometry" />
</mxCell>
<mxCell id="pub-ip" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.ip_whitelist&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;cidr: cidr [如 203.0.113.0/24]&#xa;label: varchar(100)&#xa;is_active: bool&#xa;created_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="135" width="240" x="640" y="295" as="geometry" />
</mxCell>
<mxCell id="pub-audit" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.platform_audit_logs&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;operator_id: uuid [NULL=系统]&#xa;operator_name: varchar(100) [快照]&#xa;action_type: varchar(50)&#xa;target_type: varchar(30)&#xa;target_id / target_name: varchar&#xa;payload_summary: text&#xa;result: SUCCESS/FAILED&#xa;error_message: text&#xa;ip_address: inet&#xa;created_at: timestamptz&#xa;⚠ append-only — 建议月度 RANGE 分区" vertex="1">
<mxGeometry height="225" width="265" x="1210" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-bk-schedules" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.backup_schedules&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants [NULL=全局]&#xa;frequency: hourly/daily/weekly&#xa;scheduled_time: time [UTC]&#xa;retention_count: int&#xa;storage_target: local/s3/r2/gcs&#xa;is_active: bool&#xa;created_at / updated_at: timestamptz&#xa;🔗 FK created_by → platform_admins&#xa;UNIQUE(tenant_id)" vertex="1">
<mxGeometry height="195" width="265" x="1510" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-bk-records" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.backup_records&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;trigger_type: auto/manual/&#xa; pre_upgrade/pre_restore&#xa;status: pending/in_progress/&#xa; success/failed&#xa;storage_target / storage_path: text&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;error_message: text&#xa;🔗 FK triggered_by → platform_admins&#xa;upgrade_event_id: uuid&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="245" width="265" x="1510" y="270" as="geometry" />
</mxCell>
<mxCell id="pub-exports" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.export_tasks&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK tenant_id → tenants&#xa;🔗 FK requested_by → platform_admins&#xa;modules: text[]&#xa;format: csv/json/sql_dump&#xa;status: pending/in_progress/done/failed&#xa;storage_path / download_url: text&#xa;expires_at: timestamptz [24h]&#xa;size_bytes: bigint&#xa;started_at / completed_at&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="215" width="265" x="1210" y="300" as="geometry" />
</mxCell>
<mxCell id="pub-versions" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.system_versions&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;version_number: varchar(50) [UNIQUE]&#xa;release_notes: text&#xa;artifact_url: text&#xa;status: current/previous/archived&#xa; [UNIQUE partial: status=&#39;current&#39;]&#xa;released_at: timestamptz&#xa;🔗 FK created_by → platform_admins" vertex="1">
<mxGeometry height="165" width="265" x="1810" y="50" as="geometry" />
</mxCell>
<mxCell id="pub-upgrades" parent="region-public" style="text;html=1;strokeColor=#7dd3fc;fillColor=#0c1a2e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=10;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" value="&lt;b&gt;public.upgrade_events&lt;/b&gt;&#xa;&lt;hr/&gt;&#xa;🔑 PK id: uuid&#xa;🔗 FK from_version_id → system_versions&#xa;🔗 FK to_version_id → system_versions&#xa;event_type: upgrade/rollback&#xa;strategy: full/canary&#xa;status: pending/health_check/&#xa; in_progress/success/failed/rolled_back&#xa;tenant_progress: jsonb [{tenant,status,...}]&#xa;rollback_reason: text&#xa;incident_report: text&#xa;started_at / completed_at&#xa;🔗 FK initiated_by → platform_admins&#xa;created_at: timestamptz" vertex="1">
<mxGeometry height="255" width="280" x="1810" y="240" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-domain" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-domains">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-domain-lbl" connectable="0" parent="pe-tenant-domain" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-statuslog" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-tenant-status-logs">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="295" />
<mxPoint x="320" y="288" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-statuslog-lbl" connectable="0" parent="pe-tenant-statuslog" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N append-only" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-admin-mfa" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-mfa">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-admin-mfa-lbl" connectable="0" parent="pe-admin-mfa" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-admin-session" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-sessions">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="905" y="235" />
<mxPoint x="905" y="315" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-admin-session-lbl" connectable="0" parent="pe-admin-session" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-admin-ip" edge="1" parent="region-public" source="pub-admins" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-ip">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="755" y="280" />
<mxPoint x="755" y="363" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-admin-ip-lbl" connectable="0" parent="pe-admin-ip" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="created_by" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-bksched" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-schedules">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="430" />
<mxPoint x="1642" y="430" />
<mxPoint x="1642" y="248" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-bksched-lbl" connectable="0" parent="pe-tenant-bksched" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N (NULL=全局)" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-bkrec" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-records">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="480" />
<mxPoint x="1642" y="480" />
<mxPoint x="1642" y="395" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-bkrec-lbl" connectable="0" parent="pe-tenant-bkrec" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-tenant-export" edge="1" parent="region-public" source="pub-tenants" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-exports">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="520" />
<mxPoint x="1342" y="520" />
<mxPoint x="1342" y="408" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-tenant-export-lbl" connectable="0" parent="pe-tenant-export" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="1:N" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-ver-upgrade" edge="1" parent="region-public" source="pub-versions" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-upgrades">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-ver-upgrade-lbl" connectable="0" parent="pe-ver-upgrade" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="from/to version" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="pe-upgrade-bkrec" edge="1" parent="region-public" source="pub-upgrades" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#7dd3fc;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" target="pub-bk-records">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1808" y="392" />
<mxPoint x="1775" y="392" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="pe-upgrade-bkrec-lbl" connectable="0" parent="pe-upgrade-bkrec" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#7dd3fc;" value="upgrade_event_id" vertex="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,774 @@
<mxfile host="app.diagrams.net" modified="2026-04-24" agent="OpenCode" version="21.0.0">
<diagram name="Fonrey ER Diagram" id="fonrey-er-v1">
<mxGraphModel dx="1422" dy="762" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3300" pageHeight="2340" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- ═══════════════════════════════════════════════════ -->
<!-- SWIM LANE BACKGROUNDS -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- ORG / HR region -->
<mxCell id="region-org" value="ORG / HR" style="swimlane;startSize=30;fillColor=#0d3349;strokeColor=#22d3ee;fontColor=#22d3ee;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="40" y="60" width="340" height="760" as="geometry"/>
</mxCell>
<!-- REGION &amp; COMPLEX region -->
<mxCell id="region-complex" value="REGION &amp; COMPLEX" style="swimlane;startSize=30;fillColor=#063b2f;strokeColor=#34d399;fontColor=#34d399;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="420" y="60" width="820" height="1380" as="geometry"/>
</mxCell>
<!-- PROPERTY region -->
<mxCell id="region-property" value="PROPERTY" style="swimlane;startSize=30;fillColor=#2d1a5e;strokeColor=#a78bfa;fontColor=#a78bfa;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="1280" y="60" width="900" height="1700" as="geometry"/>
</mxCell>
<!-- CLIENT region -->
<mxCell id="region-client" value="CLIENT" style="swimlane;startSize=30;fillColor=#3d1f06;strokeColor=#fbbf24;fontColor=#fbbf24;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="2220" y="60" width="860" height="1380" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- ORG MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- org_units -->
<mxCell id="org-units" value="<b>org_units</b>
<hr/>
🔑 PK id: uuid
parent_id: uuid (FK → self)
type: varchar(20)
name: varchar(100)
path: varchar(500) [物化路径]
depth: smallint
sort_order: int
is_active: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-org">
<mxGeometry x="30" y="60" width="280" height="185" as="geometry"/>
</mxCell>
<!-- staff -->
<mxCell id="staff" value="<b>staff</b>
<hr/>
🔑 PK id: uuid
FK org_unit_id → org_units
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
id_no_enc: text [AES]
user_id: uuid [FK → auth_user]
entry_date: date
status: active/resigned/...
is_active: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-org">
<mxGeometry x="30" y="310" width="280" height="215" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- REGION &amp; COMPLEX MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- districts -->
<mxCell id="districts" value="<b>districts</b>
<hr/>
🔑 PK id: uuid
city: varchar(50)
name: varchar(50)
short_name: varchar(20)
sort_order: int
is_active: bool
created_at: timestamptz" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="60" width="280" height="150" as="geometry"/>
</mxCell>
<!-- business_areas -->
<mxCell id="business-areas" value="<b>business_areas</b>
<hr/>
🔑 PK id: uuid
🔗 FK district_id → districts
name: varchar(100)
latitude: numeric(10,7)
longitude: numeric(10,7)
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="310" width="280" height="155" as="geometry"/>
</mxCell>
<!-- schools -->
<mxCell id="schools" value="<b>schools</b>
<hr/>
🔑 PK id: uuid
🔗 FK district_id → districts
name: varchar(100)
type: primary/middle/high/k9/k12
nature: public/private/international
level: normal/key/top
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="60" width="290" height="155" as="geometry"/>
</mxCell>
<!-- complexes -->
<mxCell id="complexes" value="<b>complexes</b>
<hr/>
🔑 PK id: uuid
🔗 FK district_id → districts
🔗 FK created_by → staff
name: varchar(200) [⚠ 不可直接修改]
address: varchar(500) [只读]
address_summary: varchar(100)
latitude: numeric(10,7)
longitude: numeric(10,7)
property_usage_types: varchar[]
building_structure: varchar(30)
building_type: slab/tower/slab_tower
land_use_years: varchar(30)
built_years: smallint[]
total_units: int
total_households: int
total_floor_area: numeric(12,2)
plot_area: numeric(12,2)
plot_ratio: numeric(5,2)
green_rate: numeric(5,2)
developer: varchar(200)
property_company: varchar(200)
property_fee: numeric(8,2)
property_phone: varchar(30)
parking_total: int
parking_underground: int
parking_ratio: varchar(20)
water_type: civil/commercial
electricity_type: civil/commercial
has_central_heating: bool
has_gas: bool
lock_building: bool
lock_room: bool
lock_info: bool
lock_standard_room: bool
search_vector: tsvector
remarks: text
is_active: bool
created_at: timestamptz
updated_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="570" width="340" height="570" as="geometry"/>
</mxCell>
<!-- complex_aliases -->
<mxCell id="complex-aliases" value="<b>complex_aliases</b>
<hr/>
🔑 PK id: uuid
🔗 FK complex_id → complexes
alias: varchar(200)
is_system: bool [系统别名只读]
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="570" width="290" height="130" as="geometry"/>
</mxCell>
<!-- complex_business_areas (join) -->
<mxCell id="complex-biz-areas" value="<b>complex_business_areas</b> [N:M join]
<hr/>
🔗 FK complex_id → complexes
🔗 FK business_area_id → business_areas
is_primary: bool [UNIQUE where TRUE]" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="490" width="370" height="70" as="geometry"/>
</mxCell>
<!-- complex_schools (join) -->
<mxCell id="complex-schools" value="<b>complex_schools</b> [N:M join]
<hr/>
🔗 FK complex_id → complexes
🔗 FK school_id → schools
zone_type: guaranteed/reference/lottery" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="250" width="300" height="75" as="geometry"/>
</mxCell>
<!-- buildings -->
<mxCell id="buildings" value="<b>buildings</b>
<hr/>
🔑 PK id: uuid
🔗 FK complex_id → complexes
🔗 FK school_id → schools [楼栋级学区]
name: varchar(50)
is_standard: bool
property_usage_type: varchar(20)
built_year: smallint
total_floors: smallint
land_use_years: varchar(30)
has_elevator: bool
is_active: bool
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1000" width="310" height="225" as="geometry"/>
</mxCell>
<!-- room_units -->
<mxCell id="room-units" value="<b>room_units</b>
<hr/>
🔑 PK id: uuid
🔗 FK building_id → buildings
floor: smallint
floor_name: varchar(20)
room_no: varchar(30)
display_no: varchar(50)
is_standard: bool
is_active: bool
created_at: timestamptz
updated_at: timestamptz
UNIQUE(building_id, floor, room_no)" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1260" width="310" height="200" as="geometry"/>
</mxCell>
<!-- complex_price_trends -->
<mxCell id="complex-price-trends" value="<b>complex_price_trends</b>
<hr/>
🔑 PK id: uuid
🔗 FK complex_id → complexes
record_month: date [存月份1日]
avg_sale_price: numeric(12,2)
avg_unit_price: numeric(10,2)
transaction_count: int
listing_count: int
created_at: timestamptz
UNIQUE(complex_id, record_month)" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="400" y="1000" width="380" height="185" as="geometry"/>
</mxCell>
<!-- metro_lines -->
<mxCell id="metro-lines" value="<b>metro_lines</b>
<hr/>
🔑 PK id: uuid
city: varchar(50)
name: varchar(50)
color: varchar(7) [HEX]
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1520" width="260" height="130" as="geometry"/>
</mxCell>
<!-- metro_stations -->
<mxCell id="metro-stations" value="<b>metro_stations</b>
<hr/>
🔑 PK id: uuid
🔗 FK metro_line_id → metro_lines
name: varchar(50)
latitude: numeric(10,7)
longitude: numeric(10,7)
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="320" y="1520" width="280" height="150" as="geometry"/>
</mxCell>
<!-- complex_metro_stations (join) -->
<mxCell id="complex-metro-stations" value="<b>complex_metro_stations</b> [N:M join]
<hr/>
🔗 FK complex_id → complexes
🔗 FK station_id → metro_stations
distance_meters: int [步行距离]" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="320" y="1700" width="320" height="70" as="geometry"/>
</mxCell>
<!-- complex_photos -->
<mxCell id="complex-photos" value="<b>complex_photos</b>
<hr/>
🔑 PK id: uuid
🔗 FK complex_id → complexes
category: complex/layout/vr/other
file_key: text [R2/S3]
thumbnail_key: text
file_name: varchar(255)
file_size: int
width, height: int
is_cover: bool [UNIQUE where TRUE]
sort_order: smallint
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="770" width="300" height="205" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- PROPERTY MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- properties -->
<mxCell id="properties" value="<b>properties</b>
<hr/>
🔑 PK id: uuid
🔗 FK complex_id → complexes
🔗 FK building_id → buildings
🔗 FK room_unit_id → room_units
🔗 FK agent_id → staff
listing_type: sale/rent/both
status: varchar(20)
sale_price: numeric(12,2) [万元]
rent_price: numeric(10,2) [元/月]
area: numeric(8,2) [m²]
floor: smallint
total_floors: smallint
bedroom: smallint
living_room: smallint
bathroom: smallint
orientation: varchar(30)
decoration: varchar(20)
has_elevator: bool
built_year: smallint
ownership_years: varchar(20)
is_exclusive: bool [独家委托]
completeness_score: int
search_vector: tsvector
source: varchar(30)
remarks: text
created_at: timestamptz
updated_at: timestamptz
deleted_at: timestamptz
🔗 FK created_by → staff
🔗 FK updated_by → staff
<i>[89,000+ rows · 复合索引 · 分区预留]</i>" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="60" width="380" height="560" as="geometry"/>
</mxCell>
<!-- property_contacts -->
<mxCell id="property-contacts" value="<b>property_contacts</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
role: owner/agent/tenant
is_primary: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="670" width="310" height="170" as="geometry"/>
</mxCell>
<!-- property_follow_logs -->
<mxCell id="property-follow-logs" value="<b>property_follow_logs</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK staff_id → staff
log_type: call/visit/price_change/note/...
content: text
phone_no_viewed: bool [敏感操作]
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="60" width="380" height="185" as="geometry"/>
</mxCell>
<!-- listing_histories -->
<mxCell id="listing-histories" value="<b>listing_histories</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
listed_at: timestamptz
delisted_at: timestamptz
list_price: numeric(12,2)
reason: varchar(50)
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="300" width="310" height="155" as="geometry"/>
</mxCell>
<!-- property_photos -->
<mxCell id="property-photos" value="<b>property_photos</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
category: listing/vr/layout/other
file_key: text [R2/S3]
thumbnail_key: text
is_cover: bool
sort_order: smallint
width, height: int
file_size: int
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="900" width="310" height="205" as="geometry"/>
</mxCell>
<!-- property_keys -->
<mxCell id="property-keys" value="<b>property_keys</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK holder_id → staff
key_no: varchar(50)
status: held/returned
taken_at: timestamptz
returned_at: timestamptz
notes: text" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="510" width="310" height="165" as="geometry"/>
</mxCell>
<!-- property_commissions -->
<mxCell id="property-commissions" value="<b>property_commissions</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
commission_type: exclusive/open
rate: numeric(5,4)
amount: numeric(12,2)
start_date: date
end_date: date
signed_at: timestamptz
document_key: text
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="740" width="330" height="185" as="geometry"/>
</mxCell>
<!-- property_inspections -->
<mxCell id="property-inspections" value="<b>property_inspections</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK staff_id → staff
inspected_at: timestamptz
status: pending/done/cancelled
notes: text
attachments: jsonb
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="1160" width="320" height="165" as="geometry"/>
</mxCell>
<!-- property_marketing -->
<mxCell id="property-marketing" value="<b>property_marketing</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
title: varchar(200)
highlights: text[]
description: text
tags: varchar[]
platforms: jsonb
published_at: timestamptz
updated_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="990" width="340" height="175" as="geometry"/>
</mxCell>
<!-- property_certificates -->
<mxCell id="property-certificates" value="<b>property_certificates</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
cert_no: varchar(50)
owner_name: varchar(100)
ownership_type: varchar(30)
area_registered: numeric(8,2)
issue_date: date
document_key: text" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="1390" width="330" height="165" as="geometry"/>
</mxCell>
<!-- completeness_scores -->
<mxCell id="completeness-scores" value="<b>completeness_scores</b>
<hr/>
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
score: int [0-100]
missing_fields: text[]
calculated_at: timestamptz
version: int" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="1230" width="310" height="135" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- CLIENT MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- clients -->
<mxCell id="clients" value="<b>clients</b>
<hr/>
🔑 PK id: uuid
🔗 FK agent_id → staff
client_type: private/public/closed
status: active/inactive/converted
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
budget_min/max: numeric
activity_level: 1-5 [Celery每日计算]
is_protected: bool [防自动转公客]
transfer_to_public_type: auto/manual
last_follow_at: timestamptz
source: varchar(30)
remarks: text
created_at: timestamptz
deleted_at: timestamptz
🔗 FK created_by → staff
<i>[私客/公客/成交客 三态状态机]</i>" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="60" width="370" height="360" as="geometry"/>
</mxCell>
<!-- client_requirements -->
<mxCell id="client-requirements" value="<b>client_requirements</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
req_type: second_hand/new/rent
district_ids: uuid[]
business_area_ids: uuid[]
price_min: numeric
price_max: numeric
area_min: numeric
area_max: numeric
bedrooms: int[]
school_ids: uuid[]
has_elevator: bool
is_active: bool
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="480" width="350" height="260" as="geometry"/>
</mxCell>
<!-- client_follow_logs -->
<mxCell id="client-follow-logs" value="<b>client_follow_logs</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK staff_id → staff
log_type: call/visit/match/note/status_change
content: text
next_follow_date: date
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="60" width="380" height="200" as="geometry"/>
</mxCell>
<!-- client_viewings -->
<mxCell id="client-viewings" value="<b>client_viewings</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK property_id → properties
🔗 FK agent_id → staff
viewed_at: timestamptz
feedback: text
rating: smallint [1-5]
status: planned/done/cancelled
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="310" width="360" height="195" as="geometry"/>
</mxCell>
<!-- client_property_matches -->
<mxCell id="client-matches" value="<b>client_property_matches</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK property_id → properties
🔗 FK agent_id → staff
match_type: system/manual
score: numeric(5,2)
status: pending/sent/viewed/dismissed
sent_at: timestamptz
viewed_at: timestamptz
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="800" width="380" height="205" as="geometry"/>
</mxCell>
<!-- client_status_logs -->
<mxCell id="client-status-logs" value="<b>client_status_logs</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
from_status: varchar(20)
to_status: varchar(20)
transfer_type: auto/manual
reason: text
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="560" width="370" height="195" as="geometry"/>
</mxCell>
<!-- client_favorite_folders -->
<mxCell id="client-fav-folders" value="<b>client_favorite_folders</b>
<hr/>
🔑 PK id: uuid
🔗 FK client_id → clients
name: varchar(100)
sort_order: int
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="1070" width="300" height="130" as="geometry"/>
</mxCell>
<!-- client_folder_items -->
<mxCell id="client-folder-items" value="<b>client_folder_items</b>
<hr/>
🔑 PK id: uuid
🔗 FK folder_id → client_favorite_folders
🔗 FK property_id → properties
sort_order: int
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="370" y="1070" width="320" height="130" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- EDGES / RELATIONSHIPS -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- OrgUnit self-ref -->
<mxCell id="e-org-self" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="org-units" target="org-units" parent="region-org">
<mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="340" y="153"/><mxPoint x="340" y="108"/></Array></mxGeometry>
</mxCell>
<mxCell id="e-org-self-lbl" value="自引用 parent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-org-self"><mxGeometry x="0.1" relative="1" as="geometry"/></mxCell>
<!-- OrgUnit → Staff -->
<mxCell id="e-org-staff" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="org-units" target="staff" parent="region-org">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-org-staff-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-org-staff"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → BusinessArea -->
<mxCell id="e-dist-biz" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="business-areas" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-biz-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-biz"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → Schools -->
<mxCell id="e-dist-school" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="schools" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-school-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-school"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → Complexes -->
<mxCell id="e-dist-complex" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-complex-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-complex"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- BusinessArea ↔ Complexes via join -->
<mxCell id="e-biz-join" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="business-areas" target="complex-biz-areas" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-join-complex" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-biz-areas" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Schools ↔ Complexes via join -->
<mxCell id="e-school-join" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="schools" target="complex-schools" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-school-join2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-schools" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Complexes → complex_aliases -->
<mxCell id="e-complex-alias" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-aliases" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-alias-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-alias"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → complex_photos -->
<mxCell id="e-complex-photos" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-photos" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-photos-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-photos"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → complex_price_trends -->
<mxCell id="e-complex-trend" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-price-trends" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-trend-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-trend"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → Buildings -->
<mxCell id="e-complex-bldg" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="buildings" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-bldg-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-bldg"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Buildings → RoomUnits -->
<mxCell id="e-bldg-room" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="buildings" target="room-units" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-bldg-room-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-bldg-room"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- MetroLine → MetroStation -->
<mxCell id="e-metro-line-station" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="metro-lines" target="metro-stations" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-metro-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-metro-line-station"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- MetroStation ↔ Complexes via join -->
<mxCell id="e-metro-join1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="metro-stations" target="complex-metro-stations" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-metro-join2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-metro-stations" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Properties → PropertyContacts -->
<mxCell id="e-prop-contact" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-contacts" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-contact-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-contact"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → FollowLogs -->
<mxCell id="e-prop-follow" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-follow-logs" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-follow-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-follow"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → ListingHistories -->
<mxCell id="e-prop-listing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="listing-histories" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-listing-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-listing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Photos -->
<mxCell id="e-prop-photos" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-photos" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-photos-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-photos"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Keys -->
<mxCell id="e-prop-keys" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-keys" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-keys-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-keys"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Commissions -->
<mxCell id="e-prop-comm" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-commissions" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-comm-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-comm"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Inspections -->
<mxCell id="e-prop-insp" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-inspections" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-insp-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-insp"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Marketing (1:1) -->
<mxCell id="e-prop-marketing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-marketing" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-marketing-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-marketing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Certificates (1:1) -->
<mxCell id="e-prop-cert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-certificates" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-cert-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-cert"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Completeness (1:1) -->
<mxCell id="e-prop-score" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="completeness-scores" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-score-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-score"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → ClientRequirements -->
<mxCell id="e-client-req" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-requirements" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-req-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-req"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → FollowLogs -->
<mxCell id="e-client-follow" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-follow-logs" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-follow-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-follow"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → Viewings -->
<mxCell id="e-client-viewing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-viewings" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-viewing-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-viewing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → Matches -->
<mxCell id="e-client-match" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-matches" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-match-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-match"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → StatusLogs -->
<mxCell id="e-client-statuslog" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-status-logs" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-statuslog-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-statuslog"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → FavFolders -->
<mxCell id="e-client-fav" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-fav-folders" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-fav-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-fav"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- FavFolders → FolderItems -->
<mxCell id="e-fav-items" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="client-fav-folders" target="client-folder-items" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-fav-items-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-fav-items"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- CROSS-REGION EDGES (parent=1) -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- Complexes → Properties -->
<mxCell id="e-complex-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=0;endArrow=ERmany;startArrow=ERone;fontSize=9;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" source="complexes" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-prop-lbl" value="1:N complex_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-complex-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Buildings → Properties -->
<mxCell id="e-bldg-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="buildings" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-bldg-prop-lbl" value="1:N building_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-bldg-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- RoomUnits → Properties -->
<mxCell id="e-room-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="room-units" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-room-prop-lbl" value="1:N room_unit_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-room-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Staff → Properties (agent_id) -->
<mxCell id="e-staff-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="staff" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-staff-prop-lbl" value="agent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-staff-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Staff → Clients (agent_id) -->
<mxCell id="e-staff-client" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="staff" target="clients" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-staff-client-lbl" value="agent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-staff-client"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Viewings (cross-region) -->
<mxCell id="e-prop-viewing" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-viewings" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-viewing-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-viewing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Matches (cross-region) -->
<mxCell id="e-prop-match" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-matches" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-match-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-match"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → FolderItems (cross-region) -->
<mxCell id="e-prop-folder" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-folder-items" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-folder-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-folder"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,860 @@
<mxfile host="app.diagrams.net" modified="2026-04-24" agent="OpenCode" version="21.0.0">
<diagram name="Fonrey ER Diagram" id="fonrey-er-v1">
<mxGraphModel dx="1422" dy="762" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3300" pageHeight="2340" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- ═══════════════════════════════════════════════════ -->
<!-- SWIM LANE BACKGROUNDS -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- ORG / HR region -->
<mxCell id="region-org" value="ORG / HR" style="swimlane;startSize=30;fillColor=#0d3349;strokeColor=#22d3ee;fontColor=#22d3ee;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="40" y="60" width="340" height="760" as="geometry"/>
</mxCell>
<!-- REGION & COMPLEX region -->
<mxCell id="region-complex" value="REGION &amp; COMPLEX" style="swimlane;startSize=30;fillColor=#063b2f;strokeColor=#34d399;fontColor=#34d399;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="420" y="60" width="820" height="1380" as="geometry"/>
</mxCell>
<!-- PROPERTY region -->
<mxCell id="region-property" value="PROPERTY" style="swimlane;startSize=30;fillColor=#2d1a5e;strokeColor=#a78bfa;fontColor=#a78bfa;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="1280" y="60" width="900" height="1700" as="geometry"/>
</mxCell>
<!-- CLIENT region -->
<mxCell id="region-client" value="CLIENT" style="swimlane;startSize=30;fillColor=#3d1f06;strokeColor=#fbbf24;fontColor=#fbbf24;fontSize=12;fontStyle=1;swimlaneLine=1;rounded=1;arcSize=3;" vertex="1" parent="1">
<mxGeometry x="2220" y="60" width="860" height="1380" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- ORG MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- org_units -->
<mxCell id="org-units" value="&lt;b&gt;org_units&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
parent_id: uuid (FK → self)
type: varchar(20)
name: varchar(100)
path: varchar(500) [物化路径]
depth: smallint
sort_order: int
is_active: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-org">
<mxGeometry x="30" y="60" width="280" height="185" as="geometry"/>
</mxCell>
<!-- staff -->
<mxCell id="staff" value="&lt;b&gt;staff&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
FK org_unit_id → org_units
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
id_no_enc: text [AES]
user_id: uuid [FK → auth_user]
entry_date: date
status: active/resigned/...
is_active: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#22d3ee;fillColor=#0d3349;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-org">
<mxGeometry x="30" y="310" width="280" height="215" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- REGION & COMPLEX MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- districts -->
<mxCell id="districts" value="&lt;b&gt;districts&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
city: varchar(50)
name: varchar(50)
short_name: varchar(20)
sort_order: int
is_active: bool
created_at: timestamptz" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="60" width="280" height="150" as="geometry"/>
</mxCell>
<!-- business_areas -->
<mxCell id="business-areas" value="&lt;b&gt;business_areas&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK district_id → districts
name: varchar(100)
latitude: numeric(10,7)
longitude: numeric(10,7)
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="310" width="280" height="155" as="geometry"/>
</mxCell>
<!-- schools -->
<mxCell id="schools" value="&lt;b&gt;schools&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK district_id → districts
name: varchar(100)
type: primary/middle/high/k9/k12
nature: public/private/international
level: normal/key/top
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="60" width="290" height="155" as="geometry"/>
</mxCell>
<!-- complexes -->
<mxCell id="complexes" value="&lt;b&gt;complexes&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK district_id → districts
🔗 FK created_by → staff
name: varchar(200) [⚠ 不可直接修改]
address: varchar(500) [只读]
address_summary: varchar(100)
latitude: numeric(10,7)
longitude: numeric(10,7)
property_usage_types: varchar[]
building_structure: varchar(30)
building_type: slab/tower/slab_tower
land_use_years: varchar(30)
built_years: smallint[]
total_units: int
total_households: int
total_floor_area: numeric(12,2)
plot_area: numeric(12,2)
plot_ratio: numeric(5,2)
green_rate: numeric(5,2)
developer: varchar(200)
property_company: varchar(200)
property_fee: numeric(8,2)
property_phone: varchar(30)
parking_total: int
parking_underground: int
parking_ratio: varchar(20)
water_type: civil/commercial
electricity_type: civil/commercial
has_central_heating: bool
has_gas: bool
lock_building: bool
lock_room: bool
lock_info: bool
lock_standard_room: bool
search_vector: tsvector
remarks: text
is_active: bool
created_at: timestamptz
updated_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="570" width="340" height="570" as="geometry"/>
</mxCell>
<!-- complex_aliases -->
<mxCell id="complex-aliases" value="&lt;b&gt;complex_aliases&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK complex_id → complexes
alias: varchar(200)
is_system: bool [系统别名只读]
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="570" width="290" height="130" as="geometry"/>
</mxCell>
<!-- complex_business_areas (join) -->
<mxCell id="complex-biz-areas" value="&lt;b&gt;complex_business_areas&lt;/b&gt; [N:M join]
&lt;hr/&gt;
🔗 FK complex_id → complexes
🔗 FK business_area_id → business_areas
is_primary: bool [UNIQUE where TRUE]" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="490" width="370" height="70" as="geometry"/>
</mxCell>
<!-- complex_schools (join) -->
<mxCell id="complex-schools" value="&lt;b&gt;complex_schools&lt;/b&gt; [N:M join]
&lt;hr/&gt;
🔗 FK complex_id → complexes
🔗 FK school_id → schools
zone_type: guaranteed/reference/lottery" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="250" width="300" height="75" as="geometry"/>
</mxCell>
<!-- buildings -->
<mxCell id="buildings" value="&lt;b&gt;buildings&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK complex_id → complexes
🔗 FK school_id → schools [楼栋级学区]
name: varchar(50)
is_standard: bool
property_usage_type: varchar(20)
built_year: smallint
total_floors: smallint
land_use_years: varchar(30)
has_elevator: bool
is_active: bool
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1000" width="310" height="225" as="geometry"/>
</mxCell>
<!-- room_units -->
<mxCell id="room-units" value="&lt;b&gt;room_units&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK building_id → buildings
floor: smallint
floor_name: varchar(20)
room_no: varchar(30)
display_no: varchar(50)
is_standard: bool
is_active: bool
created_at: timestamptz
updated_at: timestamptz
UNIQUE(building_id, floor, room_no)" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1260" width="310" height="200" as="geometry"/>
</mxCell>
<!-- complex_price_trends -->
<mxCell id="complex-price-trends" value="&lt;b&gt;complex_price_trends&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK complex_id → complexes
record_month: date [存月份1日]
avg_sale_price: numeric(12,2)
avg_unit_price: numeric(10,2)
transaction_count: int
listing_count: int
created_at: timestamptz
UNIQUE(complex_id, record_month)" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="400" y="1000" width="380" height="185" as="geometry"/>
</mxCell>
<!-- metro_lines -->
<mxCell id="metro-lines" value="&lt;b&gt;metro_lines&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
city: varchar(50)
name: varchar(50)
color: varchar(7) [HEX]
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="30" y="1520" width="260" height="130" as="geometry"/>
</mxCell>
<!-- metro_stations -->
<mxCell id="metro-stations" value="&lt;b&gt;metro_stations&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK metro_line_id → metro_lines
name: varchar(50)
latitude: numeric(10,7)
longitude: numeric(10,7)
sort_order: int
is_active: bool" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="320" y="1520" width="280" height="150" as="geometry"/>
</mxCell>
<!-- complex_metro_stations (join) -->
<mxCell id="complex-metro-stations" value="&lt;b&gt;complex_metro_stations&lt;/b&gt; [N:M join]
&lt;hr/&gt;
🔗 FK complex_id → complexes
🔗 FK station_id → metro_stations
distance_meters: int [步行距离]" style="text;html=1;strokeColor=#34d399;fillColor=#0a2e22;strokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#6ee7b7;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="320" y="1700" width="320" height="70" as="geometry"/>
</mxCell>
<!-- complex_photos -->
<mxCell id="complex-photos" value="&lt;b&gt;complex_photos&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK complex_id → complexes
category: complex/layout/vr/other
file_key: text [R2/S3]
thumbnail_key: text
file_name: varchar(255)
file_size: int
width, height: int
is_cover: bool [UNIQUE where TRUE]
sort_order: smallint
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#34d399;fillColor=#063b2f;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-complex">
<mxGeometry x="490" y="770" width="300" height="205" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- PROPERTY MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- properties -->
<mxCell id="properties" value="&lt;b&gt;properties&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK complex_id → complexes
🔗 FK building_id → buildings
🔗 FK room_unit_id → room_units
🔗 FK agent_id → staff
listing_type: sale/rent/both
status: varchar(20)
sale_price: numeric(12,2) [万元]
rent_price: numeric(10,2) [元/月]
area: numeric(8,2) [m²]
floor: smallint
total_floors: smallint
bedroom: smallint
living_room: smallint
bathroom: smallint
orientation: varchar(30)
decoration: varchar(20)
has_elevator: bool
built_year: smallint
ownership_years: varchar(20)
is_exclusive: bool [独家委托]
completeness_score: int
search_vector: tsvector
source: varchar(30)
remarks: text
created_at: timestamptz
updated_at: timestamptz
deleted_at: timestamptz
🔗 FK created_by → staff
🔗 FK updated_by → staff
&lt;i&gt;[89,000+ rows · 复合索引 · 分区预留]&lt;/i&gt;" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="60" width="380" height="560" as="geometry"/>
</mxCell>
<!-- property_contacts -->
<mxCell id="property-contacts" value="&lt;b&gt;property_contacts&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
role: owner/agent/tenant
is_primary: bool
created_at: timestamptz
deleted_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="670" width="310" height="170" as="geometry"/>
</mxCell>
<!-- property_follow_logs -->
<mxCell id="property-follow-logs" value="&lt;b&gt;property_follow_logs&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK staff_id → staff
log_type: call/visit/price_change/note/...
content: text
phone_no_viewed: bool [敏感操作]
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="60" width="380" height="185" as="geometry"/>
</mxCell>
<!-- listing_histories -->
<mxCell id="listing-histories" value="&lt;b&gt;listing_histories&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
listed_at: timestamptz
delisted_at: timestamptz
list_price: numeric(12,2)
reason: varchar(50)
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="300" width="310" height="155" as="geometry"/>
</mxCell>
<!-- property_photos -->
<mxCell id="property-photos" value="&lt;b&gt;property_photos&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
category: listing/vr/layout/other
file_key: text [R2/S3]
thumbnail_key: text
is_cover: bool
sort_order: smallint
width, height: int
file_size: int
created_at: timestamptz
🔗 FK created_by → staff" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="900" width="310" height="205" as="geometry"/>
</mxCell>
<!-- property_keys -->
<mxCell id="property-keys" value="&lt;b&gt;property_keys&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK holder_id → staff
key_no: varchar(50)
status: held/returned
taken_at: timestamptz
returned_at: timestamptz
notes: text" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="510" width="310" height="165" as="geometry"/>
</mxCell>
<!-- property_commissions -->
<mxCell id="property-commissions" value="&lt;b&gt;property_commissions&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
commission_type: exclusive/open
rate: numeric(5,4)
amount: numeric(12,2)
start_date: date
end_date: date
signed_at: timestamptz
document_key: text
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="740" width="330" height="185" as="geometry"/>
</mxCell>
<!-- property_inspections -->
<mxCell id="property-inspections" value="&lt;b&gt;property_inspections&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties
🔗 FK staff_id → staff
inspected_at: timestamptz
status: pending/done/cancelled
notes: text
attachments: jsonb
created_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="1160" width="320" height="165" as="geometry"/>
</mxCell>
<!-- property_marketing -->
<mxCell id="property-marketing" value="&lt;b&gt;property_marketing&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
title: varchar(200)
highlights: text[]
description: text
tags: varchar[]
platforms: jsonb
published_at: timestamptz
updated_at: timestamptz" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="990" width="340" height="175" as="geometry"/>
</mxCell>
<!-- property_certificates -->
<mxCell id="property-certificates" value="&lt;b&gt;property_certificates&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
cert_no: varchar(50)
owner_name: varchar(100)
ownership_type: varchar(30)
area_registered: numeric(8,2)
issue_date: date
document_key: text" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="30" y="1390" width="330" height="165" as="geometry"/>
</mxCell>
<!-- completeness_scores -->
<mxCell id="completeness-scores" value="&lt;b&gt;completeness_scores&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK property_id → properties [UNIQUE 1:1]
score: int [0-100]
missing_fields: text[]
calculated_at: timestamptz
version: int" style="text;html=1;strokeColor=#a78bfa;fillColor=#2d1a5e;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-property">
<mxGeometry x="470" y="1230" width="310" height="135" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- CLIENT MODULE -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- clients -->
<mxCell id="clients" value="&lt;b&gt;clients&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK agent_id → staff
client_type: private/public/closed
status: active/inactive/converted
name: varchar(50)
phone_enc: text [AES-256-GCM]
phone_hash: varchar(64) [SHA-256]
budget_min/max: numeric
activity_level: 1-5 [Celery每日计算]
is_protected: bool [防自动转公客]
transfer_to_public_type: auto/manual
last_follow_at: timestamptz
source: varchar(30)
remarks: text
created_at: timestamptz
deleted_at: timestamptz
🔗 FK created_by → staff
&lt;i&gt;[私客/公客/成交客 三态状态机]&lt;/i&gt;" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="60" width="370" height="360" as="geometry"/>
</mxCell>
<!-- client_requirements -->
<mxCell id="client-requirements" value="&lt;b&gt;client_requirements&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
req_type: second_hand/new/rent
district_ids: uuid[]
business_area_ids: uuid[]
price_min: numeric
price_max: numeric
area_min: numeric
area_max: numeric
bedrooms: int[]
school_ids: uuid[]
has_elevator: bool
is_active: bool
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="480" width="350" height="260" as="geometry"/>
</mxCell>
<!-- client_follow_logs -->
<mxCell id="client-follow-logs" value="&lt;b&gt;client_follow_logs&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK staff_id → staff
log_type: call/visit/match/note/status_change
content: text
next_follow_date: date
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="60" width="380" height="200" as="geometry"/>
</mxCell>
<!-- client_viewings -->
<mxCell id="client-viewings" value="&lt;b&gt;client_viewings&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK property_id → properties
🔗 FK agent_id → staff
viewed_at: timestamptz
feedback: text
rating: smallint [1-5]
status: planned/done/cancelled
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="310" width="360" height="195" as="geometry"/>
</mxCell>
<!-- client_property_matches -->
<mxCell id="client-matches" value="&lt;b&gt;client_property_matches&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
🔗 FK property_id → properties
🔗 FK agent_id → staff
match_type: system/manual
score: numeric(5,2)
status: pending/sent/viewed/dismissed
sent_at: timestamptz
viewed_at: timestamptz
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="800" width="380" height="205" as="geometry"/>
</mxCell>
<!-- client_status_logs -->
<mxCell id="client-status-logs" value="&lt;b&gt;client_status_logs&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
from_status: varchar(20)
to_status: varchar(20)
transfer_type: auto/manual
reason: text
created_at: timestamptz
🔗 FK created_by → staff
⚠ NO DELETE — append-only audit log" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="430" y="560" width="370" height="195" as="geometry"/>
</mxCell>
<!-- client_favorite_folders -->
<mxCell id="client-fav-folders" value="&lt;b&gt;client_favorite_folders&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK client_id → clients
name: varchar(100)
sort_order: int
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="30" y="1070" width="300" height="130" as="geometry"/>
</mxCell>
<!-- client_folder_items -->
<mxCell id="client-folder-items" value="&lt;b&gt;client_folder_items&lt;/b&gt;
&lt;hr/&gt;
🔑 PK id: uuid
🔗 FK folder_id → client_favorite_folders
🔗 FK property_id → properties
sort_order: int
created_at: timestamptz" style="text;html=1;strokeColor=#fbbf24;fillColor=#3d1f06;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;overflow=hidden;rotatable=0;fontSize=11;fontFamily=monospace;fontColor=#e2e8f0;whiteSpace=pre;" vertex="1" parent="region-client">
<mxGeometry x="370" y="1070" width="320" height="130" as="geometry"/>
</mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- EDGES / RELATIONSHIPS -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- OrgUnit self-ref -->
<mxCell id="e-org-self" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="org-units" target="org-units" parent="region-org">
<mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="340" y="153"/><mxPoint x="340" y="108"/></Array></mxGeometry>
</mxCell>
<mxCell id="e-org-self-lbl" value="自引用 parent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-org-self"><mxGeometry x="0.1" relative="1" as="geometry"/></mxCell>
<!-- OrgUnit → Staff -->
<mxCell id="e-org-staff" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#22d3ee;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="org-units" target="staff" parent="region-org">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-org-staff-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-org-staff"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → BusinessArea -->
<mxCell id="e-dist-biz" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="business-areas" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-biz-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-biz"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → Schools -->
<mxCell id="e-dist-school" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="schools" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-school-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-school"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- District → Complexes -->
<mxCell id="e-dist-complex" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="districts" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-dist-complex-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-dist-complex"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- BusinessArea ↔ Complexes via join -->
<mxCell id="e-biz-join" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="business-areas" target="complex-biz-areas" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-join-complex" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-biz-areas" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Schools ↔ Complexes via join -->
<mxCell id="e-school-join" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="schools" target="complex-schools" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-school-join2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-schools" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Complexes → complex_aliases -->
<mxCell id="e-complex-alias" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-aliases" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-alias-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-alias"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → complex_photos -->
<mxCell id="e-complex-photos" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-photos" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-photos-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-photos"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → complex_price_trends -->
<mxCell id="e-complex-trend" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="complex-price-trends" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-trend-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-trend"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Complexes → Buildings -->
<mxCell id="e-complex-bldg" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="complexes" target="buildings" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-bldg-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-complex-bldg"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Buildings → RoomUnits -->
<mxCell id="e-bldg-room" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="buildings" target="room-units" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-bldg-room-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-bldg-room"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- MetroLine → MetroStation -->
<mxCell id="e-metro-line-station" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="metro-lines" target="metro-stations" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-metro-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#34d399;" vertex="1" connectable="0" parent="e-metro-line-station"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- MetroStation ↔ Complexes via join -->
<mxCell id="e-metro-join1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="metro-stations" target="complex-metro-stations" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-metro-join2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#34d399;dashed=1;endArrow=open;startArrow=open;fontSize=9;" edge="1" source="complex-metro-stations" target="complexes" parent="region-complex">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Properties → PropertyContacts -->
<mxCell id="e-prop-contact" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-contacts" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-contact-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-contact"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → FollowLogs -->
<mxCell id="e-prop-follow" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-follow-logs" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-follow-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-follow"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → ListingHistories -->
<mxCell id="e-prop-listing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="listing-histories" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-listing-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-listing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Photos -->
<mxCell id="e-prop-photos" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-photos" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-photos-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-photos"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Keys -->
<mxCell id="e-prop-keys" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-keys" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-keys-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-keys"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Commissions -->
<mxCell id="e-prop-comm" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-commissions" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-comm-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-comm"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Inspections -->
<mxCell id="e-prop-insp" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-inspections" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-insp-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-insp"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Marketing (1:1) -->
<mxCell id="e-prop-marketing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-marketing" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-marketing-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-marketing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Certificates (1:1) -->
<mxCell id="e-prop-cert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="property-certificates" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-cert-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-cert"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Completeness (1:1) -->
<mxCell id="e-prop-score" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#a78bfa;endArrow=ERone;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="completeness-scores" parent="region-property">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-score-lbl" value="1:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-prop-score"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → ClientRequirements -->
<mxCell id="e-client-req" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-requirements" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-req-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-req"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → FollowLogs -->
<mxCell id="e-client-follow" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-follow-logs" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-follow-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-follow"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → Viewings -->
<mxCell id="e-client-viewing" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-viewings" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-viewing-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-viewing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → Matches -->
<mxCell id="e-client-match" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-matches" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-match-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-match"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → StatusLogs -->
<mxCell id="e-client-statuslog" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-status-logs" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-statuslog-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-statuslog"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Clients → FavFolders -->
<mxCell id="e-client-fav" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="clients" target="client-fav-folders" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-client-fav-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-client-fav"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- FavFolders → FolderItems -->
<mxCell id="e-fav-items" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#fbbf24;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="client-fav-folders" target="client-folder-items" parent="region-client">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-fav-items-lbl" value="1:N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fbbf24;" vertex="1" connectable="0" parent="e-fav-items"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- ═══════════════════════════════════════════════════ -->
<!-- CROSS-REGION EDGES (parent=1) -->
<!-- ═══════════════════════════════════════════════════ -->
<!-- Complexes → Properties -->
<mxCell id="e-complex-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=0;endArrow=ERmany;startArrow=ERone;fontSize=9;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" source="complexes" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-complex-prop-lbl" value="1:N complex_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-complex-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Buildings → Properties -->
<mxCell id="e-bldg-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="buildings" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-bldg-prop-lbl" value="1:N building_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-bldg-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- RoomUnits → Properties -->
<mxCell id="e-room-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#a78bfa;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="room-units" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-room-prop-lbl" value="1:N room_unit_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#a78bfa;" vertex="1" connectable="0" parent="e-room-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Staff → Properties (agent_id) -->
<mxCell id="e-staff-prop" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="staff" target="properties" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-staff-prop-lbl" value="agent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-staff-prop"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Staff → Clients (agent_id) -->
<mxCell id="e-staff-client" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#22d3ee;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="staff" target="clients" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-staff-client-lbl" value="agent_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#22d3ee;" vertex="1" connectable="0" parent="e-staff-client"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Viewings (cross-region) -->
<mxCell id="e-prop-viewing" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-viewings" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-viewing-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-viewing"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → Matches (cross-region) -->
<mxCell id="e-prop-match" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-matches" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-match-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-match"><mxGeometry relative="1" as="geometry"/></mxCell>
<!-- Properties → FolderItems (cross-region) -->
<mxCell id="e-prop-folder" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;strokeColor=#fb923c;dashed=1;endArrow=ERmany;startArrow=ERone;fontSize=9;" edge="1" source="properties" target="client-folder-items" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e-prop-folder-lbl" value="property_id" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=9;fontColor=#fb923c;" vertex="1" connectable="0" parent="e-prop-folder"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,177 @@
# Fonrey 系统角色 Persona 定义
**版本**v1.0
**状态**:已定稿
**作者**:产品团队
**最后更新**2026-04-30
> 本文档是 Fonrey 所有 PRD、DATA_MODEL、PERMISSION_SEED 等文档的角色命名**唯一权威来源**。
> 所有其他文档中的角色称谓**必须**以本文档为准,禁止自造名称或混用。
---
## 一、角色层级概览
```
┌─────────────────────────────────────────────────────────┐
│ Fonrey SaaS 平台 │
│ │
│ ① Platform Admin平台超级管理员
│ └── 管理所有租户:开通、暂停、配置、版本升级 │
│ │
│ ─ ─ ─ ─ ─ ─ ─ 租 户 边 界 ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │
│ ② Tenant Admin租户管理员
│ └── 管理本租户:组织架构、账号、权限、系统配置 │
│ │
│ ③ Agent经纪人
│ └── 日常业务操作:录入房源、跟进客源、查看数据 │
│ │
└─────────────────────────────────────────────────────────┘
```
---
## 二、Persona 详细定义
### P1 — Platform Admin平台超级管理员
| 属性 | 值 |
| ---------------- | --------------------- |
| **Persona 名称** | Platform Admin |
| **中文称谓** | 平台超级管理员 |
| **Persona Code** | `PLATFORM_ADMIN` |
| **所属层** | 平台层Platform |
| **账号归属** | 平台运营团队Fonrey 公司内部人员) |
| **账号数量** | 极少,手动创建,不通过租户系统管理 |
| **认证入口** | 独立管理后台(非租户客户端) |
| **Schema 归属** | `public`(跨所有租户) |
**职责范围:**
- 创建、暂停、注销租户Tenant
- 为租户初始化 Tenant Admin 账号
- 管理平台版本、发布客户端安装包
- 监控系统健康状态、查看平台级日志
- 配置平台级参数短信网关、OSS、第三方集成等
**不涉及:**
- 不进入任何租户的业务数据
- 不参与租户内部的权限分配、组织管理
**关键约束:**
- Platform Admin 走**独立认证体系**,不纳入租户 RBAC 权限模型
- 无需在 `permission_definitions` 中注册权限项
---
### P2 — Tenant Admin租户管理员
| 属性 | 值 |
| ---------------- | ---------------------------------- |
| **Persona 名称** | Tenant Admin |
| **中文称谓** | 租户管理员 |
| **Persona Code** | `TENANT_ADMIN` |
| **所属层** | 租户层Tenant |
| **账号归属** | 各租户(房产经纪公司)的系统管理员 |
| **账号数量** | 每个租户 13 个,由 Platform Admin 创建初始账号 |
| **认证入口** | 与经纪人相同的租户客户端Electron App |
| **登录账号** | 平台运营分配的自定义字符串(不限于手机号格式) |
| **初始密码** | 平台统一固定初始密码,首次登录强制修改 |
| **Schema 归属** | 租户 Schema`tenant_{id}` |
| | |
**职责范围:**
- 维护组织架构(部门/门店树)
- 办理员工入职、离职、调动
- 创建和管理员工系统账号
- 配置角色与权限(角色创建、权限分配、个人权限调整)
- 配置系统枚举值Lookup Items、房源录入规则、客源规则
- 查看全租户范围的业务数据(受数据权限规则约束)
**不涉及:**
- 不能跨租户操作
- 不能修改平台级配置(版本、短信网关等)
**与「经纪人」的区别:**
- Tenant Admin 是**管理身份**,不从事日常房源/客源业务操作
- 在系统内显示为「管理员」身份,拥有全模块管理权限
- 可以同时持有某个业务角色(如总经),但账号性质以 Tenant Admin 为主
**在 PRD 中的出现场景:**
- 组织人事管理模块的主操作者
- 权限管理模块的主操作者
- 系统配置模块的主操作者
- 在楼盘管理、发布管理中执行管理操作的用户
---
### P3 — Agent经纪人
| 属性 | 值 |
| ---------------- | ----------------------------- |
| **Persona 名称** | Agent |
| **中文称谓** | 经纪人 |
| **Persona Code** | `AGENT` |
| **所属层** | 租户层Tenant |
| **账号归属** | 各租户的在职员工 |
| **账号数量** | 每个租户 N 个,由 Tenant Admin 创建 |
| **认证入口** | 租户客户端Electron App |
| **登录账号** | 手机号(由 Tenant Admin 录入员工时自动创建) |
| **初始密码** | 系统统一固定初始密码,首次登录强制修改 |
| **Schema 归属** | 租户 Schema`tenant_{id}` |
**内部岗位子类型Agent Sub-roles**
Agent 是统称,内部通过「角色」区分岗位权限层级。系统内置以下角色(不可删除):
| 角色名称 | Role Code | 典型岗位 | 数据权限范围 |
| ---- | ---------------- | -------- | --------- |
| 置业顾问 | `ROLE_AGENT` | 一线经纪人 | 仅自己的数据 |
| 店管 | `ROLE_STORE_MGR` | 门店店长 | 本门店数据 |
| 区管 | `ROLE_AREA_MGR` | 区域经理 | 本区域数据 |
| 区总 | `ROLE_AREA_DIR` | 区域总监 | 本区域+下属区数据 |
| 副总 | `ROLE_VP` | 副总经理 | 全公司数据(部分) |
| 总经 | `ROLE_GM` | 总经理 | 全公司数据 |
| 其他职能 | `ROLE_OTHER` | 行政/财务/HR | 按需配置 |
**在 PRD 中的出现场景:**
- 房源管理模块的主操作者
- 客源管理模块的主操作者
- 楼盘管理模块的查看用户
- 登录模块的主要使用者
---
## 三、命名规范与替换对照表
以下为历史文档中出现的混乱称谓,与标准称谓的对照关系:
| 历史称谓(禁止继续使用) | 应替换为 | 说明 |
| ------------ | ----------------------- | ---------------- |
| 超级管理员 | Platform Admin平台超级管理员 | 仅指平台层 |
| 平台管理员 | Platform Admin平台超级管理员 | 同上 |
| 系统管理员 | Tenant Admin租户管理员 | 租户层管理员 |
| 管理员(泛称) | Tenant Admin租户管理员 | 明确指向租户层 |
| HR 管理员 | Tenant Admin租户管理员 | 同一人,不区分子角色 |
| HR 行政 | Tenant Admin租户管理员 | 同上 |
| 一线经纪人 | Agent经纪人 | 统称,角色层面才区分岗位 |
| 置业顾问(作为用户称谓) | Agent经纪人 | 置业顾问仅作为「内置角色名」使用 |
**规则说明:**
1. PRD User Story 的 **As a** 部分:使用中文称谓(`Tenant Admin租户管理员``Agent经纪人`
2. 权限矩阵、DATA_MODEL 注释:使用 Persona Code`TENANT_ADMIN``AGENT`
3. 角色名称(置业顾问、店管、总经等)**仅在角色管理相关语境中**出现,不作为用户身份称谓
4. `Platform Admin` 在各 PRD 中尽量少出现,其功能属于「系统管理模块」,不在各子模块 PRD 内展开
---
## 四、快速索引
| 场景 | 使用 |
|------|------|
| PRD User Story 主语 | `Tenant Admin租户管理员` / `Agent经纪人` |
| 错误提示文案(面向用户) | 「请联系您的租户管理员」 |
| DATA_MODEL 注释 | `created_by: 创建该记录的 Agent经纪人用户 ID` |
| 权限矩阵行标题 | 使用角色名(置业顾问 / 店管 / 总经 ... |
| 代码注释 / 枚举值 | `PLATFORM_ADMIN` / `TENANT_ADMIN` / `AGENT` |
| 日志、审计记录 | `operator_type: PLATFORM_ADMIN / TENANT_ADMIN / AGENT` |

View File

@@ -0,0 +1,287 @@
# Fonrey 房睿 — MVP 范围书
**Status**: Draft
**Author**: Product Team
**Last Updated**: 2026-04-24
**Version**: 1.0
> **For AI assistants**: 本文件定义 Phase 1MVP的边界。在任何功能实现前先对照本文确认是否在范围内。范围外的功能禁止在 MVP 阶段实现。
## 变更历史
| 版本 | 日期 | 作者 | 变更说明 |
|------|------|------|---------|
| v1.0 | 2026-04-24 | Product Team | 初稿:定义 Phase 1MVP边界与范围 |
---
## 1. 产品背景与目标
**Fonrey房睿** 是一套面向中小型房产经纪公司的 B2B SaaS 管理平台,解决以下核心痛点:
- 房源/客源信息散乱,全靠人工记录
- 跟进记录缺失,数据流失严重
- 重复录入浪费大量经纪人时间
- 无法支撑 89,000+ 数据量级下的高效房客匹配
**MVP 目标**:在一家种子客户(单租户)环境下,完整跑通"录入房源 → 录入客源 → 匹配带看 → 成交"的核心业务链路。
---
## 2. MVP 核心功能清单Phase 1 必须实现)
### 2.1 优先级定义
| 优先级 | 含义 |
|--------|------|
| **P0** | MVP 上线前必须完成,阻断核心业务链路 |
| **P1** | MVP 上线后第一个迭代周期内完成 |
| **P2** | 已规划,列入路线图但不阻断上线 |
---
### 2.2 模块优先级矩阵
#### 🏠 房源管理
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 录入住宅(二手出售/出租) | **P0** | 核心业务入口 |
| 房源列表(二手&租赁) | **P0** | 含筛选、排序、分页 |
| 房源详情页 | **P0** | 含基本信息、产证、交易信息展示 |
| 跟进记录(全部/写入/修改/其他) | **P0** | 含钥匙、委托、实勘 |
| 图片管理(相册上传/分类/排序) | **P0** | 核心房源内容 |
| 业主联系人管理 | **P0** | 含新增/编辑/查看同业主房源 |
| 价格调整(调价/调价记录) | **P0** | 核心运营操作 |
| 房源状态变更(在售/暂缓/成交/下架) | **P0** | 状态机核心 |
| 房源维护完成度(诊断面板) | **P1** | 提升数据质量 |
| 敏感信息跟进(查看权限控制) | **P1** | 需配合权限模块 |
| 附件管理 | **P1** | 非阻断性 |
| 市场报盘 | **P1** | 运营辅助功能 |
| 价格解读 | **P1** | 分析辅助 |
| 录入别墅/商铺/商住/写字楼/其他 | **P2** | 住宅优先,商业类低频 |
| 全部商铺列表 / 全部写字楼列表 | **P2** | 配合 P2 录入功能 |
| 房源广场 | **P2** | 跨租户/公共池功能 |
#### 🏙️ 楼盘管理
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 楼盘列表 + 楼盘详情(楼盘信息/楼栋/结构) | **P0** | 房源数据底座,必须先行 |
| 区域管理(城区/商圈) | **P0** | 房源关联必须 |
| 楼盘照片管理 | **P1** | 数据完善 |
| 楼盘价格走势 | **P1** | 分析辅助 |
| 周边配套(学校管理) | **P1** | 补充信息 |
| 应用数据标准 | **P2** | 明确不做 |
#### 👥 客源管理
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 录入私客(求购/求租) | **P0** | 核心业务 |
| 私客列表(全部/求购/求租) | **P0** | 含筛选、排序 |
| 私客详情(基本信息/需求信息) | **P0** | |
| 跟进记录(全部/写入/修改/其他) | **P0** | |
| 带看管理(预约带看/新增带看) | **P0** | 房客匹配核心 |
| 联系人管理 | **P0** | |
| 客源状态变更(改等级/改状态) | **P0** | |
| 转公客 / 转成交 / 转无效 | **P0** | 生命周期核心 |
| 二手配房(智能匹配) | **P1** | 核心价值,但可后续迭代 |
| 客源解读 | **P1** | AI 辅助分析 |
| 客源信息概览 | **P1** | 汇总视图 |
| 客源收藏夹 | **P1** | 辅助功能 |
| 公客管理 | **P2** | 私客优先 |
| 成交客管理 | **P2** | |
| 暂缓私客 | **P2** | |
#### 🏢 组织人事
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 公司组织结构(部门/门店树) | **P0** | 权限系统基础 |
| 员工列表/员工详情 | **P0** | |
| 员工入职/账号创建 | **P0** | |
| 员工离职 / 调动 | **P1** | |
| 员工通讯录 | **P1** | |
| 异动记录 | **P1** | |
| 奖惩记录 | **P2** | |
| 职务管理 | **P1** | |
| 门店分布地图 | **P2** | |
#### 🔐 权限管理
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 角色管理(预设角色 + 自定义角色) | **P0** | 权限基础 |
| 人员权限列表 | **P0** | |
| 角色批量分配 | **P0** | |
| 功能权限(菜单级) | **P0** | |
| 数据权限(部门/个人/全司) | **P0** | |
| 字段级权限(敏感字段可见性) | **P1** | 配合房源/客源敏感信息 |
| 个人特定权限覆盖 | **P1** | |
#### 🔑 用户登录
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 账号密码登录 | **P0** | |
| 多租户识别(子域名/域名) | **P0** | |
| Token 管理 / 会话超时 | **P0** | |
| 短信验证码登录 | **P1** | |
| 密码重置 | **P1** | |
| 记住登录状态 | **P1** | |
#### ⚙️ 系统配置
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 首页设置 | **P1** | |
| 房源设置(字段必填/自定义字段/标签) | **P0** | 影响录入表单 |
| 相关方设置 | **P1** | |
| 客源设置(基本配置/参数配置) | **P1** | |
| 人事OA设置 | **P2** | 依赖人事OA模块完整规划本期不实现 |
| 交易设置 | **P2** | 依赖交易模块完整规划,本期不实现 |
| 财务设置 | **Out of Scope** | 消费方「财务管理模块」为 Out of Scope配置面板无意义随模块一并排除 |
| 合同设置 | **Out of Scope** | 消费方「合同管理模块」为 Out of Scope配置面板无意义随模块一并排除 |
#### 🖥️ 系统管理(运营后台)
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 租户管理(开通/暂停/配置) | **P1** | 单租户种子阶段可手动 |
| 系统健康监控 | **P1** | |
| 操作审计日志 | **P2** | |
| 灰度发布 / 滚动升级 | **P2** | |
#### 💻 客户端发布
| 功能 | 优先级 | 说明 |
|------|--------|------|
| Windows 桌面客户端(内置浏览器) | **P1** | 种子客户使用 Web 端可先行 |
| 自动更新机制 | **P1** | 配合客户端 |
---
## 3. 非目标Out of Scope — MVP 阶段绝对不做)
以下功能在 MVP 阶段**明确不实现**AI 生成代码时不得为这些功能预留接口或引入相关依赖:
| 功能 | 原因 |
|------|------|
| 移动端适配 | v2 规划 |
| 新房模块(新房管理/新房设置) | 独立模块,后续版本 |
| 合同管理模块 | 独立模块,后续版本 |
| 财务管理/提成结算 | 独立模块,后续版本 |
| 三网发布(安居客/链家/贝壳对接) | 独立模块,后续版本 |
| 数据报表/行程量化 | 独立模块,后续版本 |
| 在线充值/增值服务 | 独立模块,后续版本 |
| 任务管理OA任务/入职祝福) | 低优先 |
| 考勤管理 | 独立 HR 模块 |
| 审批流程 | 独立 OA 模块 |
| 智慧大屏 / VR换装 | 增值产品 |
| 房源广场(跨租户公共池) | 多租户复杂场景 |
---
## 4. 用户故事MVP 核心路径)
### Story 1 — 经纪人录入房源
> As a **Agent经纪人**,
> I want to **快速录入一套二手住宅并上传图片和业主联系方式**,
> So that **这套房源的信息能被团队所有成员找到和跟进**.
**验收标准**
- 可在 3 分钟内完成住宅基本信息录入
- 上传图片后自动按分类展示
- 录入后即刻出现在房源列表
---
### Story 2 — 经纪人跟进房源
> As a **Agent经纪人**,
> I want to **对我负责的房源记录每次跟进(面访/电话/钥匙/实勘)**,
> So that **我的跟进历史有据可查,团队不会重复联系同一业主**.
**验收标准**
- 跟进记录按时间线倒序展示
- 支持写入跟进、修改跟进、其他跟进(钥匙/委托/实勘)
- 敏感信息跟进只对有权限的人员可见
---
### Story 3 — 经纪人录入客源
> As a **Agent经纪人**,
> I want to **录入意向购房/租房客户并跟进其需求变化**,
> So that **我能在合适时机将客户与合适房源匹配**.
**验收标准**
- 区分求购/求租两种意向
- 支持跟进记录
- 可安排带看并记录带看结果
---
### Story 4 — 转成交
> As a **Agent经纪人**,
> I want to **将已达成交易的客源标记为"成交"并关联成交房源**,
> So that **成交数据进入系统留存,房源状态自动更新**.
**验收标准**
- 转成交时必须选择关联房源
- 成交后客源状态自动变为"成交客"
- 关联房源状态建议变更为"成交"(可手动确认)
---
### Story 5 — 店长查看团队数据
> As a **门店店长**,
> I want to **查看本门店所有员工的房源和客源列表**,
> So that **我能掌握团队整体情况并合理分配资源**.
**验收标准**
- 数据权限按部门隔离,店长可见本门店数据
- 可筛选查看特定员工的房源/客源
- 无法看到其他门店的数据
---
## 5. MVP 技术边界
| 约束 | 决策 |
|------|------|
| 租户数 | **单租户**种子阶段,多租户架构已就位但不激活多租户切换 UI |
| 数据量 | 目标支撑 **89,000 条**房源,测试阶段以 10,000 条压测 |
| 浏览器支持 | Chrome 最新版 / Edge 最新版,不支持 IE |
| 语言 | 简体中文,不做国际化 |
| 移动端 | **不做**Web 端 Desktop-first |
| 导出 | Excel/CSV 导出通过 Celery 异步,不超时 |
---
## 6. MVP 交付检查清单
在 MVP 正式上线前,以下项目必须全部勾选:
- [ ] 房源录入(住宅)完整流程可用
- [ ] 房源列表可筛选/排序/分页
- [ ] 客源录入(求购/求租)完整流程可用
- [ ] 带看创建与记录可用
- [ ] 转成交流程可用
- [ ] 楼盘数据可录入(为房源提供底座)
- [ ] 员工账号可创建/分配角色
- [ ] 权限隔离:经纪人只能看自己数据,店长能看本店数据
- [ ] 89,000 条数据量下列表查询 < 2 秒(含索引优化)
- [ ] 图片上传到 Cloudflare R2 可用
- [ ] 多租户 Schema 隔离验证通过
---
## 7. 版本路线图
| 版本 | 目标 | 核心功能 |
|------|------|---------|
| **v0.1 MVP** | 单租户种子验证 | P0 功能全部上线 |
| **v0.2** | 功能完善 | P1 功能上线,开始多租户测试 |
| **v0.3** | 商业化就绪 | Windows 客户端、多租户正式开放、系统配置完善 |
| **v1.0** | 正式发布 | 新房模块、合同/财务模块路线图确认 |

860
Project/fonrey/PRD/TASK.md Normal file
View File

@@ -0,0 +1,860 @@
## Project Task Board
### 项目状态总览
- 产品名称Fonrey 房睿
- 当前阶段MVP Phase 1
- 技术栈Django 4.x + HTMX + Alpine.js + Tailwind CSS + PostgreSQL 16 + Redis + Celery + Cloudflare R2
- 最后更新2026-04-26
---
### Task 索引总览
> 点击 US 编号可直接跳转到对应 Task 详情。
#### Phase 1 — MVPP0
| US 编号 | 模块 | Task 描述 | 状态 |
|---|---|---|---|
| [US-ACCOUNT-001](#US-ACCOUNT-001-经纪人管理员使用账号密码登录系统) | 用户登录 | 经纪人/管理员使用账号密码登录系统 | [ ] |
| [US-ACCOUNT-002](#US-ACCOUNT-002-系统识别多租户子域名域名路由) | 用户登录 | 系统识别多租户(子域名/域名路由) | [ ] |
| [US-ACCOUNT-003](#US-ACCOUNT-003-系统管理-Token-与会话超时) | 用户登录 | 系统管理 Token 与会话超时 | [ ] |
| [US-COMPLEX-001](#US-COMPLEX-001-管理员录入与维护楼盘基础信息) | 楼盘管理 | 管理员录入与维护楼盘基础信息 | [ ] |
| [US-COMPLEX-002](#US-COMPLEX-002-经纪人查看楼盘列表与详情) | 楼盘管理 | 经纪人查看楼盘列表与详情 | [ ] |
| [US-COMPLEX-003](#US-COMPLEX-003-管理员维护区域管理城区商圈) | 楼盘管理 | 管理员维护区域管理(城区/商圈) | [ ] |
| [US-PROPERTY-001](#US-PROPERTY-001-经纪人录入二手住宅出售出租) | 房源管理 | 经纪人录入二手住宅(出售/出租) | [ ] |
| [US-PROPERTY-002](#US-PROPERTY-002-经纪人查看与筛选房源列表) | 房源管理 | 经纪人查看与筛选房源列表 | [ ] |
| [US-PROPERTY-003](#US-PROPERTY-003-经纪人查看房源详情页) | 房源管理 | 经纪人查看房源详情页 | [ ] |
| [US-PROPERTY-004](#US-PROPERTY-004-经纪人写入与查看房源跟进记录) | 房源管理 | 经纪人写入与查看房源跟进记录 | [ ] |
| [US-PROPERTY-005](#US-PROPERTY-005-经纪人管理房源图片上传分类排序) | 房源管理 | 经纪人管理房源图片(上传/分类/排序) | [ ] |
| [US-PROPERTY-006](#US-PROPERTY-006-经纪人管理业主联系人) | 房源管理 | 经纪人管理业主联系人 | [ ] |
| [US-PROPERTY-007](#US-PROPERTY-007-经纪人调整房源价格) | 房源管理 | 经纪人调整房源价格 | [ ] |
| [US-PROPERTY-008](#US-PROPERTY-008-经纪人变更房源状态) | 房源管理 | 经纪人变更房源状态 | [ ] |
| [US-CLIENT-001](#US-CLIENT-001-经纪人录入新私客) | 客源管理 | 经纪人录入新私客 | [ ] |
| [US-CLIENT-002](#US-CLIENT-002-经纪人查看与筛选私客列表全部求购求租) | 客源管理 | 经纪人查看与筛选私客列表(全部/求购/求租) | [ ] |
| [US-CLIENT-003](#US-CLIENT-003-经纪人批量操作私客列表) | 客源管理 | 经纪人批量操作私客列表 | [ ] |
| [US-CLIENT-004](#US-CLIENT-004-经纪人查看私客详情页) | 客源管理 | 经纪人查看私客详情页 | [ ] |
| [US-CLIENT-005](#US-CLIENT-005-经纪人查看与编辑需求信息) | 客源管理 | 经纪人查看与编辑需求信息 | [ ] |
| [US-CLIENT-006](#US-CLIENT-006-经纪人写入与查看跟进记录) | 客源管理 | 经纪人写入与查看跟进记录 | [ ] |
| [US-CLIENT-007](#US-CLIENT-007-经纪人管理带看记录预约带看新增带看) | 客源管理 | 经纪人管理带看记录(预约带看/新增带看) | [ ] |
| [US-CLIENT-008](#US-CLIENT-008-经纪人管理客源联系人查看新增编辑) | 客源管理 | 经纪人管理客源联系人(查看/新增/编辑) | [ ] |
| [US-CLIENT-009](#US-CLIENT-009-经纪人修改客源等级) | 客源管理 | 经纪人修改客源等级 | [ ] |
| [US-CLIENT-010](#US-CLIENT-010-经纪人修改客源状态) | 客源管理 | 经纪人修改客源状态 | [ ] |
| [US-CLIENT-011](#US-CLIENT-011-经纪人手动将私客转为公客) | 客源管理 | 经纪人手动将私客转为公客 | [ ] |
| [US-CLIENT-012](#US-CLIENT-012-经纪人将私客转为成交客) | 客源管理 | 经纪人将私客转为成交客 | [ ] |
| [US-CLIENT-013](#US-CLIENT-013-经纪人将客源标记为无效) | 客源管理 | 经纪人将客源标记为无效 | [ ] |
| [US-CLIENT-014](#US-CLIENT-014-经纪人编辑客源完整信息联系人基础信息需求) | 客源管理 | 经纪人编辑客源完整信息(联系人/基础信息/需求) | [ ] |
| [US-CLIENT-015](#US-CLIENT-015-经纪人管理客源相关员工查看编辑归属人首录人) | 客源管理 | 经纪人管理客源相关员工(查看/编辑归属人/首录人) | [ ] |
| [US-CLIENT-016](#US-CLIENT-016-系统自动将超时无跟进的私客转为公客) | 客源管理 | 系统自动将超时无跟进的私客转为公客 | [ ] |
| [US-CLIENT-017](#US-CLIENT-017-系统自动检测重复客源并提示) | 客源管理 | 系统自动检测重复客源并提示 | [ ] |
| [US-ORG-001](#US-ORG-001-管理员维护公司组织结构部门门店树) | 组织人事 | 管理员维护公司组织结构(部门/门店树) | [ ] |
| [US-ORG-002](#US-ORG-002-管理员查看与维护员工列表) | 组织人事 | 管理员查看与维护员工列表 | [ ] |
| [US-ORG-003](#US-ORG-003-管理员办理员工入职并创建系统账号) | 组织人事 | 管理员办理员工入职并创建系统账号 | [ ] |
| [US-PERMISSION-001](#US-PERMISSION-001-管理员配置角色预设角色自定义角色) | 权限管理 | 管理员配置角色(预设角色/自定义角色) | [ ] |
| [US-PERMISSION-002](#US-PERMISSION-002-管理员查看与管理人员权限列表) | 权限管理 | 管理员查看与管理人员权限列表 | [ ] |
| [US-PERMISSION-003](#US-PERMISSION-003-管理员批量为员工分配角色) | 权限管理 | 管理员批量为员工分配角色 | [ ] |
| [US-PERMISSION-004](#US-PERMISSION-004-系统执行功能权限控制菜单级) | 权限管理 | 系统执行功能权限控制(菜单级) | [ ] |
| [US-PERMISSION-005](#US-PERMISSION-005-系统执行数据权限控制部门个人全司) | 权限管理 | 系统执行数据权限控制(部门/个人/全司) | [ ] |
| [US-SETTING-001-A](#US-SETTING-001-A-管理员配置可选枚举值-Lookup-Items) | 系统配置 | 管理员配置可选枚举值Lookup Items | [ ] |
| [US-SETTING-001-B](#US-SETTING-001-B-管理员配置房源字段必填规则) | 系统配置 | 管理员配置房源字段必填规则 | [ ] |
| [US-SETTING-001-C](#US-SETTING-001-C-管理员配置客源录入规则) | 系统配置 | 管理员配置客源录入规则(查重范围/必填字段) | [ ] |
#### Phase 2 — 增强功能P1
| US 编号 | 模块 | Task 描述 | 状态 |
|---|---|---|---|
| [US-ACCOUNT-010](#US-ACCOUNT-010-经纪人使用短信验证码登录) | 用户登录 | 经纪人使用短信验证码登录 | [ ] |
| [US-ACCOUNT-011](#US-ACCOUNT-011-经纪人重置账号密码) | 用户登录 | 经纪人重置账号密码 | [ ] |
| [US-ACCOUNT-012](#US-ACCOUNT-012-系统记住用户登录状态) | 用户登录 | 系统记住用户登录状态 | [ ] |
| [US-COMPLEX-010](#US-COMPLEX-010-管理员管理楼盘照片) | 楼盘管理 | 管理员管理楼盘照片 | [ ] |
| [US-COMPLEX-011](#US-COMPLEX-011-管理员维护楼盘价格走势) | 楼盘管理 | 管理员维护楼盘价格走势 | [ ] |
| [US-COMPLEX-012](#US-COMPLEX-012-管理员维护周边配套学校管理) | 楼盘管理 | 管理员维护周边配套(学校管理) | [ ] |
| [US-PROPERTY-010](#US-PROPERTY-010-经纪人查看房源维护完成度诊断面板) | 房源管理 | 经纪人查看房源维护完成度(诊断面板) | [ ] |
| [US-PROPERTY-011](#US-PROPERTY-011-经纪人管理敏感信息跟进权限控制) | 房源管理 | 经纪人管理敏感信息跟进(权限控制) | [ ] |
| [US-PROPERTY-012](#US-PROPERTY-012-经纪人管理房源附件) | 房源管理 | 经纪人管理房源附件 | [ ] |
| [US-PROPERTY-013](#US-PROPERTY-013-经纪人查看房源市场报盘) | 房源管理 | 经纪人查看房源市场报盘 | [ ] |
| [US-PROPERTY-014](#US-PROPERTY-014-经纪人查看房源价格解读) | 房源管理 | 经纪人查看房源价格解读 | [ ] |
| [US-CLIENT-020](#US-CLIENT-020-经纪人使用二手配房功能查看匹配房源) | 客源管理 | 经纪人使用二手配房功能查看匹配房源 | [ ] |
| [US-CLIENT-021](#US-CLIENT-021-经纪人查看客源解读AI行为分析) | 客源管理 | 经纪人查看客源解读AI行为分析 | [ ] |
| [US-CLIENT-022](#US-CLIENT-022-经纪人将重点客源收藏至收藏夹) | 客源管理 | 经纪人将重点客源收藏至收藏夹 | [ ] |
| [US-CLIENT-023](#US-CLIENT-023-经纪人通过快捷入口编辑客源基础信息) | 客源管理 | 经纪人通过快捷入口编辑客源基础信息 | [ ] |
| [US-CLIENT-024](#US-CLIENT-024-经纪人查看客源操作日志) | 客源管理 | 经纪人查看客源操作日志 | [ ] |
| [US-ORG-010](#US-ORG-010-管理员办理员工离职与调动) | 组织人事 | 管理员办理员工离职与调动 | [ ] |
| [US-ORG-011](#US-ORG-011-管理员维护员工通讯录) | 组织人事 | 管理员维护员工通讯录 | [ ] |
| [US-ORG-012](#US-ORG-012-管理员管理员工职务) | 组织人事 | 管理员管理员工职务 | [ ] |
| [US-PERMISSION-010](#US-PERMISSION-010-管理员配置字段级权限敏感字段可见性) | 权限管理 | 管理员配置字段级权限(敏感字段可见性) | [ ] |
| [US-PERMISSION-011](#US-PERMISSION-011-管理员配置个人特定权限覆盖) | 权限管理 | 管理员配置个人特定权限覆盖 | [ ] |
| [US-SETTING-010](#US-SETTING-010-管理员配置首页展示内容) | 系统配置 | 管理员配置首页展示内容 | [ ] |
| [US-SETTING-011](#US-SETTING-011-管理员配置相关方规则) | 系统配置 | 管理员配置相关方规则 | [ ] |
| [US-SETTING-012](#US-SETTING-012-管理员配置客源相关参数) | 系统配置 | 管理员配置客源相关参数 | [ ] |
| [US-SYSTEM-010](#US-SYSTEM-010-Platform Admin平台超级管理员管理租户开通暂停配置) | 系统管理 | Platform Admin平台超级管理员管理租户开通/暂停/配置) | [ ] |
| [US-SYSTEM-011](#US-SYSTEM-011-Platform Admin平台超级管理员监控系统健康状态) | 系统管理 | Platform Admin平台超级管理员监控系统健康状态 | [ ] |
| [US-RELEASE-010](#US-RELEASE-010-系统发布Windows桌面客户端安装包) | 客户端发布 | 系统发布Windows桌面客户端安装包 | [ ] |
| [US-RELEASE-011](#US-RELEASE-011-客户端自动检测并更新至最新版本) | 客户端发布 | 客户端自动检测并更新至最新版本 | [ ] |
#### Phase 3 — 路线图功能P2
| US 编号 | 模块 | Task 描述 | 状态 |
|---|---|---|---|
| [US-PROPERTY-020](#US-PROPERTY-020-经纪人录入别墅商铺商住写字楼其他类型房源) | 房源管理 | 经纪人录入别墅/商铺/商住/写字楼/其他类型房源 | [ ] |
| [US-PROPERTY-021](#US-PROPERTY-021-经纪人查看全部商铺写字楼列表) | 房源管理 | 经纪人查看全部商铺/写字楼列表 | [ ] |
| [US-COMPLEX-020](#US-COMPLEX-020-管理员使用应用数据标准功能) | 楼盘管理 | 管理员使用应用数据标准功能 | [ ] |
| [US-CLIENT-030](#US-CLIENT-030-经纪人查看与管理公客列表) | 客源管理 | 经纪人查看与管理公客列表 | [ ] |
| [US-CLIENT-031](#US-CLIENT-031-经纪人查看与管理成交客列表) | 客源管理 | 经纪人查看与管理成交客列表 | [ ] |
| [US-CLIENT-032](#US-CLIENT-032-经纪人管理暂缓私客) | 客源管理 | 经纪人管理暂缓私客 | [ ] |
| [US-ORG-020](#US-ORG-020-管理员查看员工异动记录) | 组织人事 | 管理员查看员工异动记录 | [ ] |
| [US-ORG-021](#US-ORG-021-管理员管理员工奖惩记录) | 组织人事 | 管理员管理员工奖惩记录 | [ ] |
| [US-ORG-022](#US-ORG-022-管理员查看门店分布地图) | 组织人事 | 管理员查看门店分布地图 | [ ] |
| [US-SETTING-020](#US-SETTING-020-管理员配置人事OA相关参数) | 系统配置 | 管理员配置人事OA相关参数 | [ ] |
| [US-SETTING-021](#US-SETTING-021-管理员配置交易规则) | 系统配置 | 管理员配置交易规则 | [ ] |
| ~~US-SETTING-022~~ | ~~系统配置~~ | ~~管理员配置财务规则~~**已移出,财务模块 Out of Scope** | ~~[ ]~~ |
| ~~US-SETTING-023~~ | ~~系统配置~~ | ~~管理员配置合同模板~~**已移出,合同模块 Out of Scope** | ~~[ ]~~ |
| [US-SYSTEM-020](#US-SYSTEM-020-Platform Admin平台超级管理员查看操作审计日志) | 系统管理 | Platform Admin平台超级管理员查看操作审计日志 | [ ] |
| [US-SYSTEM-021](#US-SYSTEM-021-Platform Admin平台超级管理员管理灰度发布滚动升级) | 系统管理 | Platform Admin平台超级管理员管理灰度发布/滚动升级 | [ ] |
---
## Phase 1 - MVPP0上线前必须完成
---
### 用户登录
##### US-ACCOUNT-001 经纪人管理员使用账号密码登录系统
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - 账号密码登录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/登录管理/登录_UI.md`
- 状态:[ ]
- 验收标准:输入正确账号密码后跳转首页;密码错误时展示"账号或密码错误"提示连续错误5次后账号锁定提示登录成功后 Token 写入 Cookie
##### US-ACCOUNT-002 系统识别多租户子域名域名路由
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - 多租户识别
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 状态:[ ]
- 验收标准:访问不同子域名时系统自动切换对应租户 Schema非法/不存在子域名返回404页面跨租户请求被拦截并返回403
##### US-ACCOUNT-003 系统管理 Token 与会话超时
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - Token 管理/会话超时
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 状态:[ ]
- 验收标准会话超时后自动跳转登录页Token 刷新机制正常工作;登出后 Token 立即失效,再次请求跳转登录页
---
### 楼盘管理
##### US-COMPLEX-001 管理员录入与维护楼盘基础信息
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 楼盘信息管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/楼盘管理/楼盘详情_UI.md`
- 状态:[ ]
- 验收标准:可录入楼盘名称、地址、楼栋、结构等基础字段;保存成功后楼盘出现在楼盘列表;必填字段未填时高亮错误提示;楼盘编号系统自动生成且唯一
##### US-COMPLEX-002 经纪人查看楼盘列表与详情
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 楼盘列表/楼盘详情
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/楼盘管理/楼盘列表_UI.md`
- 状态:[ ]
- 验收标准:楼盘列表支持按名称/地址关键词搜索支持分页默认20条/页);点击楼盘名称跳转详情页;详情页展示楼盘基本信息、楼栋结构信息
##### US-COMPLEX-003 管理员维护区域管理城区商圈
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 区域管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 状态:[ ]
- 验收标准:可新增/编辑/删除城区和商圈;商圈必须归属于城区;区域数据被房源和客源模块正确关联引用;删除有关联数据的区域时系统给出警告
---
### 房源管理
##### US-PROPERTY-001 经纪人录入二手住宅出售出租
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 录入住宅(二手出售/出租)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/房源管理/新增房源_UI.md`
- 状态:[ ]
- 验收标准可在3分钟内完成住宅基本信息录入必填字段楼盘/楼层/面积/价格)未填时高亮错误提示;保存成功后跳转房源详情页并显示"保存成功";录入后即刻出现在房源列表
##### US-PROPERTY-002 经纪人查看与筛选房源列表
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 房源列表(二手&租赁)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/房源管理/房源列表_UI.md`
- 状态:[ ]
- 验收标准:列表支持按楼盘名/业主姓名/电话/房源编号关键词搜索;支持状态/区域/价格/房型多维度组合筛选列表分页默认20条/页且89000条数据下查询响应<2秒支持导出当前筛选结果为Excel
##### US-PROPERTY-003 经纪人查看房源详情页
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 房源详情页
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/房源管理/房源详情_UI.md`
- 状态:[ ]
- 验收标准:详情页完整展示基本信息、产证信息、交易信息;号码默认打码,点击"查看号码"后解密展示并记录审计日志;页面各功能 Tab 可正常切换
##### US-PROPERTY-004 经纪人写入与查看房源跟进记录
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 跟进记录(全部/写入/修改/其他)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:跟进记录按时间线倒序展示;支持写入跟进/修改跟进/其他跟进(钥匙/委托/实勘子Tab切换跟进内容最少6字校验写入成功后记录实时出现在列表顶部
##### US-PROPERTY-005 经纪人管理房源图片上传分类排序
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 图片管理(相册上传/分类/排序)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:支持上传 bmp/jpg/png/gif 格式图片单文件最大20MB上传成功后自动按分类展示支持拖拽排序封面图可手动指定
##### US-PROPERTY-006 经纪人管理业主联系人
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 业主联系人管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:支持新增/编辑业主联系人(姓名/电话/微信);手机号加密存储,展示时默认打码;可查看同业主名下其他房源;至少保留一个联系人
##### US-PROPERTY-007 经纪人调整房源价格
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 价格调整(调价/调价记录)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:调价弹窗需填写新价格和调价原因;调价成功后房源列表和详情页价格实时更新;调价记录以时间线形式留存且不可删除;调价幅度在列表中以"降价XX万"标签展示
##### US-PROPERTY-008 经纪人变更房源状态
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 房源状态变更(在售/暂缓/成交/下架)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:状态变更严格遵循状态机流转规则(在售→暂缓/成交/下架);每次状态变更需填写原因;状态变更后列表状态标签实时更新;状态变更记录写入跟进日志
---
### 客源管理
##### US-CLIENT-001 经纪人录入新私客
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 1经纪人录入新私客5.2 录入私客
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/新增客源_UI.md`
- 状态:[ ]
- 验收标准:录入页面可通过顶部导航「客源」→「+新增私客」或右侧快捷入口「增客」触达联系人1必填姓名/性别/电话1联系人2起可增加删除基础信息必填字段状态/用途/等级/来源)缺填时高亮错误并定位到第一个错误处;保存成功后跳转该客源详情页并显示"保存成功"提示
##### US-CLIENT-002 经纪人查看与筛选私客列表全部求购求租
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 2/3/4经纪人查看与筛选私客列表5.1 客源列表
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源列表_UI.md`
- 状态:[ ]
- 验收标准顶部Tab导航私客/资料客/营销客/成交客/公客及二级Tab求购/求租/暂缓/全部私客)可正常切换;搜索框支持按客源姓名/号码/号码后4位/客源编号/备注检索;筛选栏支持状态/等级/位置/价格/房室等多维度组合筛选列表底部实时显示当前筛选总条数分页默认20条/页89000条数据量下查询<2秒
##### US-CLIENT-003 经纪人批量操作私客列表
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 2列表批量操作5.1.3 批量操作
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源列表_UI.md`
- 状态:[ ]
- 验收标准:勾选客源后激活批量操作按钮(修改相关方/修改来源/删除客源/合并客户);批量删除执行软删除,可在"已删客源"中查看批量修改相关方成功后列表归属人字段实时刷新列表支持导出当前筛选结果为ExcelCelery异步处理
##### US-CLIENT-004 经纪人查看私客详情页
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 6经纪人查看私客详情页Story 15经纪人查看客源信息概览面板
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:详情页顶部展示需求标题+联系人姓名+带看进度标签;右侧固定信息概览面板展示客户编号/委托日期/需求类型等字段且不随页面滚动消失主内容区Tab导航需求信息/跟进记录/带看/客源解读/智能配房)默认激活"需求信息"Tab右侧面板三个主操作按钮打电话/写跟进/报备带看)可正常触发对应流程
##### US-CLIENT-005 经纪人查看与编辑需求信息
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 7经纪人查看与编辑需求信息
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准需求信息Tab展示总价/面积/居室/装修/朝向/楼层/楼龄/意向商圈/意向小区等字段(三栏布局);字段值为空时显示"-"占位符;右上角「编辑」链接点击后字段转为输入框/选择器;保存成功后返回详情页并刷新需求信息区块
##### US-CLIENT-006 经纪人写入与查看跟进记录
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 8经纪人写入与查看跟进记录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准跟进记录分5个子Tab全部/写入跟进/敏感信息跟进/修改跟进/其他跟进全部Tab支持时间范围筛选及「有录音」「有图片」快速过滤跟进记录以时间线形式按日期分组倒序展示写入跟进时跟进目的支持23项多选跟进内容最少6字校验系统自动生成的操作日志如新增私客/状态变更)出现在"其他跟进"Tab
##### US-CLIENT-007 经纪人管理带看记录预约带看新增带看
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 9经纪人管理带看记录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准带看Tab分"预约"和"带看"两个子Tab新增带看表单必填字段带看时间/带看情况≥6字/带看房源≥1套校验通过才可提交带看记录以时间线展示含带看房源蓝色可点击链接和带看进度标签员工选择器弹层支持组织树搜索和多选带看房源选择器支持按编号/楼盘/业主关键词搜索
##### US-CLIENT-008 经纪人管理客源联系人查看新增编辑
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 23经纪人管理客源联系人
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:联系人面板默认打码显示电话("+86 135\*\*\*\*\*\*\*\*"),点击「查看号码」后展示完整号码并写入敏感信息跟进日志;新增联系人必填字段(姓名/称呼/电话1校验编辑联系人时电话1需点击「查看号码」后才可编辑保存成功后联系人面板实时刷新
##### US-CLIENT-009 经纪人修改客源等级
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 17经纪人修改客源等级
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准点击「改等级」触发弹窗展示当前等级只读和新等级下拉选择器A急迫/B较强/C一般/D较弱/E暂不关注新等级未选择时「确定」按钮置灰保存成功后信息概览面板等级标签实时更新操作日志中自动新增"改等级"记录
##### US-CLIENT-010 经纪人修改客源状态
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 18经纪人修改客源状态
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:点击「改状态」触发弹窗,展示当前状态(只读)、新状态下拉(求购/求租/租购)、等级下拉和必填更改理由文本框;新状态未选或理由未填时「确定」按钮置灰;保存成功后信息概览面板状态标签实时更新;操作日志新增"改状态"记录(含更改理由)
##### US-CLIENT-011 经纪人手动将私客转为公客
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 19经纪人手动将私客转为公客
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准点击「转公客」触发弹窗状态和等级均为必填确认后客源从私客列表移除并进入公客池client_type='public'transfer_to_public_type='manual');操作日志新增"转公客"记录(含操作人/操作时间);权限控制:仅归属人、首录人或有管理权限的店长/经理可操作
##### US-CLIENT-012 经纪人将私客转为成交客
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 20经纪人将私客转为成交客
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:点击「转成交」触发"客户成交"弹窗,必填字段(状态/房源类型/成交房源/成交日期/成交价格/成交方)全部填写后「确定」按钮高亮;成交房源选择浮层支持关键词搜索和区域/状态筛选;提交后客源移入成交客列表,状态更新为"成交";操作日志新增"转成交"记录(含成交信息摘要)
##### US-CLIENT-013 经纪人将客源标记为无效
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 21经纪人将客源标记为无效
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:点击「转无效」触发弹窗,展示蓝色提示框说明"将把所有电话标记无效",无效原因单选(号码无效/同行中介/广告推销/客户无意向/其他)默认选中"号码无效";确认后所有联系人电话标记为无效,客源从私客活跃列表移除;操作日志新增"转无效"记录(含无效原因);权限控制:仅归属人、首录人或有管理权限者可操作
##### US-CLIENT-014 经纪人编辑客源完整信息联系人基础信息需求
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 14经纪人编辑客源信息
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/编辑客源_UI.md`
- 状态:[ ]
- 验收标准编辑页面分三个Tab联系人/基础信息/二手或新房或租房联系人Tab的电话1需点击「查看号码」后才可编辑旁边提供「标记无效」链接基础信息Tab包含需求类型/购房目的/付款方式/名下房产/贷款记录等扩展字段;保存时校验所有必填字段,成功后返回详情页并刷新需求信息区块
##### US-CLIENT-015 经纪人管理客源相关员工查看编辑归属人首录人
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 24经纪人管理客源相关员工
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:相关员工面板展示首录人/归属人的所属门店+小组+姓名及参与时间;点击「编辑」触发弹窗,首录人和归属人均为必填下拉选择器(支持搜索姓名);保存后面板实时更新,操作日志新增"修改相关员工"记录;权限控制:跨团队/跨店修改需店长及以上权限
##### US-CLIENT-016 系统自动将超时无跟进的私客转为公客
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - 关键业务规则:私客自动转公
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准Celery Beat 定时任务每日凌晨执行超过运营配置天数如30天无跟进且非保护客的私客自动转入公客池transfer_to_public_type='auto');自动转公后 client_status_logs 生成一条 to_public 记录;即将过期的私客在列表中显示"即将掉公"提示标签
##### US-CLIENT-017 系统自动检测重复客源并提示
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 2顶部重复检测提示关键业务规则私客手机号唯一性
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准:录入/编辑联系人手机号时实时通过phone_hash检测与现有私客/成交客/公客的重复;客源列表顶部实时显示"私客与成交客重复XX"和"私客与公客重复XX"蓝色可点击链接;点击重复数字链接可查看重复名单
---
### 组织人事
##### US-ORG-001 管理员维护公司组织结构部门门店树
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 公司组织结构(部门/门店树)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:支持新增/编辑/删除部门和门店节点;组织树以层级结构展示(公司→区域→门店→小组);删除有员工的部门时系统提示并阻止操作;组织结构变更实时反映在员工选择器弹层中
##### US-ORG-002 管理员查看与维护员工列表
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 员工列表/员工详情
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:员工列表支持按姓名/手机号关键词搜索;支持按部门/状态筛选;列表展示员工姓名/所属门店/职位/状态等字段;点击员工姓名跳转员工详情页
##### US-ORG-003 管理员办理员工入职并创建系统账号
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 员工入职/账号创建
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:入职表单必填字段(姓名/手机号/所属门店/职位)校验;创建账号后系统自动生成登录密码并可发送给员工;新员工账号立即可登录系统;员工账号与组织树节点正确关联
---
### 权限管理
##### US-PERMISSION-001 管理员配置角色预设角色自定义角色
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 角色管理(预设角色+自定义角色)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准:系统预设角色(经纪人/店长/管理员等)不可删除但可复制;支持创建自定义角色并配置功能权限;角色名称在同租户内唯一;删除自定义角色前需解除所有人员绑定
##### US-PERMISSION-002 管理员查看与管理人员权限列表
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 人员权限列表
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准:人员权限列表展示员工姓名/所属部门/当前角色;支持按部门/角色筛选;列表支持分页;点击员工行可查看详细权限配置
##### US-PERMISSION-003 管理员批量为员工分配角色
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 角色批量分配
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准勾选多名员工后可批量指定角色批量分配成功后所有选中员工角色立即生效分配后员工下次登录或刷新页面权限即更新Redis权限快照失效重载
##### US-PERMISSION-004 系统执行功能权限控制菜单级
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 功能权限(菜单级)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准无权限的菜单在导航中不展示直接访问无权限URL返回403页面权限变更后Redis缓存自动失效用户下一次请求即应用新权限经纪人无法访问管理员专属功能页
##### US-PERMISSION-005 系统执行数据权限控制部门个人全司
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 数据权限(部门/个人/全司)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准经纪人只能看到自己名下的房源和客源数据店长可见本门店所有员工的房源和客源管理员可见全司数据不同数据权限级别的用户查询结果严格隔离不可通过URL参数绕过
---
### 系统配置
##### US-SETTING-001-A 管理员配置可选枚举值 Lookup Items
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置模块PRD.md` - US-SETTING-001-A
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL.md`(待 Atlas 补充 `lookup_items` DDL
- 状态:[ ]
- 验收标准:
- 管理员进入「系统设置 → 参数配置」,页面按模块分组展示所有可配置参数项(客源来源、跟进目的、房源来源)
- 可新增自定义选项,新选项追加至列表末尾并立即对经纪人录入下拉生效(刷新后)
- 系统预制选项(`is_system=True`)不可删除,仅可停用;停用后前端下拉不再展示,历史数据保留并标注「已停用」
- 支持调整选项排序(拖拽或修改排序值),排序变更在保存后前端生效
- 配置保存时主动失效 Redis 缓存 key `{tenant_schema}:setting:lookup:{module}.{key}`,最长 5 分钟延迟
##### US-SETTING-001-B 管理员配置房源字段必填规则
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置模块PRD.md` - US-SETTING-001-B
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL.md`(待 Atlas 补充 `field_requirement_rules` DDL`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:
- 管理员进入「系统设置 → 房源字段规则」,以「用途 × 交易状态」矩阵展示配置(住宅×出售、住宅×出租)
- 每个字段显示当前规则(必填 / 选填 / 隐藏),以三态 Toggle 或 Radio 形式编辑,保存后生效
- 规则应用于录入界面:必填字段显示「*」标记,提交时为空则拦截;隐藏字段不渲染
- 规则变更仅影响新录入,不影响存量房源数据
- MVP 可配置字段:朝向、装修情况、楼层、建筑面积、套内面积、房型、产权年限、车位数
##### US-SETTING-001-C 管理员配置客源录入规则
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置模块PRD.md` - US-SETTING-001-C
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL.md`(待 Atlas 补充 `tenant_settings` DDL`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准:
- 管理员进入「系统设置 → 客源规则」,可配置新增私客时的查重范围:本人(默认)/ 本部门 / 全公司
- 查重规则实时生效:经纪人录入手机号失焦后,系统按当前查重范围加密比对,若重复则提示归属人和录入时间,经纪人可选「仍然录入」或「放弃」
- 可配置客源必填字段开关(等级、来源默认必填;总价区间、居室需求、购房目的默认选填)
- 配置保存时主动失效 Redis 缓存 key `{tenant_schema}:setting:client_rules`,经纪人下次打开录入界面即应用最新规则
---
## Phase 2 - 增强功能P1MVP 后第一迭代)
---
### 用户登录
##### US-ACCOUNT-010 经纪人使用短信验证码登录
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - 短信验证码登录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 状态:[ ]
- 验收标准:点击"发送验证码"后60秒内不可重复点击验证码5分钟内有效输入正确验证码后登录成功跳转首页错误验证码给出明确提示
##### US-ACCOUNT-011 经纪人重置账号密码
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - 密码重置
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 状态:[ ]
- 验收标准通过手机号验证码或邮箱链接验证身份新密码需二次确认且满足强度要求8位以上含字母+数字);重置成功后原会话立即失效需重新登录;重置操作写入审计日志
##### US-ACCOUNT-012 系统记住用户登录状态
- 参考PRD文档`Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md` - 记住登录状态
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_LOGIN.md`
- 状态:[ ]
- 验收标准:勾选"记住我"后关闭浏览器重新打开仍处于登录态记住登录的有效期为7天超过有效期自动跳转登录页管理员可在后台强制让指定账号的所有会话失效
---
### 楼盘管理
##### US-COMPLEX-010 管理员管理楼盘照片
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 楼盘照片管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 状态:[ ]
- 验收标准支持上传楼盘封面图和相册图片格式jpg/png/gif单文件≤20MB上传成功后图片在楼盘详情页展示支持删除图片楼盘封面图可手动设置
##### US-COMPLEX-011 管理员维护楼盘价格走势
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 楼盘价格走势
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 状态:[ ]
- 验收标准:楼盘详情页可查看历史价格走势折线图;价格走势数据按月维度展示;支持手动录入或批量导入历史均价数据
##### US-COMPLEX-012 管理员维护周边配套学校管理
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 周边配套(学校管理)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_COMPLEX.md`
- 状态:[ ]
- 验收标准:可为楼盘关联周边学校(从学校库选择);学校库支持新增/编辑/删除;楼盘详情页展示关联学校列表;学校数据可在客源意向学校字段中搜索引用
---
### 房源管理
##### US-PROPERTY-010 经纪人查看房源维护完成度诊断面板
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 房源维护完成度(诊断面板)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准房源详情页展示数据完整度百分比及未填字段提示列表点击未填字段提示可直接跳转对应编辑区域完整度≥80%时展示绿色状态,<60%时展示红色警告
##### US-PROPERTY-011 经纪人管理敏感信息跟进权限控制
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 敏感信息跟进(查看权限控制)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准敏感信息跟进Tab仅对有权限的角色可见查看敏感信息自动写入审计日志不可删除无权限用户看到Tab时显示"无权限查看"提示而非隐藏Tab
##### US-PROPERTY-012 经纪人管理房源附件
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 附件管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准支持上传PDF/Word/Excel等文档类附件单文件≤50MB附件按上传时间倒序展示支持下载和删除附件附件上传通过Celery异步处理不阻塞主线程
##### US-PROPERTY-013 经纪人查看房源市场报盘
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 市场报盘
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:房源详情页可查看同楼盘其他在售房源的报价分布;展示该楼盘近期成交均价和当前挂牌均价;数据按成交日期倒序展示
##### US-PROPERTY-014 经纪人查看房源价格解读
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 价格解读
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md`
- 状态:[ ]
- 验收标准:房源详情页展示基于同楼盘/同商圈数据的价格合理性分析;展示当前报价与参考价的偏差百分比;无足够数据时展示"暂无参考数据"
---
### 客源管理
##### US-CLIENT-020 经纪人使用二手配房功能查看匹配房源
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 11经纪人使用二手配房功能推荐房源
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准智能配房Tab分"录客配房"和"系统配房"两个子Tab录客配房按优质户型/降价/热门/新上四个分组展示房源卡片;每套房源卡片展示封面图/小区名/户型/面积/售价/标签;点击「分享房源」触发房源分享流程;支持「批量分享」多套房源
##### US-CLIENT-021 经纪人查看客源解读AI行为分析
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 10经纪人查看客源解读
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准客源解读Tab展示活跃行为/活跃时间/购房偏好三个模块购房偏好支持近7日/近30日/近90日三个时间维度切换切换后数据联动刷新价格/户型/面积偏好以圆环图+图例形式展示;无数据时展示"暂无数据"而非报错
##### US-CLIENT-022 经纪人将重点客源收藏至收藏夹
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 16经纪人收藏客源至私客收藏夹
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:点击「☆收藏」触发"选择私客收藏夹"浮层,默认选中"默认收藏夹"支持在浮层内创建新收藏夹名称最多10字超出「创建」按钮置灰收藏成功后图标变为实心★橙色私客列表支持按收藏夹筛选展示
##### US-CLIENT-023 经纪人通过快捷入口编辑客源基础信息
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 22经纪人编辑客源基础信息快捷入口
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 参考UI_Design文档`Project/fonrey/UI_DESIGN/客源管理/客源详情_UI.md`
- 状态:[ ]
- 验收标准:点击信息概览面板「编辑客源」入口触发"编辑基础信息"抽屉浮层;浮层包含需求类型/用途/来源(必填)及购房目的/付款方式/名下房产等选填字段;点击「确定」校验必填字段,保存成功后面板相关字段实时更新;点击「取消」或×关闭不保存
##### US-CLIENT-024 经纪人查看客源操作日志
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 25经纪人查看客源操作日志
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准:点击「查看操作日志」跳转独立日志列表页(面包屑:客源/客源详情/客源操作日志);支持按日期范围/操作人/操作类型筛选;日志列表按操作时间倒序排列,展示操作时间/操作人/操作类型/操作内容日志为只读不支持编辑或删除支持分页默认20条/页)
---
### 组织人事
##### US-ORG-010 管理员办理员工离职与调动
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 员工离职/调动
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:员工离职后账号立即禁用无法登录;离职员工名下房源和客源可批量转移给其他员工;员工调动后所属部门/组织信息实时更新;调动/离职操作写入异动记录
##### US-ORG-011 管理员维护员工通讯录
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 员工通讯录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:通讯录支持按姓名/部门搜索;展示员工姓名/职位/手机号/所属门店;手机号默认打码,有权限才可查看完整号码;支持按部门分组展示
##### US-ORG-012 管理员管理员工职务
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 职务管理
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准:支持新增/编辑/删除职务(如经纪人/店长/区域经理);职务可关联权限角色;删除有在职员工绑定的职务时系统提示并阻止
---
### 权限管理
##### US-PERMISSION-010 管理员配置字段级权限敏感字段可见性
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 字段级权限(敏感字段可见性)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准:可为不同角色配置联系人手机号/证件号码等敏感字段的可见性无查看权限的角色访问时号码始终打码无法点击查看查看敏感字段操作记录在审计日志中字段级权限配置变更后Redis缓存立即失效
##### US-PERMISSION-011 管理员配置个人特定权限覆盖
- 参考PRD文档`Project/fonrey/PRD/权限管理/权限管理模块PRD.md` - 个人特定权限覆盖
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 状态:[ ]
- 验收标准:支持在角色权限基础上为特定员工单独开启或关闭特定权限点;个人覆盖权限优先级高于角色权限;个人权限覆盖配置记录可查看和删除
---
### 系统配置
##### US-SETTING-010 管理员配置首页展示内容
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置.md` - 首页设置
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md`
- 状态:[ ]
- 验收标准:可配置首页展示的统计数据卡片(如今日新增房源/客源数量);配置变更后首页实时生效;不同角色可配置不同的首页视图
##### US-SETTING-011 管理员配置相关方规则
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置.md` - 相关方设置
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md`
- 状态:[ ]
- 验收标准:可配置房源/客源的相关方角色(如协作人/跟进人)及其权限范围;相关方配置影响房源/客源详情页的相关员工区块展示
##### US-SETTING-012 管理员配置客源相关参数
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置.md` - 客源设置(基本配置/参数配置)
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md`
- 状态:[ ]
- 验收标准可配置私客自动转公客的天数阈值可配置客源来源枚举值lookup_items可配置活跃度计算的各阈值天数配置变更后Celery定时任务下次执行时使用新配置
---
### 系统管理(运营后台)
##### US-SYSTEM-010 Platform Admin平台超级管理员管理租户开通暂停配置
- 参考PRD文档`Project/fonrey/PRD/系统管理/系统管理模块PRD.md` - 租户管理(开通/暂停/配置)
- 状态:[ ]
- 验收标准可在运营后台新开通租户自动创建独立PostgreSQL Schema可暂停租户暂停后租户用户无法登录可为租户配置域名/子域名;租户操作记录写入平台操作日志
##### US-SYSTEM-011 Platform Admin平台超级管理员监控系统健康状态
- 参考PRD文档`Project/fonrey/PRD/系统管理/系统管理模块PRD.md` - 系统健康监控
- 状态:[ ]
- 验收标准运营后台展示系统核心指标API响应时间/错误率/Celery队列积压Sentry错误告警正常接收Grafana面板可查看历史监控数据关键指标超阈值时触发告警通知
---
### 客户端发布
##### US-RELEASE-010 系统发布Windows桌面客户端安装包
- 参考PRD文档`Project/fonrey/PRD/发布管理/客户端发布管理模块PRD.md` - Windows桌面客户端
- 状态:[ ]
- 验收标准electron-builder 输出 NSIS .exe 安装包和便携版 .zip安装包经EV证书签名安装时无SmartScreen警告安装包上传至Cloudflare R2并通过CDN分发后端 ClientRelease 表新增一条版本记录
##### US-RELEASE-011 客户端自动检测并更新至最新版本
- 参考PRD文档`Project/fonrey/PRD/发布管理/客户端发布管理模块PRD.md` - 自动更新机制
- 状态:[ ]
- 验收标准客户端启动时及每4小时自动检测 GET /api/client/updates/latest/ 接口有新版本时后台静默下载下载完成后提示用户重启下载完成后校验SHA256与服务端返回一致才允许安装强制更新标记时用户无法跳过更新
---
## Phase 3 - 路线图功能P2已规划未排期
---
### 房源管理
##### US-PROPERTY-020 经纪人录入别墅商铺商住写字楼其他类型房源
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 录入别墅/商铺/商住/写字楼/其他
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-PROPERTY-021 经纪人查看全部商铺写字楼列表
- 参考PRD文档`Project/fonrey/PRD/房源管理/房源管理模块PRD.md` - 全部商铺列表/全部写字楼列表
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
---
### 楼盘管理
##### US-COMPLEX-020 管理员使用应用数据标准功能
- 参考PRD文档`Project/fonrey/PRD/房源管理/楼盘管理模块PRD.md` - 应用数据标准
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
---
### 客源管理
##### US-CLIENT-030 经纪人查看与管理公客列表
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 12经纪人查看与筛选公客列表
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-CLIENT-031 经纪人查看与管理成交客列表
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 13经纪人查看成交客列表
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-CLIENT-032 经纪人管理暂缓私客
- 参考PRD文档`Project/fonrey/PRD/客源管理/客源管理模块PRD.md` - Story 5经纪人管理暂缓私客
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_CLIENT.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
---
### 组织人事
##### US-ORG-020 管理员查看员工异动记录
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 异动记录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-ORG-021 管理员管理员工奖惩记录
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 奖惩记录
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-ORG-022 管理员查看门店分布地图
- 参考PRD文档`Project/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md` - 门店分布地图
- 参考DATA_MODEL文档`Project/fonrey/DATA_MODEL/DATA_MODEL_ORG.md`
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
---
### 系统配置
##### US-SETTING-020 管理员配置人事OA相关参数
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置.md` - 人事OA设置
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-SETTING-021 管理员配置交易规则
- 参考PRD文档`Project/fonrey/PRD/系统配置/系统配置.md` - 交易设置
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### ~~US-SETTING-022 管理员配置财务规则~~
> ⛔ **已移出路线图**:消费方「财务管理/提成结算模块」在 MVP 及近期版本中明确为 Out of Scope见 PRD_MVP.md §3对应的系统配置面板无实际消费方随模块一并排除。若后续财务模块立项本 US 随之恢复。
##### ~~US-SETTING-023 管理员配置合同模板~~
> ⛔ **已移出路线图**:消费方「合同管理模块」在 MVP 及近期版本中明确为 Out of Scope见 PRD_MVP.md §3对应的系统配置面板无实际消费方随模块一并排除。若后续合同模块立项本 US 随之恢复。
---
### 系统管理(运营后台)
##### US-SYSTEM-020 Platform Admin平台超级管理员查看操作审计日志
- 参考PRD文档`Project/fonrey/PRD/系统管理/系统管理模块PRD.md` - 操作审计日志
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
##### US-SYSTEM-021 Platform Admin平台超级管理员管理灰度发布滚动升级
- 参考PRD文档`Project/fonrey/PRD/系统管理/系统管理模块PRD.md` - 灰度发布/滚动升级
- 状态:[ ]
- 验收标准规划中详细验收标准待PRD细化后补充
---
## Phase 4 - 明确不做Out of Scope
> 以下功能在 MVP 阶段及近期版本路线图中明确不实现,仅作备忘。
<!-- OUT OF SCOPE: 移动端适配 - v2 规划 -->
<!-- OUT OF SCOPE: 新房模块(新房管理/新房设置)- 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 合同管理模块 - 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 财务管理/提成结算 - 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 三网发布(安居客/链家/贝壳对接)- 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 数据报表/行程量化 - 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 在线充值/增值服务 - 独立模块,后续版本 -->
<!-- OUT OF SCOPE: 任务管理OA任务/入职祝福)- 低优先 -->
<!-- OUT OF SCOPE: 考勤管理 - 独立 HR 模块 -->
<!-- OUT OF SCOPE: 审批流程 - 独立 OA 模块 -->
<!-- OUT OF SCOPE: 智慧大屏 / VR换装 - 增值产品 -->
<!-- OUT OF SCOPE: 房源广场(跨租户公共池)- 多租户复杂场景 -->
<!-- OUT OF SCOPE: 资料客详细功能 - 后续版本规划 -->
<!-- OUT OF SCOPE: 营销客详细功能(含端口发布/朋友圈分享)- 营销模块另行规划 -->
<!-- OUT OF SCOPE: AI 智能推荐引擎自研 - 基础版基于规则匹配实现 -->
<!-- OUT OF SCOPE: 客源数据对外 API 开放 - 接口规范另行设计 -->
---
### 已完成
(暂无)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,902 @@
# PRD平台管理后台Platform Admin Console
**状态**Draft
**作者**:产品经理
**最后更新**2026-05-02
**版本**v1.1
**关联模块**:权限管理(仅平台侧管理员账号体系)、登录管理(管理员登录与 MFA、所有租户业务模块间接仅作运营/监控对象)
**利益相关方**工程负责人、运营团队、安全合规、客户成功团队、IT 运维
---
## 变更历史
| 版本 | 日期 | 作者 | 变更说明 |
|------|------|------|---------|
| v1.0 | 2026-05-02 | 产品经理 | 由 `PRD/系统管理/系统管理模块PRD.md` (v1.3) 与 `PRD/发布管理/客户端发布管理模块PRD.md` (v1.2) 合并而来;统一面向平台管理员视角,统一规划页面路由与业务 API 操作清单。原两份 PRD 文件同步删除,参见 `ADR-20260502-001`。 |
| v1.1 | 2026-05-02 | 产品经理 | **修复 P-B-1**:全部用户故事补齐功能编号 PA-001 ~ PA-011与登录管理 PRD v3.0「功能 ID + 验收标准」风格对齐建立需求→测试用例→Bug 单可追溯链条。**修复 X-1**:新增 PA-011「租户注销 SOP」用户故事Persona E覆盖删除前数据导出确认、双人复核审批流程、30 天冷静期可撤销机制;同步更新租户状态机(新增 Pending Cancellation 状态节点、权限矩阵新增注销审批相关操作行、§5.1.4 删除章节(引用 PA-011 SOP、§5.4 页面导航逻辑(注销审批待办流程)、租户列表筛选(新增 Pending Cancellation / Pending Delete 状态、§5.5.1 API 操作清单(新增注销发起、审批、撤销、硬删除操作项)。 |
> **本 PRD 取代以下两份历史文档**
> - `PRD/系统管理/系统管理模块PRD.md`v1.3,已删除,被本 PRD 替代)
> - `PRD/发布管理/客户端发布管理模块PRD.md`v1.2,已删除,被本 PRD 替代)
>
> 决策依据:`ADR-20260502-001`REQ
---
## 1. 问题陈述
### 1.1 背景
Fonrey房睿是面向房产经纪公司的 B2B SaaS 平台,采用 `django-tenants` 实现 PostgreSQL Schema 级别多租户隔离,同时通过 Electron 桌面客户端为终端经纪人提供统一入口。随着平台商业化推进,**平台运营团队**需要一套独立、跨租户的「平台管理后台」Platform Admin Console来统一承担
1. 租户全生命周期管理开通、挂起、注销、License 续期)
2. 平台版本治理(基础数据版本、租户数据版本、灰度升级、回滚)
3. 数据备份与恢复(灾难场景应对)
4. 客户端发布治理(桌面客户端版本上线、强制升级、版本分布监控)
5. 操作审计与合规
6. 平台管理员账号与安全MFA、IP 白名单、强制登出)
平台管理后台部署于平台公共域名(与租户应用域名隔离),数据落在 `public` schema跨租户。Tenant Admin 与 Agent 一律无访问权限。
### 1.2 核心痛点
| 痛点 | 影响方 | 当前代价 |
|------|--------|---------|
| 无统一租户管理界面,开通 / 挂起 / 注销依赖人工脚本 | 运营团队 | 高错误风险,操作耗时 |
| 平台版本升级需停机,影响所有租户 | 全量用户 | SLA 违约风险 |
| 数据备份无策略,灾难恢复依赖人工 | 平台稳定性 | 数据丢失风险 |
| 高危操作无审计轨迹 | 管理层 / 合规 | 法律与客户信任风险 |
| 客户端浏览器版本碎片化,兼容性问题层出 | Agent / 客户成功 | 支持成本飙升 |
| 客户端无统一发布渠道,旧版本与新后端 API 不兼容 | 全量用户 | 数据错误风险 |
| 无法跨租户掌握客户端版本分布与覆盖率 | Platform Admin | 无法定向催更 |
### 1.3 目标用户
| 角色 | 使用场景 | 频率 |
|------|---------|------|
| Platform Admin平台超级管理员 | 全局配置、高危操作、版本上线、强制升级 | 低频(每周) |
| 运营人员Ops Operator | 日常租户管理、监控巡检、客户咨询响应 | 高频(每日) |
| 只读审计员Read-only Auditor | 日志查询、合规报告导出 | 中频(每周) |
> 本 PRD **不涉及**租户内部的 Tenant Admin、Agent 角色——他们对本后台无访问权限。
---
## 2. 目标与成功指标
| 目标 | 指标 | 当前基线 | 目标值 | 测量窗口 |
|------|------|---------|--------|---------|
| 租户开通效率 | 新租户开通耗时 | 人工脚本 ~30 分钟 | < 5 分钟(含自动初始化) | 上线后 30 天 |
| 平台升级零停机 | 升级期间受影响租户数 | 全量中断 | 灰度阶段 ≤ 5% 租户 | 每次升级 |
| 数据恢复能力 | RTO单租户恢复时间 | 无标准流程 | < 2 小时 | v1 上线即达标 |
| 操作合规覆盖 | 高危操作审计日志覆盖率 | 0% | 100% | 上线后 30 天 |
| 管理员安全 | 平台管理员 MFA 启用率 | 0% | 100%(强制) | 上线即达标 |
| 客户端兼容性问题消除 | 因浏览器兼容产生的支持工单数 | 待统计 | 降低 ≥ 90% | 客户端上线后 60 天 |
| 客户端版本一致性 | 在线用户使用最新客户端比例 | 0%(无客户端) | ≥ 95% | 版本发布后 7 天 |
| 客户端自动更新成功率 | 收到通知 → 升级完成的成功率 | 无基准 | ≥ 98% | 每次发布后 48 小时 |
---
## 3. 非目标Non-Goals
**不在 v1 实现**
- 自动化账单计费、多币种支持、Webhook 自定义集成市场
- 租户端自助迁移工具
- 客户响应数据导出请求Story「响应客户数据导出请求」暂缓列入后续迭代
**不属于本后台**
- 租户内业务权限的细粒度配置(见权限管理模块 PRD
- 客服工单系统、SLA 自动赔付
- macOS / Linux 桌面客户端、移动端 App
- 客户端离线模式、私有化离线安装方案
- 客户端代码混淆 / 反逆向加固
**不支持**
- 移动端浏览器访问平台管理后台(运营场景明确为 PC 桌面)
- 多语言界面(运营团队为内部人员,中文已满足)
---
## 4. 用户角色与核心故事
### Persona A — 运营人员 Lily日常租户管理
> 负责 Fonrey 的日常运营,每天处理新客户开通、异常租户处理、客户咨询。使用 PC 浏览器登录平台管理后台。
#### PA-001 Story A1新租户开通
> 作为运营人员,我希望通过填写表单快速完成租户开通,并由系统自动完成数据库初始化与欢迎通知,无需手动执行脚本。
**验收标准**
- [ ] 表单提交后,系统在后台自动创建 PostgreSQL Schema 并注入默认配置(见 §5.1.2「默认配置内容」),完成耗时 < 60 秒
- [ ] 默认配置注入包含两部分:
- **权限定义PermissionDef**:注入平台所有权限码定义(`permission_code`、描述、模块归属),作为该租户 RBAC 体系基础
- **系统默认角色与权限绑定**:按「角色权限矩阵.md」注入 7 个系统内置业务角色(置业顾问、店管、区管、区总、副总、总经、其他职能),并完成角色—权限绑定
- [ ] 初始 Tenant Admin以联系人手机号创建**不通过业务角色赋权**,由系统在租户创建时直接写入「租户管理员」专属权限集合;该集合独立于 7 个业务角色之外,不在租户角色管理界面显示,不可由 Tenant Admin 自行修改或分配。新增/变更租户管理员须由平台运营方在本后台操作
- [ ] 新租户创建后:平台运营管理员收到站内消息;租户联系人收到欢迎邮件(见下方「欢迎邮件规范」)
- [ ] 联系人无邮箱时,运营人员可在租户详情页下载「入驻信息 PDF 文档」,通过微信等渠道转发
- [ ] 租户访问地址采用统一域名 + Tenant Code 参数形式(`https://app.fonrey.com/?tenant={Tenant Code}`),无需子域名;详情页展示该链接,可一键复制
- [ ] 创建失败时回滚所有已创建资源,并显示明确错误原因
**欢迎邮件规范**(联系人有邮箱时自动发送):
| 字段 | 内容 |
|------|------|
| 主题 | 【房睿平台】您的账号已开通,欢迎登录 |
| 收件人 | 租户联系人邮箱 |
| 正文 | 公司名称、**Tenant Code**、登录地址、Tenant Admin 手机号(脱敏后三位)、**系统初始密码**(明文,首次登录后强制修改)、客服联系方式 |
| 备注 | 初始密码由系统随机生成12 位,含大小写字母+数字),发送后立即标记为「首次登录强制修改」 |
**入驻信息 PDF**(无邮箱客户的备选方案):
- 包含与欢迎邮件相同的关键信息
- 入口:租户详情 → 基本信息 Tab → 「下载入驻信息」按钮
- 文件名:`{公司名称}_入驻信息_{日期}.pdf`
#### PA-002 Story A2挂起问题租户
> 作为运营人员,我希望快速冻结欠费租户的访问,同时保证数据不丢失,并在欠费解决后一键恢复。
**验收标准**
- [ ] 挂起后,该租户所有用户登录跳转至「账号已暂停」提示页;平台后台对该租户数据访问不受影响
- [ ] 支持设置到期时间,到期后系统自动恢复租户状态,并发送通知邮件
- [ ] 所有挂起 / 恢复操作记录于审计日志,包含操作人、时间、原因
#### PA-003 Story A3License 时效管理与到期自动挂起
> 作为运营人员(或 Platform Admin我希望每个租户能设置 License 有效期,到期后系统自动挂起租户,并在到期前提前预警。
**验收标准**
- [ ] 每个租户「基本信息」中包含 **License 到期日期**字段,由 Platform Admin 在创建或续费时设定
- [ ] 系统每日自动检查 License 到期到期后自动挂起挂起原因标注为「License 到期」
- [ ] 自动挂起后租户联系人收到通知邮件Platform Admin 续费后可手动解除挂起
- [ ] **提前 15 天预警**:租户内 Tenant Admin 登录后管理界面顶部出现倒计时横幅:「您的 License 将于 X 天后到期({到期日期}),请联系平台续费」
- [ ] 倒计时横幅仅对 Tenant Admin 可见,不影响普通 Agent
- [ ] License 到期日期在租户列表与详情页均可见支持「即将到期15 天内)」筛选
#### PA-004 Story A4查看与控制租户用户数License 计费维度)
> 作为运营人员(或 Platform Admin我希望看到每个租户的当前用户数并能设置用户数上限以便根据 License 授权进行管控。
**验收标准**
- [ ] 租户列表新增「当前用户数」列,显示该租户当前有效用户总数(含 Tenant Admin + 全部 Agent不含已删除/离职)
- [ ] 租户详情页「基本信息」Tab 显示:当前用户数 / License 授权用户数上限12 / 50
- [ ] Platform Admin 可设置「License 授权用户数上限」字段达到上限时Tenant Admin 在该租户内无法继续创建新用户,并收到提示:「当前用户数已达 License 上限,请联系平台扩容」
- [ ] Platform Admin 可随时调整用户数上限(扩容 / 缩容),变更写入审计日志
- [ ] 租户列表支持按「用户数已满(≥ 上限)」筛选,便于平台运营主动识别需续费的租户
> **PA-011 Story A5响应客户数据导出请求** — **状态**暂缓v1 不实现,列入后续迭代)。
---
### Persona E — Platform Admin David租户注销合规流程
> 处理租户主动申请注销或因严重违规被强制注销的场景。注销操作不可逆硬删除后需要多重防护——数据导出存证、双人复核、30 天冷静期撤销窗口。
#### PA-011 Story E1租户注销 SOPTenant Cancellation SOP
> 作为 Platform Admin我希望执行租户注销时有标准化的审批流程保障——要求数据导出完成、双人复核授权、30 天冷静期可撤销——防止因误操作或内部滥权导致客户数据不可恢复地丢失。
**验收标准AC**
**AC-1 发起注销申请**
- [ ] 运营人员或 Platform Admin 可在租户详情页发起「注销申请」,填写:注销原因(客户主动申请 / 严重违规 / 其他)、申请说明(自由文本)
- [ ] 发起后,租户状态流转至 `Pending Cancellation`(待审批注销),此时租户访问被阻断(等同挂起效果),不可通过常规「恢复」操作解除
- [ ] 发起操作写入审计日志
**AC-2 数据导出前置确认**
- [ ] 进入审批流程前系统强制展示「数据导出确认」步骤要求操作人触发该租户的完整数据导出§5.1.5),等待导出任务完成(状态 Done后方可提交审批
- [ ] 若申请人选择「客户已自行备份,无需平台导出」,需勾选明确确认框,并填写备注(客户签字回执编号或说明),此记录写入审计日志
- [ ] 审批单中需展示:导出任务 ID或「已确认客户自备份」备注、导出完成时间
**AC-3 双人复核审批**
- [ ] 注销审批流需要第二名 Platform Admin不得与申请人为同一账号在系统内确认审批确认时需通过 MFA 二次验证
- [ ] 审批人可「批准」或「驳回」:驳回时必须填写驳回原因;驳回后租户状态恢复至 `Suspended`(仍不可直接恢复为 Active需运营人员手动解除挂起
- [ ] 若 48 小时内无人审批,系统向所有 Platform Admin 发送催审通知(站内消息 + 邮件)
**AC-4 软删除与 30 天冷静期**
- [ ] 审批通过后,租户立即进入软删除状态(`Pending Delete`,现有机制);冷静期默认 30 天
- [ ] 冷静期内Platform Admin 可在租户列表「待清除」筛选下找到该租户,执行「撤销注销」
- [ ] 撤销注销需第二名 Platform Admin 同样通过 MFA 确认(与初始注销审批相同的双人复核机制)
- [ ] 撤销成功后,租户状态恢复至 `Suspended`(需运营人员手动评估后恢复为 Active撤销操作及原因写入审计日志
**AC-5 硬删除与资源释放**
- [ ] 冷静期届满后,系统自动触发硬删除,或 Platform Admin 可在冷静期内提前手动触发硬删除(需再次 MFA 确认,并再次展示「此操作不可逆」警告)
- [ ] 硬删除释放PostgreSQL Schema、Cloudflare R2 存储桶、Tenant Code、License 席位
- [ ] 硬删除完成后:向租户联系邮箱发送「账号已注销」通知(含注销日期、数据保留期说明);审计日志记录完整操作链
**注销状态流转**(补充至 §7 状态机):
```
[活跃 Active] 或 [已挂起 Suspended]
↓ 运营/Platform Admin 发起注销申请
[待审批注销 Pending Cancellation]
↓ 双人 MFA 审批通过
[待清除 Pending Delete](冷静期 30 天)
↓ 可在冷静期内由双人 MFA 撤销 → 恢复至 [已挂起 Suspended]
↓ 冷静期届满 或 提前手动硬删除MFA
[已删除 Deleted]
```
### Persona B — Platform Admin David系统升级、回滚与版本治理
> 负责平台技术运维,周期性执行版本升级,关注升级稳定性与租户影响面。拥有所有高危操作权限。
#### PA-005 Story B1灰度系统升级
> 作为 Platform Admin我希望先对内测租户升级新版本验证稳定后再全量推送避免一次性影响所有客户。
**验收标准**
- [ ] 升级前自动执行健康检查,存在异常服务时阻断升级并提示
- [ ] 支持指定目标租户进行灰度升级,灰度租户名单可编辑
- [ ] 升级过程实时展示进度(每个租户的升级状态),支持查看升级日志
- [ ] 升级失败时系统自动告警,并提供一键回滚入口
#### PA-006 Story B2升级失败回滚
> 作为 Platform Admin我希望在升级出现问题时立即回滚至上一稳定版本并生成事件报告。
**验收标准**
- [ ] 回滚操作触发前自动保存当前状态快照
- [ ] 支持全量回滚或单租户回滚
- [ ] 回滚完成后生成事件报告:失败原因、回滚耗时、影响范围
- [ ] 回滚操作需二次身份验证确认MFA
#### PA-007 Story B3查看平台与租户版本总览
> 作为 Platform Admin我希望在管理界面一眼看到整个平台的版本情况包括基础数据版本和每个租户各自的数据升级版本。
**验收标准**
「版本总览」页面分两部分:
- **Part 1平台基础数据版本**
- [ ] 展示当前平台基础数据(公共 Schema 中的 PermissionDef、系统配置等 seed 数据)的版本号
- [ ] 该版本对所有租户一致,每次平台升级为一次性全量升级
- [ ] 字段:版本号、最后升级时间、升级描述、升级执行人
- **Part 2租户数据升级版本**
- [ ] 列表展示每个租户当前数据版本号(即该租户 Schema 已完成的 migration 版本)
- [ ] 灰度升级下各租户版本可能不一致
- [ ] 字段:租户名称、当前数据版本、上次升级时间、升级状态(最新 / 待升级 / 升级中 / 升级失败)
- [ ] 支持按「待升级」「升级失败」筛选,快速定位异常租户
- [ ] 点击租户行可跳转至该租户详情的「备份记录」Tab
- [ ] 页面支持手动刷新;版本数据允许最多 5 分钟缓存延迟
---
### Persona C — Platform Admin David客户端发布治理
> 同 Persona B但聚焦桌面客户端版本上线、强制升级与跨租户版本分布。
#### PA-008 Story C1发布新版本客户端
> 作为 Platform Admin我希望通过本后台上传新版客户端安装包并配置版本信息使全平台所有租户的客户端能感知到更新并引导升级。
**验收标准**
- [ ] 平台管理后台提供「客户端版本管理」页面(位于本后台一级菜单,与租户系统管理隔离)
- [ ] 支持上传 `.exe` 安装包,并填写:
- 版本号SemVer`X.Y.Z`,自动校验格式)
- 版本类型(普通更新 / 强制更新)
- 最低兼容版本(低于此版本的客户端将被强制升级)
- 更新日志Markdown最多 2000 字,对外展示)
- 内部发布说明(不对外)
- [ ] 支持版本状态:草稿(不对外生效)/ 已发布 / 已下线
- [ ] 上传成功后,系统自动计算 SHA256 校验值并填充到该版本元数据,作为客户端下载完成后的完整性校验依据
- [ ] 发布后,客户端下次检测时即可感知(无需等待)
- [ ] 支持版本回滚:将指定历史版本重新置为「已发布」,自动将当前版本标记为「已下线」
- [ ] 支持便携版Portable ZIP作为可选上传项
#### PA-009 Story C2跨租户监控客户端版本分布
> 作为 Platform Admin我希望跨租户查看当前所有在线客户端的版本分布以了解全平台升级覆盖率对仍使用旧版本的客户端发出提醒或强制升级。
**验收标准**
- [ ] 「客户端版本管理」页展示版本分布统计:各版本在线客户端数量及占比(饼图或条形图)
- [ ] 支持按租户维度查看版本分布(区分不同经纪公司,便于识别落后租户)
- [ ] 支持按租户统计安装数:可查看任一租户的「当前活跃安装数(最近 24h」与「历史装机总数不论是否活跃
- [ ] 支持「全平台租户活跃榜」视图:以列表形式展示各 active 状态租户的 `tenant_code`、租户名称、活跃安装数、历史装机总数,按活跃安装数降序排列
- [ ] 支持对指定版本范围的客户端推送「强制更新」标记(如:将所有低于 v1.5.0 的客户端标记为强制更新)
- [ ] 支持升级进度趋势图:新版本发布后各天累计升级完成的用户比例
> **客户端侧用户故事不在本 PRD 范围**如「Agent 下载安装客户端」「Agent 自动升级」)。这些故事面向终端用户使用的桌面 App不属于平台管理后台职责。但本 PRD 通过 Story C1/C2 为客户端侧故事提供版本数据源与控制能力。
---
### Persona D — 只读审计员 Carol合规审计
> 负责平台合规审查,定期导出操作日志供法务或客户审查。无任何写权限。
#### PA-010 Story D1审计日志查询与导出
> 作为审计员,我希望按操作人、时间范围、操作类型筛选操作日志,并导出为报告。
**验收标准**
- [ ] 日志列表支持多维度筛选:操作人、时间范围、操作对象、操作类型(创建 / 修改 / 删除 / 高危)
- [ ] 每条日志包含:操作人、操作时间、操作对象(租户/用户ID、内容摘要、结果成功/失败)、来源 IP
- [ ] 支持导出筛选结果为 CSV
---
## 5. 功能详细说明
### 5.1 租户与 License 管理
#### 5.1.1 租户生命周期
**新建租户表单字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 公司名称 | Text | ✅ | 最大 100 字符 |
| 联系人 | Text | ✅ | 主联系人姓名 |
| 联系人手机号 | Text | ✅ | 用于创建初始 Tenant Admin |
| 联系邮箱 | Email | ❌(可选) | 发送欢迎邮件;无邮箱改用 PDF 下载 |
| 所在地区 | Select | ✅ | 省市两级 |
| 订阅套餐 | Select | ✅ | Basic / Professional / Enterprise |
| License 到期日期 | Date | ✅ | 到期后自动挂起 |
| License 授权用户数上限 | Number | ✅ | 用户数计费维度 |
**创建流程**
1. 表单校验通过后,后台异步任务执行:
- 创建 PostgreSQL Schema`tenant_{id}`
- 执行 Migrate 初始化表结构
- 注入默认配置(见下方「默认配置内容」)
- 生成初始密码12 位随机),标记为「首次登录强制修改」
- 联系邮箱已填写时:发送欢迎邮件(含 Tenant Code、初始密码、平台访问链接
2. 任务完成后更新租户状态为 `active`;失败则全量回滚并标记为 `failed`
3. 生成唯一 Tenant IDUUID及 Tenant Code12位纯数字`202605023765`
#### 5.1.2 默认配置内容Schema 初始化时注入)
| 配置类型 | 内容 | 说明 |
|---------|------|------|
| 权限定义PermissionDef | 平台全量权限码 | 从 public schema 同步至租户 schema |
| 系统默认业务角色 | 7 个内置角色(置业顾问/店管/区管/区总/副总/总经/其他职能)+ 角色—权限绑定 | 按「角色权限矩阵.md」 |
| 初始 Tenant Admin 用户 | 以联系人手机号创建 | 写入「租户管理员」专属权限集合,独立于 7 个业务角色 |
#### 5.1.3 挂起Suspend
- **触发方式**
- 手动:运营人员选择挂起原因(欠费 / 违规 / 主动申请 / 其他)
- 自动:系统每日检查 License 到期日期到期自动挂起原因「License 到期」)
- 可设置挂起到期时间(留空表示永久挂起直至手动恢复)
- 挂起效果:租户用户访问被拒,重定向至暂停提示页;平台管理后台对该租户数据访问不受影响
- 自动恢复手动设置了到期时间的挂起会到期自动恢复License 到期挂起须 Platform Admin 手动恢复
- 通知:挂起 / 恢复均向租户联系邮箱发送通知(无邮箱跳过)
- License 到期前 15 天起,租户端 Tenant Admin 看到倒计时横幅
#### 5.1.4 删除Delete
> **注意**租户注销是高危操作须遵循「PA-011 租户注销 SOP」——发起注销申请 → 强制数据导出确认 → 双人 MFA 复核审批 → 软删除30 天冷静期,可撤销)→ 自动或提前手动硬删除。**不允许绕过此 SOP 直接执行删除。**
| 模式 | 说明 |
|------|------|
| 软删除Pending Delete | SOP 审批通过后自动进入;数据保留 30 天(默认,可配置)后自动清除 |
| 硬删除 | 冷静期内 Platform Admin 提前触发(需 MFA + 不可逆警告),或冷静期届满自动触发;仅 Platform Admin 可操作 |
**删除前置条件PA-011 SOP 强制执行)**
1. 发起申请时必须填写注销原因
2. 审批前强制完成数据导出确认(平台导出 Done 或客户自备份书面确认)
3. 须第二名 Platform Admin≠ 申请人MFA 审批通过
4. 软删除冷静期内可由双人 MFA 执行「撤销注销」
删除完成后释放:访问域名 / Tenant Code、Cloudflare R2 存储桶、License 席位
#### 5.1.5 数据导出Export
> Story A5响应客户数据导出请求暂缓本节描述能力为运营内部数据核查使用。
- 触发:管理员手动,选择目标租户 + 模块 + 格式
- 异步执行状态实时刷新Pending → In Progress → Done / Failed
- 内容结构化数据CSV / JSON / SQL Dump+ 文件资产 URL 清单(**不打包文件实体**
- 模块选项:客户数据 / 房源数据 / 交易记录 / 系统配置 / 全量
- 存储:压缩后存于 Cloudflare R2 临时目录,签名下载链接 24 小时有效
**文件资产导出处理规则**v1 决策):
- R2 Bucket 配置为 public read文件通过 CDN 持久 URL 形式内嵌于导出数据
- 账号未硬删除前CDN URL 持续有效
- 迁移类需求(需要文件实体)走「完整备份」流程,不走「数据导出」
| 导出格式 | 图片字段示例 | 附件字段示例 |
|---------|------------|------------|
| CSV | `photos` 列:多个 CDN URL 以英文分号分隔 | `attachments` 列:`文件名\|CDN URL` 以分号分隔 |
| JSON | `"photos": [{"url": "...", "filename": "...", "created_at": "..."}]` | `"attachments": [{"url": "...", "filename": "..."}]` |
| SQL Dump | 文件元数据表原样导出,`file_url` 字段为 CDN URL | 同左 |
导出包附 `README.txt` 说明文件资产链接策略。
**数据导出 vs 完整备份**
| 维度 | 数据导出Export | 完整备份Backup |
|------|------------------|-----------------|
| 用途 | 合规审计、数据核查、业务分析 | 灾难恢复、租户迁移 |
| 文件资产 | CDN URL 清单 | 含 R2 文件实体 |
| 完成时间 | 分钟级 | 小时级 |
| 触发方式 | 运营人员手动 | 手动 / 系统自动(升级前) |
| 存储成本 | 极低 | 较高 |
#### 5.1.6 数据备份Snapshot
- 自动触发:升级前系统对参与租户全量备份
- 手动触发:管理员可在租户详情页发起
- 内容:数据库 Schemapg_dump+ R2 文件存储(附件、图片)
- 字段:备份时间、触发方式、备份大小、状态
- 保留策略:默认最近 10 个版本,可在全局配置中调整
- 存储:加密;目标可选(本地 / S3 / R2 / GCS
#### 5.1.7 数据恢复Restore
恢复流程:
```
选择目标备份版本
→ 二次确认(显示当前数据版本将被覆盖)
→ 自动对当前数据生成临时快照
→ 租户切换为维护模式
→ 执行恢复
→ 自动恢复服务 → 生成恢复报告
```
报告字段:操作人、操作时间、恢复前/后版本、耗时、结果。
#### 5.1.8 套餐升级
- 升级路径Basic → Professional → Enterprise
- 升级前展示差异对比表功能项、用户数上限、存储空间、API 额度)
- 生效模式:立即 / 下一账期
- 升级前自动备份;升级失败可一键回滚
- 升级历史:时间、操作人、升级前后套餐
#### 5.1.9 租户用户与权限管理
**Tenant Admin 管理**
- 每个租户可设置 1 至多名 Tenant Admin
- Platform Admin 可直接创建新用户并赋予 Tenant Admin或从租户现有用户中指定
- 支持新增 / 替换 / 撤销
**密码重置**(针对租户内任意用户):
- 方式一:发送重置链接至注册邮箱(用户自助)
- 方式二:管理员直接设置临时密码(首次登录强制修改)
- 所有操作进审计日志
#### 5.1.10 租户监控与统计
**资源监控指标**
| 指标 | 展示维度 |
|------|---------|
| CPU / 内存占用 | 实时折线图 |
| 存储用量 | 当前值 vs 套餐上限 |
| API 调用次数 | 当日 / 本月累计 |
| 活跃用户数 | 当日活跃 |
| 当日登录次数 | 累计折线图 |
| 异常请求数 | 4xx / 5xx 分类 |
| 慢查询数量 | > 500ms |
支持为每个指标配置阈值告警(邮件 / Webhook
**可用性 / SLA**
- 服务可用率统计:日 / 周 / 月
- 故障事件记录:开始 / 恢复时间、持续时长、影响描述
- SLA 达标率报告,可导出
---
### 5.2 平台版本治理
#### 5.2.1 系统升级流程
```
上传/拉取升级包
→ 自动健康检查(所有服务正常才允许)
→ 配置升级策略:全量 / 灰度(指定内测租户)
→ 升级前自动备份(参与本次升级的租户)
→ 执行升级
→ 实时展示升级进度(租户维度状态列表)
→ 完成通知(成功/失败详情)
```
**灰度升级策略**
- 维护「内测租户组」列表,由 Platform Admin 配置
- 灰度阶段仅对内测租户升级,其余租户保持原版本
- 内测验证通过(手动确认)后,触发全量升级
#### 5.2.2 升级回滚
- 触发:手动 / 自动(监控检测到错误率超阈值)
- 范围:全量回滚 / 单租户回滚
- 前置:自动保存当前状态快照
- 后续:生成事件报告(失败原因、回滚耗时、受影响租户列表)
- 必需MFA 二次验证
#### 5.2.3 定时备份策略
| 配置项 | 选项 |
|--------|------|
| 备份频率 | 每小时 / 每日 / 每周 |
| 执行时间窗口 | 默认每日 02:00 |
| 保留数量 | 最近 N 个版本(默认 10 |
| 存储目标 | 本地 / AWS S3 / Cloudflare R2 / GCS |
支持为单租户配置独立备份计划,覆盖全局策略。备份失败自动告警,支持手动重试。
#### 5.2.4 版本总览
见 Story B3 验收标准。该视图为运营提供「平台基础数据版本 + 各租户数据版本」双维度可见性,是灰度升级时代的版本治理核心入口。
---
### 5.3 客户端发布治理
> 本节面向 Persona C 的 Story C1 / C2。客户端本身的技术形态、自动更新机制、签名分发等实现细节属于技术方案范畴请参见 `TECH_STACK/平台管理后台技术方案.md`。本 PRD 仅描述产品视角的业务能力。
#### 5.3.1 版本管理
平台管理后台提供独立的「客户端版本管理」一级页面,承担:
**版本列表展示字段**
| 列 | 说明 |
|----|------|
| 版本号 | SemVer 格式,如 `v1.2.3` |
| 版本类型 | 普通更新 / 强制更新(后者标红) |
| 状态 | 草稿 / 已发布(绿)/ 已下线(灰) |
| 发布时间 | 设为已发布的时间 |
| 下载量 | 该版本安装包被下载次数 |
| 操作 | 发布 / 下线 / 编辑 / 复制下载链接 |
**新增/编辑版本表单字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 版本号 | Text | ✅ | SemVer自动校验 |
| 版本类型 | 单选 | ✅ | 普通更新 / 强制更新 |
| 最低兼容版本 | Text | ❌ | 低于该版本的客户端被强制升级 |
| 安装包EXE | File | ✅ | 上传至 R2最大 500MB |
| 便携版ZIP | File | ❌ | 同上 |
| SHA256 校验值 | Text自动 | ✅ | 上传后系统计算并填充 |
| 更新日志 | Markdown | ✅ | 对外展示,最多 2000 字 |
| 发布说明(内部) | Text | ❌ | 仅内部查看 |
| 状态 | 单选 | ✅ | 草稿 / 立即发布 |
#### 5.3.2 版本分布统计
| 图表 | 说明 |
|------|------|
| 版本分布饼图 | 按客户端版本号统计当前活跃用户数量及占比 |
| 升级进度趋势图 | 新版本发布后各天累计升级完成的用户比例 |
| 租户版本明细 | 按租户展示其员工的客户端版本分布 |
| 全平台租户活跃榜 | `tenant_code` + 租户名称 + 活跃安装数(最近 24h + 历史装机总数,按活跃安装数降序 |
底层数据由 `public.client_heartbeats` 表的 `tenant_id` 维度聚合提供。
#### 5.3.3 强制更新推送
- Platform Admin 可对指定版本范围的客户端打上「强制更新」标记(如所有低于 v1.5.0 的客户端强制升级)
- 客户端侧的强制更新行为(不展示「稍后提醒」、必须升级后方可继续使用)由客户端实现保障
---
### 5.4 平台管理后台总体规划
#### 5.4.1 页面清单与访问权限
> 以下为产品视角的页面清单。「页面路径」以业务可读形式表达,不约束技术实现(具体 URL 由技术方案决定。所有页面均位于平台管理后台域无访问权限的角色Tenant Admin、Agent 及未登录用户)一律重定向到登录页或 403。
| # | 页面 | 路径(业务可读) | 访问权限 | 说明 |
| --- | ----------- | -------------------------------- | --------------------------------------- | --------------------------------- |
| 1 | 登录页 | `/admin/login` | 公开(仅平台管理员账号可登录) | 强制 MFA 配置 |
| 2 | 仪表盘 | `/admin` | 全部三类管理员 | 平台总览、健康度、近期告警、最近高危操作 |
| 3 | 租户列表 | `/admin/tenants` | Platform Admin / 运营人员(写)/ 审计员(只读) | 检索、筛选、快捷操作 |
| 4 | 新建租户 | `/admin/tenants/new` | Platform Admin / 运营人员 | — |
| 5 | 租户详情:基本信息 | `/admin/tenants/{id}` | 同租户列表 | 含 License、用户数、状态等编辑入口 |
| 6 | 租户详情:用户管理 | `/admin/tenants/{id}/users` | 同上 | Tenant Admin 列表、用户列表、密码重置 |
| 7 | 租户详情:套餐信息 | `/admin/tenants/{id}/plan` | 同上 | 套餐详情、用量、升级入口 |
| 8 | 租户详情:监控 | `/admin/tenants/{id}/monitoring` | 同上 | 资源使用、SLA |
| 9 | 租户详情:备份记录 | `/admin/tenants/{id}/backups` | Platform Admin/ 运营人员(仅触发备份)/ 审计员(只读) | 备份列表、触发备份、恢复入口(仅 Platform Admin |
| 10 | 租户详情:操作历史 | `/admin/tenants/{id}/history` | 全部三类 | 该租户相关所有管理员操作日志 |
| 11 | 系统版本管理 | `/admin/system/versions` | Platform Admin/ 运营人员(只读)/ 审计员(只读) | 含「版本总览」(平台基础数据版本 + 各租户数据版本)、升级、回滚 |
| 12 | 备份管理 | `/admin/system/backups` | Platform Admin/ 运营人员(触发)/ 审计员(只读) | 全局备份计划、备份任务列表、恢复入口 |
| 13 | 监控与告警 | `/admin/monitoring` | Platform Admin / 运营人员(写)/ 审计员(只读) | 全局监控图表、告警规则、告警历史 |
| 14 | **客户端版本管理** | `/admin/client-releases` | Platform Admin/ 运营人员(只读)/ 审计员(只读) | 版本列表、发布、下线、回滚、版本分布、租户活跃榜 |
| 15 | 审计日志 | `/admin/audit-logs` | 全部三类(含审计员) | 多维度筛选、导出 CSV |
| 16 | 管理员设置 | `/admin/settings/admins` | 仅 Platform Admin | 管理员账号、角色、MFA、IP 白名单、登录会话 |
#### 5.4.2 页面间导航逻辑(用户故事视角)
- 用户访问任一受保护页面但未登录 → 跳转 `/admin/login`
- 登录成功且未配置 MFA → 强制跳转 MFA 配置向导后再进入仪表盘
- 仪表盘的「最近高危操作」区域,每条记录可点击跳转到「审计日志」并自动以该记录为筛选条件
- 仪表盘的「系统健康」区域,点击异常服务可跳转「监控与告警」对应租户/服务视图
- 租户列表点击「公司名称」→ 进入「租户详情:基本信息」(默认 Tab
- 租户详情各 Tab 之间通过页内 Tab 切换URL 同步变化(页面 510
- 租户详情「备份记录」中点击「恢复」→ 出现二次确认弹窗 + MFA 验证(仅 Platform Admin 可见)
- 「系统版本管理」中点击某租户行 → 跳转到该租户的「备份记录」Tab
- 「客户端版本管理」中点击「全平台租户活跃榜」中的租户名称 → 跳转到该租户「基本信息」Tab
- 客户端版本「发布 / 下线 / 强制更新推送」操作 → 二次确认;强制更新推送额外要求 MFA
- 任何高危操作(删除/注销租户、数据恢复、系统回滚、客户端版本下线、强制更新推送)触发 MFA 二次确认弹窗
- 租户详情「基本信息」页面Platform Admin / 运营人员可发起「注销申请」;状态为 `Pending Cancellation` 时,原「挂起/恢复」按钮隐藏,显示「查看注销审批单」入口
- 注销审批待办:系统向第二名 Platform Admin 发送站内通知点击通知进入审批单详情展示申请人、注销原因、数据导出确认记录提供「批准MFA」/「驳回」操作
- 审批批准后,租户自动进入 `Pending Delete`;租户列表「待清除」筛选下可见,提供「撤销注销(双人 MFA」和「提前硬删除MFA」入口
- 用户超过 30 分钟无操作 → 自动登出,下次操作跳转登录页
- 「管理员设置」中 Platform Admin 强制登出某管理员 → 该管理员的所有会话立即失效,下次请求跳转登录页
#### 5.4.3 仪表盘内容
| 模块 | 展示内容 |
|------|---------|
| 全局概览 | 总租户数、活跃租户数、本月新增 |
| 系统健康 | 各核心服务状态Django / PostgreSQL / Redis / Celery / R2 |
| 近期告警 | 最近 24 小时告警,按严重程度分类 |
| 资源概览 | 平台整体存储用量、API 调用量趋势 |
| 客户端覆盖 | 当前活跃客户端总数、最新版本占比 |
| 最近操作 | 最近 10 条高危操作审计记录 |
#### 5.4.4 租户列表
- 分页(默认 20 条/页)
- 搜索公司名称、Tenant Code、联系邮箱关键词
- 筛选状态Active / Suspended / Pending Cancellation / Pending Delete / Deleted、套餐、注册时间、**即将到期15 天内)**、**用户数已满**
-公司名称、Tenant Code、套餐、状态、注册时间、活跃用户数 / License 上限、客户端最新版本占比
- 快捷操作:查看详情、挂起、发起备份、数据导出
---
### 5.5 业务 API 操作清单(产品视角)
> 仅描述「平台管理员需要能完成哪些业务操作」;不定义具体 RESTful 路径、HTTP 方法、参数结构。具体接口设计见对应技术方案文档。
#### 5.5.1 租户管理
- 平台管理员需要能查询租户列表(支持多维度筛选)
- 平台管理员需要能查看单租户的完整详情基本信息、套餐、用户、License、备份、操作历史
- 平台管理员需要能创建新租户(含异步初始化 Schema 与默认配置)
- 平台管理员需要能编辑租户基本信息(公司名、联系人、邮箱、地区)
- 平台管理员需要能挂起 / 恢复租户(含挂起原因、到期时间)
- 平台管理员运营人员需要能发起租户注销申请PA-011 SOP填写原因、完成数据导出确认
- 第二名 Platform Admin 需要能审批租户注销申请(批准需 MFA驳回需填写驳回原因
- 平台管理员需要能在冷静期内撤销租户注销(双人 MFA
- 平台管理员需要能在冷静期内提前手动触发硬删除MFA + 不可逆确认)
- 系统需能在冷静期届满后自动触发硬删除并释放所有资源
- 平台管理员需要能软删除 / 硬删除租户(仅限通过 PA-011 SOP 触发,不提供绕过 SOP 的直接删除入口)
- 平台管理员需要能调整租户的 License 到期日期
- 平台管理员需要能调整租户的 License 授权用户数上限
- 平台管理员需要能升级租户套餐
- 平台管理员需要能下载租户的「入驻信息 PDF」
#### 5.5.2 租户用户管理
- 平台管理员需要能查询租户内的全部用户
- 平台管理员需要能新增 / 替换 / 撤销 Tenant Admin
- 平台管理员需要能为租户内任意用户重置密码(链接 / 临时密码两种方式)
#### 5.5.3 数据导出与备份
- 平台管理员需要能为指定租户触发数据导出(异步,可查询任务状态、获取下载链接)
- 平台管理员需要能为指定租户手动触发完整备份
- 平台管理员需要能查询某租户的备份记录列表
- 平台管理员需要能基于某个备份执行恢复操作(需 MFA
- 平台管理员需要能配置全局 / 单租户的定时备份策略
#### 5.5.4 平台版本与升级
- 平台管理员需要能上传 / 拉取系统升级包
- 平台管理员需要能配置升级策略(全量 / 灰度,含内测租户名单)
- 平台管理员需要能触发升级,并实时查询升级进度(租户维度)
- 平台管理员需要能查看升级日志
- 平台管理员需要能触发升级回滚(全量 / 单租户,需 MFA
- 平台管理员需要能查询「平台基础数据版本」与「各租户数据升级版本」总览
#### 5.5.5 客户端发布
- 平台管理员需要能查询客户端版本列表(含状态、下载量)
- 平台管理员需要能新增客户端版本(上传安装包,系统自动计算 SHA256
- 平台管理员需要能修改某版本元数据(更新日志、版本类型、最低兼容版本等)
- 平台管理员需要能切换某版本状态(草稿 → 已发布 / 已发布 → 已下线)
- 平台管理员需要能将某历史版本回滚为「已发布」(同时把当前版本置为已下线)
- 平台管理员需要能对指定版本范围的客户端打上「强制更新」标记
- 平台管理员需要能查询全平台客户端版本分布(饼图、趋势图)
- 平台管理员需要能查询全平台租户活跃榜(活跃安装数、历史装机总数)
- 平台管理员需要能查询任一租户的客户端版本分布与活跃数
> 客户端**自身**与更新服务的交互(如查询最新版本、上报心跳)属于客户端运行时与平台之间的接口,不属于平台管理员的操作;本 PRD 不在此章描述,由 `TECH_STACK/平台管理后台技术方案.md` 定义。
#### 5.5.6 监控与告警
- 平台管理员需要能查询全局 / 单租户的监控图表
- 平台管理员需要能配置告警规则(指标 + 阈值 + 通知渠道)
- 平台管理员需要能查询告警历史
#### 5.5.7 审计日志
- 平台管理员(含审计员)需要能按多维度筛选审计日志
- 平台管理员(含审计员)需要能导出筛选结果为 CSV
#### 5.5.8 平台管理员账号与安全
- Platform Admin 需要能创建 / 编辑 / 停用平台管理员账号
- Platform Admin 需要能配置管理员角色Platform Admin / 运营人员 / 只读审计员)
- 全部平台管理员需要能首次登录时配置 MFA无法跳过
- 全部平台管理员需要能在高危操作时通过 MFA 二次确认
- Platform Admin 需要能配置 IP 白名单
- Platform Admin 需要能查看活跃会话并强制登出指定管理员
---
### 5.6 安全与访问控制
**强制要求(不可降级)**
| 安全要求 | 说明 |
|---------|------|
| MFA 强制启用 | 所有管理员账号首次登录强制配置 TOTP不可跳过 |
| IP 白名单 | 仅允许指定 IP 范围访问平台管理后台 |
| 高危操作二次验证 | 删除租户、数据恢复、系统回滚、客户端版本下线、强制更新推送均触发 MFA 二次确认 |
| 会话超时 | 无操作 30 分钟自动登出Token 失效 |
| 强制登出 | Platform Admin 可在「管理员设置」中强制终止指定管理员的所有会话 |
| 与租户应用隔离 | 平台管理后台部署在独立平台域名,不与租户应用共享 Session / Cookie |
### 5.7 操作审计日志规范
所有写操作Create / Update / Delete及高危操作必须落审计日志字段
```
{
"id": "UUID",
"operator_id": "管理员用户 ID",
"operator_name": "管理员显示名",
"action_type": "CREATE_TENANT | SUSPEND_TENANT | RESUME_TENANT | DELETE_TENANT | HARD_DELETE_TENANT | RESTORE_DATA | SYSTEM_UPGRADE | ROLLBACK | RESET_PASSWORD | RELEASE_CLIENT_VERSION | OFFLINE_CLIENT_VERSION | FORCE_UPDATE_PUSH | UPDATE_LICENSE | UPDATE_LICENSE_USER_LIMIT | AUTO_SUSPEND_LICENSE_EXPIRED | ...",
"target_type": "Tenant | User | System | Backup | ClientRelease | Admin",
"target_id": "操作对象 ID",
"target_name": "操作对象可读名称",
"payload_summary": "操作内容摘要(非敏感字段)",
"result": "SUCCESS | FAILED",
"error_message": "失败原因(如有)",
"ip_address": "操作来源 IP",
"created_at": "ISO 8601 时间戳"
}
```
---
## 6. 角色权限矩阵
| 操作 | Platform Admin | 运营人员 | 只读审计员 |
|------|---------------|---------|-----------||
| 创建租户 | ✅ | ✅ | ❌ |
| 挂起 / 恢复租户 | ✅ | ✅ | ❌ |
| 发起租户注销申请PA-011 SOP | ✅ | ✅ | ❌ |
| 审批注销申请第二人复核MFA | ✅ | ❌ | ❌ |
| 撤销注销(冷静期内,双人 MFA | ✅ | ❌ | ❌ |
| 提前手动硬删除MFA | ✅ | ❌ | ❌ |
| 软删除 → Pending Delete仅经 SOP 自动触发) | 系统自动 | — | — |
| 调整 License 到期日期 / 用户数上限 | ✅ | ✅ | ❌ |
| 数据导出 | ✅ | ✅ | ❌ |
| 手动触发备份 | ✅ | ✅ | ❌ |
| 数据恢复 | ✅ | ❌ | ❌ |
| 系统升级 | ✅ | ❌ | ❌ |
| 系统回滚 | ✅ | ❌ | ❌ |
| 查看版本总览 | ✅ | ✅ | ❌ |
| 配置告警规则 | ✅ | ✅ | ❌ |
| 发布 / 下线客户端版本 | ✅ | ❌ | ❌ |
| 客户端版本回滚 | ✅ | ❌ | ❌ |
| 推送强制更新标记 | ✅ | ❌ | ❌ |
| 查看客户端版本分布与活跃榜 | ✅ | ✅ | ✅ |
| 查看审计日志 | ✅ | ✅ | ✅ |
| 导出审计日志 | ✅ | ✅ | ✅ |
| 管理员账号管理 | ✅ | ❌ | ❌ |
| 强制登出管理员 | ✅ | ❌ | ❌ |
| 配置 IP 白名单 | ✅ | ❌ | ❌ |
---
## 7. 租户状态机
```
[新建中 Creating]
↓ 成功
[活跃 Active] ←──────────────────┐
↓ 手动 / License 到期挂起 │ 到期自动恢复(仅手动挂起且设置了到期时间)/ 手动恢复
[已挂起 Suspended] ───────────────┘
↓ 运营/PA 发起注销申请PA-011
[待审批注销 Pending Cancellation]
↓ 驳回(第二名 PA→ [已挂起 Suspended]
↓ 批准(第二名 PAMFA
[待清除 Pending Delete](冷静期 30 天)
↓ 双人 MFA 撤销注销 → [已挂起 Suspended]
↓ 冷静期届满(自动)/ 提前手动硬删除MFA
[已删除 Deleted]
```
> **注意**Active / Suspended 状态下均可发起注销申请(后者更常见)。不允许直接跳过 SOP 从任何状态进入 Pending Delete 或 Deleted。
---
## 8. 不构建清单What We're NOT Building
| 请求/功能 | 原因 | 重新评估条件 |
|----------|------|------------|
| 自动化账单与发票 | 财务模块独立立项 | 财务模块 PRD 完成后接入 |
| 租户端自助迁移工具 | 当前规模不需要 | 租户数 > 500 时重新评估 |
| 平台管理后台移动端 | 运营场景明确为 PC | v2 规划,用户调研支持时推进 |
| Webhook 事件推送市场 | 集成复杂度高,无客户驱动 | 有 3+ 客户明确需求时评估 |
| 多语言管理界面 | 内部团队中文已满足 | 国际化扩张时规划 |
| macOS / Linux 客户端 | 99% 用户为 Windows | 市场需求出现时评估 |
| 移动端客户端 App | v2 规划 | v2 启动时 |
| 客户端离线模式 | 联网使用为既定形态 | 暂不考虑 |
| 客户端代码混淆 / 反逆向 | 优先功能交付 | 安全审计要求时启动 |
| 响应客户数据导出请求 | 暂缓 | 后续迭代排期时细化 |
---
## 9. 发布计划
| 阶段 | 时间 | 范围 | 通过标准 |
|------|------|------|---------|
| 内部 Alpha | Week 14 | 平台内部团队 | 核心租户 CRUD 流程无 P0 BugMFA 可用;客户端发布流程闭环 |
| 封闭 Beta | Week 56 | 运营团队日常使用 + 1 家种子客户的客户端 | 备份/恢复完整可用;审计日志 100% 覆盖;客户端自动更新成功率 ≥ 95% |
| 正式上线 | Week 7 | 全量运营团队 + 全部客户的客户端 | 升级/回滚验证通过;监控告警规则配置完成;客户端版本一致性 ≥ 95% |
**回滚标准**
- 正式上线 72 小时内发现租户数据隔离漏洞或审计日志丢失 → 立即回滚平台管理后台
- 客户端版本上线 24 小时内:自动更新失败率 > 5%、白屏 / 崩溃率 > 2%、或收到 P0 安全报告 → 立即下线该版本并恢复上一稳定版本
---
## 10. 技术考量(指引性)
> 本节为产品视角对技术决策的影响概述,详细方案请见各技术文档。
| 维度 | 关键依赖 | 备注 |
|------|---------|------|
| 多租户隔离 | `django-tenants` | 平台管理后台数据落 `public` schema跨租户查询走 `public` 维度 |
| 异步任务 | Celery + Celery Beat | 创建租户、备份、导出、定时挂起检查等 |
| 数据库备份 | PostgreSQL `pg_dump`(待评估流式方案) | 大租户备份耗时风险 |
| 文件存储 | Cloudflare R2 | 备份、导出包、客户端安装包 |
| 监控 | Sentry + Grafana | 已在技术栈中规划 |
| MFA | `django-otp` + TOTP | 待最终选型 |
| 客户端 | Electron + electron-updater | 见 `TECH_STACK/平台管理后台技术方案.md` |
| 客户端心跳/活跃统计 | `public.client_heartbeats`Upsert + 24h 活跃口径,见 `ADR-20260430-007` | 是租户活跃榜与版本分布的数据源 |
| 客户端 API 命名空间 | 统一 `/api/release/...`(见 `ADR-20260430-009` | 与租户客源管理 `apps/client/` 命名空间隔离 |
### 待解决问题(开发启动前必须确认)
- [ ] 数据库备份方案:`pg_dump` 直接执行还是基于 WAL 的增量备份(如 pgBackRest— Owner: 工程负责人
- [ ] 监控数据来源Grafana 直接对接 PostgreSQL 还是通过 Prometheus Exporter— Owner: 运维团队
- [ ] MFA 库选型:`django-otp` + TOTP 还是集成第三方认证?— Owner: 工程负责人
- [ ] 审计日志存储:写入 `public` schema 还是独立日志服务(如 Elasticsearch— Owner: 工程负责人
- [ ] 客户端 EV 代码签名证书采购主体与预算 — Owner: IT 负责人
- [ ] CI/CD 平台选型(客户端构建签名流水线)— Owner: 运维负责人
- [ ] 客户端便携版Portable ZIP是否纳入 v1 — Owner: PM
- [ ] 客户端启动时租户 URL 分发方式(统一域名重定向 vs 内置配置文件)— Owner: 产品 + 工程
---
## 11. 附录
### 11.1 术语表
| 术语 | 定义 |
|------|------|
| Platform Admin | 平台超级管理员,拥有所有高危操作权限 |
| 运营人员 | 平台日常运营,承担租户管理、监控巡检 |
| 只读审计员 | 仅查询与导出审计日志,无写权限 |
| Tenant Admin | 租户内的最高权限用户,**对平台管理后台无访问权限** |
| Tenant Code | 租户唯一可读短码,用作租户访问 URL 中的 `tenant=` 参数 |
| 灰度升级 | 先对内测租户升级、验证通过后再全量推送 |
| 数据导出 | 输出结构化数据 + 文件 CDN URL 清单,分钟级,不含文件实体 |
| 完整备份 | 数据库 + 文件实体的完整副本,小时级,用于灾难恢复与租户迁移 |
| SemVer | 语义化版本控制 `主.次.补丁` |
| EV 证书 | Extended Validation 代码签名证书 |
| SHA256 | 用于校验客户端安装包完整性的散列算法 |
### 11.2 关联文档
| 类型 | 文档 |
| ------------------ | ----------------------------------------- |
| 项目入口 | `README.md` |
| 开发约束 | `AGENTS.md` |
| MVP 范围 | `PRD/PRD_MVP.md` |
| 客户端技术方案 | `TECH_STACK/平台管理后台技术方案.md` |
| Public Schema 数据模型 | `DATA_MODEL/DATA_MODEL_PUBLIC.md` |
| ADR | `ADR.md`(含 `ADR-20260502-001`:本 PRD 合并决策) |

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More