# gov_service_guides OpenSearch Mapping 详细设计

## 1. 文档目标

本文档是 [prd/standard-service-guide-structured-schema-draft.md](prd/standard-service-guide-structured-schema-draft.md) 的配套落地设计，专门定义标准办事指南结构化详情索引 `gov_service_guides` 的 OpenSearch mapping、索引策略、查询方式和落库规则。

设计目标：

1. 稳定保存标准办事指南的完整栏目化结构
2. 支持按事项名、日常用语、编码、材料、收费、窗口、地域等维度检索与过滤
3. 保留材料清单、收费项、窗口信息等“行级语义”
4. 与现有 `gov_doc_meta`、`gov_doc_chunks`、Matter 图谱形成互补关系
5. 控制索引复杂度，避免把所有字段都做成全文大字段或全部做成图节点

---

## 2. 设计边界

### 2.1 索引定位

`gov_service_guides` 的语义定位是：

- 一份标准办事指南文档的结构化详情视图
- 面向事项详情、导办、预审、咨询答复的业务索引
- 不是全文检索主索引
- 不是通用文档元数据索引
- 不是图数据库替代品

### 2.2 与现有索引的职责边界

| 索引 | 职责 |
|------|------|
| `gov_doc_chunks` | 全文 chunk 检索、向量召回、正文高亮 |
| `gov_doc_meta` | 通用文档元数据、文档列表、状态与 ACL |
| `gov_service_guides` | 标准办事指南结构化详情 |

补充要求：

- `gov_service_guides` 需要复制 `acl_ids` 作为查询过滤字段
- 权限真值源仍然是 `gov_doc_meta.acl_ids`
- 权限变更时，Guide 索引必须与 meta/chunk 一起同步刷新
- 事项/Guide 默认按公共服务场景公开；仅当存在显式 `acl_ids` 时才进入受限访问控制

### 2.3 与图谱的职责边界

Neo4j 继续负责：

- Matter / Condition / Material / TimeLimit / Organization / Region 的关系化表达
- 跨文档导航与结构证据

`gov_service_guides` 负责：

- 材料行级要求
- 收费项
- 办理窗口
- 法律救济
- 咨询投诉方式
- 办理流程详情
- 标准指南版本细节

### 2.4 首版不做的事情

首版 mapping 不解决：

- 全量自动对比索引
- 窗口地理坐标搜索
- 收费规则复杂计算
- 法律依据全文分条搜索
- 所有字段的全文高亮

这些都可以在索引稳定后再扩展。

---

## 3. 索引命名与首版策略

### 3.1 首版推荐命名

首版直接使用固定索引名：`gov_service_guides`

### 3.2 原因

1. 当前仓库中的 `gov_doc_chunks`、`gov_doc_meta` 都由 `ESClient.create_indices()` 直接创建固定索引
2. 单独为 Guide 引入读写别名，会让首版运维模式与现状分叉
3. v1 先把 schema、抽取和 ACL 链路跑稳，比提前引入 alias 更重要

### 3.3 后续演进策略

如果后续 schema 升级频繁，再演进为：

- 物理索引：`gov_service_guides_v2`
- 读别名：`gov_service_guides`
- 写别名：`gov_service_guides_write`

### 3.4 索引创建原则

建议在 `ESClient.create_indices()` 中追加：

1. 若 `gov_service_guides` 不存在，则创建固定索引
2. 首版不做隐式升级
3. schema 变更通过显式迁移脚本处理

---

## 4. 核心建模原则

### 4.1 根字段与嵌套字段并存

为兼顾查询效率与结构完整性，推荐将字段分为两层：

1. 根级镜像字段
   - 用于高频搜索、筛选、聚合、排序

2. 结构化详情字段
   - 保留完整对象与数组明细

例如：

- 根级：`matter_name`、`colloquial_names`、`implementation_code`、`express_supported`
- 详情级：`materials[]`、`fees[]`、`service_windows[]`

补充约束：

- 根级 `colloquial_names` 必须派生自 `matter_identity.colloquial_names`
- 不再单独维护 `basic_info.daily_terms` 之类的第二套别名来源

### 4.2 行对象必须使用 nested

以下数组字段必须使用 `nested`：

- `materials`
- `fees`
- `service_windows`
- `legal_basis`
- `cross_region_service`
- `result_info`

原因：

1. 要保留单行内部字段关联关系
2. 避免跨行误命中
3. 便于做精确过滤

其中 `cross_region_service` 需要采用“摘要 + 明细”双字段策略：

- `regions_summary` 用于过滤与展示
- `regions_detail` 用于保留超长明细列表，不参与首版索引检索

例如查询：

- 材料名称 = “居民身份证”
- 且 `exempt_submission = true`

这两个条件必须命中同一材料行，不能分别命中不同行。

### 4.3 低价值大文本不入索引

`raw_sections` 建议保留在 `_source` 中，但设置为 `enabled: false`，不参与检索和倒排。

原因：

1. 它的主要用途是回链与调试
2. 全量倒排会显著增加索引体积
3. 真正需要全文检索时，应回到 `gov_doc_chunks`

### 4.4 聚合友好优先

以下字段优先按 `keyword` 存储：

- 编码类字段
- 布尔类标志
- 枚举型字段
- 聚合分面字段

### 4.5 主检索文本要单独收敛

不建议对所有对象层层全文查询。建议构造一个单独的聚合文本字段：

- `guide_search_text`

用于承接：

- 事项名称
- 日常用语
- 条件摘要
- 材料名列表
- 收费项名称
- 窗口名称
- 法律依据名称

这样可以让常规查询先命中 Guide 详情，再按 nested 条件做精细过滤。

---

## 5. 字段分层设计

### 5.1 根级镜像字段

以下字段建议镜像到根级，作为高频查询入口：

| 字段 | 类型 | 用途 |
|------|------|------|
| profile_id | keyword | 结构化详情主键 |
| doc_id | keyword | 关联 Document |
| acl_ids | keyword | ACL 过滤，与 `gov_doc_meta` 保持一致 |
| content_hash | keyword | 关联文档内容版本 |
| scene_type | keyword | 固定值 `standard_service_guide` |
| source | keyword | 来源平台 |
| is_current | boolean | 是否当前版本 |
| guide_version | keyword | 指南版本 |
| matter_name | text + keyword | 主搜索字段 |
| colloquial_names | text + keyword | 别名搜索，派生自 `matter_identity.colloquial_names` |
| matter_type | keyword | 行政许可/公共服务等 |
| implementation_code | keyword | 精确定位 |
| basic_code | keyword | 精确定位 |
| business_item_code | keyword | 精确定位 |
| matter_version | keyword | 事项版本 |
| implementing_subject | text + keyword | 主体搜索与展示 |
| implementing_subject_nature | keyword | 过滤展示 |
| delegated_department | text + keyword | 精确/模糊搜索 |
| service_objects | keyword | 自然人/法人 |
| service_modes | keyword | 网上/窗口等 |
| online_depth | keyword | 过滤与聚合 |
| hall_required | boolean | 过滤 |
| express_supported | boolean | 过滤 |
| reservation_supported | boolean | 过滤 |
| must_onsite | boolean | 过滤 |
| visit_count_to_hall | integer | 排序/展示 |
| promised_time_limit_days | integer | 数值过滤/排序 |
| legal_time_limit_days | integer | 数值过滤/排序 |
| handled_org_names | keyword | 办理部门过滤 |
| region_names | keyword | 地域过滤 |
| linked_matter_ids | keyword | 关联 Matter |
| material_names | keyword | 快速条件过滤 |
| fee_names | keyword | 快速条件过滤 |
| window_names | keyword | 快速条件过滤 |
| legal_basis_names | keyword | 快速条件过滤 |
| guide_search_text | text | 汇总全文检索入口 |
| needs_review | boolean | 质量过滤 |
| completeness_score | float | 质量排序/筛选 |
| confidence_score | float | 质量排序/筛选 |
| extracted_at | date | 最新优先排序 |
| updated_at | date | 更新时间 |

### 5.2 结构化详情字段

以下字段保留在详情对象中：

- `document_info`
- `matter_identity`
- `basic_info`
- `cross_region_service`
- `review_info`
- `result_info`
- `acceptance_info`
- `process_info`
- `materials`
- `fees`
- `legal_basis`
- `rights_and_obligations`
- `remedies`
- `consultation_and_supervision`
- `service_windows`
- `bindings`
- `quality`
- `raw_sections`

---

## 6. OpenSearch Settings 设计

### 6.1 建议 settings

```json
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    "refresh_interval": "5s",
    "analysis": {
      "analyzer": {
        "ik_max_index": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase"]
        },
        "ik_smart_synonym": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": ["lowercase", "gov_synonym"]
        }
      },
      "filter": {
        "gov_synonym": {
          "type": "synonym",
          "synonyms": [
            "办证,办理证件,申办证件",
            "护照,普通护照,因私出国普通护照",
            "预约办理,网上预约,预约办证",
            "窗口,大厅,受理点",
            "投诉,监督,举报"
          ]
        }
      },
      "normalizer": {
        "lowercase_normalizer": {
          "type": "custom",
          "filter": ["lowercase"]
        }
      }
    }
  }
}
```

### 6.2 说明

1. `number_of_shards = 1`
   - 标准办事指南数量通常远低于全文 chunk 数量
   - 首版以简化运维、降低 nested 查询成本为主

2. `refresh_interval = 5s`
   - 与现有索引保持一致，兼顾入库与查询

3. 复用 IK 分词与政务同义词
   - 与现有搜索体验保持一致
   - 便于事项口语召回

4. 同义词应偏保守
   - 不要引入强业务推断型同义词
   - 重点覆盖办事口语、证照别名、窗口别称

---

## 7. Mapping 详细设计

### 7.1 完整 mapping 草案

```json
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "profile_id": {"type": "keyword"},
      "doc_id": {"type": "keyword"},
      "acl_ids": {"type": "keyword"},
      "content_hash": {"type": "keyword"},
      "scene_type": {"type": "keyword"},
      "source": {"type": "keyword"},
      "source_url": {"type": "keyword", "index": false},
      "is_current": {"type": "boolean"},
      "guide_version": {"type": "keyword"},
      "extractor_version": {"type": "keyword"},
      "extracted_at": {
        "type": "date",
        "format": "strict_date_optional_time||epoch_millis"
      },
      "updated_at": {
        "type": "date",
        "format": "strict_date_optional_time||epoch_millis"
      },

      "matter_name": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym",
        "fields": {
          "keyword": {"type": "keyword", "ignore_above": 512}
        }
      },
      "colloquial_names": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym",
        "fields": {
          "keyword": {"type": "keyword", "ignore_above": 512}
        }
      },
      "matter_type": {"type": "keyword"},
      "implementation_code": {"type": "keyword"},
      "basic_code": {"type": "keyword"},
      "business_item_code": {"type": "keyword"},
      "matter_version": {"type": "keyword"},
      "implementing_subject": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym",
        "fields": {
          "keyword": {"type": "keyword", "ignore_above": 512}
        }
      },
      "implementing_subject_nature": {"type": "keyword"},
      "delegated_department": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym",
        "fields": {
          "keyword": {"type": "keyword", "ignore_above": 512}
        }
      },

      "service_objects": {"type": "keyword"},
      "service_modes": {"type": "keyword"},
      "online_depth": {"type": "keyword"},
      "hall_required": {"type": "boolean"},
      "express_supported": {"type": "boolean"},
      "reservation_supported": {"type": "boolean"},
      "must_onsite": {"type": "boolean"},
      "must_onsite_reason": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym"
      },
      "visit_count_to_hall": {"type": "integer"},
      "promised_time_limit_days": {"type": "integer"},
      "legal_time_limit_days": {"type": "integer"},

      "handled_org_names": {"type": "keyword"},
      "region_names": {"type": "keyword"},
      "linked_matter_ids": {"type": "keyword"},
      "material_names": {"type": "keyword"},
      "fee_names": {"type": "keyword"},
      "window_names": {"type": "keyword"},
      "legal_basis_names": {"type": "keyword"},

      "guide_search_text": {
        "type": "text",
        "analyzer": "ik_max_index",
        "search_analyzer": "ik_smart_synonym"
      },

      "needs_review": {"type": "boolean"},
      "completeness_score": {"type": "float"},
      "confidence_score": {"type": "float"},

      "document_info": {
        "properties": {
          "title": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "normalized_title": {"type": "keyword"},
          "doc_type": {"type": "keyword"},
          "knowledge_category": {"type": "keyword"},
          "publish_date": {
            "type": "date",
            "format": "yyyy-MM-dd||yyyy/MM/dd||strict_date_optional_time||epoch_millis"
          },
          "effective_date": {
            "type": "date",
            "format": "yyyy-MM-dd||yyyy/MM/dd||strict_date_optional_time||epoch_millis"
          },
          "expiry_date": {
            "type": "date",
            "format": "yyyy-MM-dd||yyyy/MM/dd||strict_date_optional_time||epoch_millis"
          },
          "issuing_org": {"type": "keyword"},
          "status": {"type": "keyword"}
        }
      },

      "matter_identity": {
        "properties": {
          "matter_name": {"type": "keyword"},
          "colloquial_names": {"type": "keyword"},
          "matter_type": {"type": "keyword"},
          "basic_code": {"type": "keyword"},
          "implementation_code": {"type": "keyword"},
          "business_item_code": {"type": "keyword"},
          "matter_version": {"type": "keyword"},
          "implementing_subject": {"type": "keyword"},
          "subject_nature": {"type": "keyword"},
          "delegated_department": {"type": "keyword"}
        }
      },

      "basic_info": {
        "properties": {
          "service_object": {"type": "keyword"},
          "promised_time_limit": {
            "properties": {
              "raw_text": {"type": "keyword", "ignore_above": 512},
              "duration": {"type": "keyword"},
              "unit": {"type": "keyword"},
              "is_working_day": {"type": "boolean"}
            }
          },
          "legal_time_limit": {
            "properties": {
              "raw_text": {"type": "keyword", "ignore_above": 512},
              "duration": {"type": "keyword"},
              "unit": {"type": "keyword"},
              "is_working_day": {"type": "boolean"}
            }
          },
          "visit_count_to_hall": {"type": "integer"},
          "must_onsite": {"type": "boolean"},
          "must_onsite_reason": {"type": "text", "index": false},
          "case_type": {"type": "keyword"},
          "notified_commitment_enabled": {"type": "boolean"},
          "hall_required": {"type": "boolean"},
          "express_supported": {"type": "boolean"},
          "reservation_supported": {"type": "boolean"},
          "reservation_url": {"type": "keyword", "index": false},
          "service_modes": {"type": "keyword"},
          "online_depth": {"type": "keyword"},
          "linked_agencies": {"type": "keyword"}
        }
      },

      "cross_region_service": {
        "type": "nested",
        "properties": {
          "service_scope_type": {"type": "keyword"},
          "regions_summary": {"type": "keyword"},
          "regions_detail": {"type": "keyword", "index": false},
          "regions_truncated": {"type": "boolean"},
          "service_modes": {"type": "keyword"},
          "notes": {"type": "text", "index": false},
          "raw_text": {"type": "text", "index": false}
        }
      },

      "review_info": {
        "properties": {
          "power_level": {"type": "keyword"},
          "power_source": {"type": "keyword"},
          "service_forms": {"type": "keyword"},
          "business_system": {"type": "keyword"}
        }
      },

      "result_info": {
        "type": "nested",
        "properties": {
          "outcome_name": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "outcome_type": {"type": "keyword"},
          "template_name": {"type": "keyword", "ignore_above": 512},
          "sample_name": {"type": "keyword", "ignore_above": 512},
          "electronic_certificate_status": {"type": "keyword"},
          "notes": {"type": "text", "index": false}
        }
      },

      "acceptance_info": {
        "properties": {
          "service_targets": {"type": "keyword"},
          "natural_person_topics": {"type": "keyword"},
          "legal_person_topics": {"type": "keyword"},
          "local_feature_topics": {"type": "keyword"},
          "application_scope": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          },
          "acceptance_conditions": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          }
        }
      },

      "process_info": {
        "properties": {
          "summary": {"type": "text", "index": false},
          "step_titles": {"type": "keyword", "ignore_above": 512},
          "raw_text": {"type": "text", "index": false},
          "notes": {"type": "text", "index": false},
          "needs_review": {"type": "boolean"}
        }
      },

      "materials": {
        "type": "nested",
        "properties": {
          "guide_material_id": {"type": "keyword"},
          "material_name": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "linked_material_id": {"type": "keyword"},
          "requirement_level": {"type": "keyword"},
          "original_count": {"type": "integer"},
          "copy_count": {"type": "integer"},
          "form_types": {"type": "keyword"},
          "paper_spec": {"type": "keyword"},
          "electronic_license_linked": {"type": "boolean"},
          "exempt_submission": {"type": "boolean"},
          "reusable_previous_submission": {"type": "boolean"},
          "material_type": {"type": "keyword"},
          "source_channel": {"type": "keyword"},
          "fill_instructions": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          },
          "notes": {"type": "text", "index": false},
          "applicable_conditions": {"type": "keyword"},
          "blank_form_available": {"type": "boolean"},
          "sample_available": {"type": "boolean"},
          "download_hint": {"type": "text", "index": false}
        }
      },

      "fees": {
        "type": "nested",
        "properties": {
          "fee_name": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "amount_text": {"type": "keyword"},
          "amount_value": {"type": "scaled_float", "scaling_factor": 100},
          "currency": {"type": "keyword"},
          "charging_body": {"type": "keyword"},
          "charging_method": {"type": "keyword"},
          "reducible": {"type": "boolean"},
          "notes": {"type": "text", "index": false}
        }
      },

      "legal_basis": {
        "type": "nested",
        "properties": {
          "law_name": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "document_no": {"type": "keyword"},
          "article_no": {"type": "keyword"},
          "issuing_body": {"type": "keyword"},
          "effective_date": {
            "type": "date",
            "format": "yyyy-MM-dd||yyyy/MM/dd||strict_date_optional_time||epoch_millis"
          },
          "article_content": {"type": "text", "index": false}
        }
      },

      "rights_and_obligations": {
        "properties": {
          "rights": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          },
          "obligations": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          }
        }
      },

      "remedies": {
        "properties": {
          "administrative_reconsideration": {
            "properties": {
              "department": {"type": "keyword"},
              "address": {"type": "text", "index": false},
              "phones": {"type": "keyword"},
              "url": {"type": "keyword", "index": false}
            }
          },
          "administrative_litigation": {
            "properties": {
              "department": {"type": "keyword"},
              "address": {"type": "text", "index": false},
              "phones": {"type": "keyword"},
              "url": {"type": "keyword", "index": false}
            }
          }
        }
      },

      "consultation_and_supervision": {
        "properties": {
          "consultation_phones": {"type": "keyword"},
          "consultation_urls": {"type": "keyword", "index": false},
          "complaint_phones": {"type": "keyword"},
          "complaint_urls": {"type": "keyword", "index": false}
        }
      },

      "service_windows": {
        "type": "nested",
        "properties": {
          "window_name": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym",
            "fields": {
              "keyword": {"type": "keyword", "ignore_above": 512}
            }
          },
          "location": {
            "type": "text",
            "analyzer": "ik_max_index",
            "search_analyzer": "ik_smart_synonym"
          },
          "office_phone": {"type": "keyword"},
          "office_hours": {"type": "text", "index": false},
          "navigation": {"type": "text", "index": false},
          "scope": {"type": "keyword"}
        }
      },

      "bindings": {
        "properties": {
          "matter_ids": {"type": "keyword"},
          "organization_bindings": {
            "type": "nested",
            "properties": {
              "name": {"type": "keyword"},
              "organization_id": {"type": "keyword"},
              "role": {"type": "keyword"}
            }
          },
          "region_bindings": {
            "type": "nested",
            "properties": {
              "name": {"type": "keyword"},
              "region_id": {"type": "keyword"},
              "role": {"type": "keyword"}
            }
          }
        }
      },

      "quality": {
        "properties": {
          "completeness_score": {"type": "float"},
          "confidence_score": {"type": "float"},
          "missing_fields": {"type": "keyword"},
          "needs_review": {"type": "boolean"},
          "warnings": {"type": "keyword"}
        }
      },

      "raw_sections": {
        "type": "object",
        "enabled": false
      }
    }
  }
}
```

---

## 8. 字段设计说明

### 8.1 为什么根级保留 `material_names` / `fee_names` / `window_names`

这些字段不是用来替代 nested 明细，而是为了：

1. 提供轻量 facet 与过滤能力
2. 降低简单查询的 nested 成本
3. 支撑搜索结果卡片的快速摘要

### 8.2 为什么 `raw_sections` 不建索引

因为结构化后的 Guide 查询应该尽量基于字段，而不是重新回退到大段原文。

`raw_sections` 的最佳用途是：

- 调试抽取器
- 质检回链
- 后台人工复核
- 二次重抽取

### 8.3 为什么 `dynamic = strict`

标准办事指南结构极易因为 prompt 漂移产生字段爆炸，因此建议首版采用严格 schema。

好处：

1. 强制抽取器输出受控字段
2. 防止错误字段悄悄入库
3. 便于版本化升级

代价：

1. 新字段上线必须显式升级 mapping
2. 抽取器一旦漂移会立即报错

这个代价是值得的，因为它能把结构质量问题尽早暴露。

### 8.4 为什么 `materials` 必须是 nested 而不是 object[]

因为 OpenSearch 对普通 object 数组会打平，导致：

- `material_name = 居民身份证`
- `exempt_submission = true`

可能来自两行不同材料，却被错误认为同一行命中。

这正是标准事项材料清单最不能接受的错误类型。

---

## 9. 写入与更新规则

### 9.1 文档主键

`profile_id` 作为 `gov_service_guides` 的主键。

推荐生成规则：

```text
guide_{sha256(doc_id + guide_version + matter_name_normalized)[:12]}
```

### 9.2 幂等写入规则

写入建议采用 `index id=profile_id` 覆盖式更新，不使用脚本 merge。

原因：

1. Guide 详情是结构化快照
2. 多层 nested 合并成本高且容易脏数据残留
3. 覆盖式写入更符合“重抽取即重建”的模型

### 9.3 与 `gov_doc_meta` 的联动写入

当标准办事指南抽取成功时，建议同步更新 `gov_doc_meta`：

- `document_scene_type = standard_service_guide`
- `guide_profile_id`
- `guide_matter_name`
- `guide_codes`
- `service_guide_status = completed`

同时写入 `gov_service_guides.acl_ids = gov_doc_meta.acl_ids`。

说明：

- `acl_ids` 在 Guide 索引中是复制字段，不是权限真值源
- `matter_identity.colloquial_names` 是别名事实源，`gov_doc_meta` 不再单独维护 `guide_colloquial_names`

如果抽取失败：

- `service_guide_status = failed`
- 不影响文档主入库完成态

### 9.4 ACL 同步规则

1. 首次写入 Guide 时，`acl_ids` 直接取文档元数据中的 `acl_ids`
2. 权限变更任务更新 `gov_doc_meta` 后，需按 `doc_id` 覆盖更新对应 Guide 文档的 `acl_ids`
3. 查询时默认先放行公开 Guide；遇到非空 `acl_ids` 再沿用现有 PermissionService 构造的 filter
4. 文档删除时，需按 `doc_id` 同步删除对应 Guide 文档

### 9.5 失败策略

推荐将 Guide 抽取视为“可降级非阻塞阶段”：

1. 文档入库成功
2. Guide 抽取失败
3. 则总入库状态为 `partial_failed`

这样不会影响全文检索和基础 QA。

---

## 10. 查询设计建议

### 10.1 ACL 过滤前置

所有对 `gov_service_guides` 的查询都必须先附加 ACL 过滤，再执行检索和 nested 条件：

1. `acl_ids = []` 视为公开，允许匿名访问
2. 非公开文档要求用户 ACL 令牌与 `acl_ids` 有交集
3. 过滤逻辑与现有 search/document 接口保持一致
4. 公开查询接口应支持“无 JWT 时只查公开 Guide；有 JWT 时公开 + 授权 Guide 一并返回”

### 10.2 常规搜索

优先在以下字段上检索：

- `matter_name`
- `colloquial_names`
- `guide_search_text`

### 10.3 精确定位

精确匹配优先字段：

- `implementation_code`
- `basic_code`
- `business_item_code`
- `profile_id`
- `doc_id`

### 10.4 高频过滤

建议首版支持的过滤器：

- `matter_type`
- `service_objects`
- `service_modes`
- `online_depth`
- `express_supported`
- `reservation_supported`
- `must_onsite`
- `handled_org_names`
- `region_names`
- `needs_review`

### 10.5 典型 nested 查询示例

以下示例省略了统一 ACL filter；实际执行时应放在外层 `bool.filter` 中。

#### 材料行查询

```json
{
  "query": {
    "nested": {
      "path": "materials",
      "query": {
        "bool": {
          "must": [
            {"term": {"materials.material_name.keyword": "居民身份证"}},
            {"term": {"materials.exempt_submission": true}}
          ]
        }
      },
      "inner_hits": {"size": 3}
    }
  }
}
```

#### 收费项查询

```json
{
  "query": {
    "nested": {
      "path": "fees",
      "query": {
        "bool": {
          "must": [
            {"term": {"fees.reducible": false}},
            {"range": {"fees.amount_value": {"gte": 10000}}}
          ]
        }
      }
    }
  }
}
```

### 10.5 排序建议

默认排序建议：

1. 精确编码命中优先
2. `matter_name.keyword` 精确匹配优先
3. `_score` 降序
4. `is_current = true` 优先
5. `extracted_at` 降序

---

## 11. 与现有代码的落地点

### 11.1 `backend/app/infrastructure/es_client.py`

建议新增：

- `GOV_SERVICE_GUIDES_MAPPING`
- `settings.es_service_guide_index`
- `create_indices()` 中的 Guide 索引初始化逻辑

### 11.2 `backend/app/core/search_engine.py`

短期内不建议把 Guide 搜索完全并入全文搜索主链路。

建议：

1. 先独立实现 Service Guide 查询服务
2. 再由 Query Planner 决定何时路由到 Guide 索引

### 11.3 `backend/app/core/ingest_pipeline.py`

建议新增 Guide 结构化抽取和写索引阶段：

- `service_guide_detect`
- `service_guide_extract`
- `service_guide_index`

---

## 12. 验收标准

以下结果可作为 mapping 设计落地验收标准：

1. 材料、收费、窗口等数组字段均使用 `nested` 并可正确做同一行过滤。
2. 事项名称、日常用语、实施编码三类入口均可稳定查询。
3. `raw_sections` 保留在 `_source` 中，但不会显著放大索引倒排。
4. 可以按 `express_supported`、`reservation_supported`、`must_onsite` 等布尔字段直接过滤。
5. `gov_doc_meta` 与 `gov_service_guides` 可以通过 `doc_id` 稳定关联。

---

## 13. 最终建议

`gov_service_guides` 的 mapping 设计重点不是“字段尽量多”，而是三件事：

1. 根级镜像字段足够支撑高频搜索和过滤
2. 行对象用 nested 保住语义正确性
3. 原文与质量信息可回链但不过度索引

这三点做好后，标准办事指南才会从“能入库的文档”升级成“可稳定查询的结构化业务对象”。