# 技术设计文档

## Overview

本设计为现有 Laravel 11 项目增加法国 LEKO/CITEO 统一同步 API。API_Flow 直接消费 `Data` 与 `bizParam`，生成文件、上传 OSS 并返回固定文件槽。它对 `sqlsrv_source` 及 Source Repository 保持零读写，但法国公司 LEKO 必须继续通过现有 `InseeApiClient` 调用 INSEE 官方 SIRENE API。

本次纠正的核心边界是：INSEE_API 是必要的外部 HTTP 依赖，不是 Source_DB。`InseeLegalForm`、`InseeNafCode`、`InseeSiret` 从请求契约删除；服务端以 `RegNumber` 派生 SIREN，查询并映射 `_insee.legal_form`、`_insee.naf_code`、`_insee.siret_siege`。Source_Flow 的数据库、INSEE 重试、状态、附件和通知行为不修改。

### 目标

- 两个 PushType 使用同步 JSON 接口和固定 file1-file10 响应。
- 从真实生成器反推最小 Data 与 bizParam，不加入未被消费的字段。
- LEKO 法国公司复用现有 INSEE 查询键、端点、映射、默认值和重试语义。
- API_Flow 对 Source_DB 零读写，同时允许模板、LibreOffice、INSEE_API 和 OSS。
- 保持 Source_Flow 行为与既有接口兼容。

### 非目标

- 不改变 `EprRegProcessor`、旧 CITEO Id API 或 Source_Flow。
- 不让调用方覆盖服务端 INSEE 数据。
- 不增加任务表、异步队列、结果查询或数据库补偿。
- 不在本阶段实施代码或创建 `tasks.md`。

### 调研结论

1. `EprRegProcessor::fetchInseeData()` 仅对 `CountryTwoCode=FR` 查询；查询键为 RegNumber 去非数字后前 9 位，长度不是 9 时返回空 INSEE_Data。
2. `InseeApiClient` 使用 SIRENE 3.11 的 SIREN/SIRET 端点和 `X-INSEE-Api-Key-Integration`；HTTP 非 200 抛出异常。
3. 现有编排对 INSEE 最多尝试 5 次，间隔 6 秒；最终失败后不生成文件。API_Flow 延续此策略，但不执行 Source 状态或通知副作用。
4. XLSX 的 J/L/O 只读取 `_insee`；其他 LEKO 输入来自 `XlsxGenerator`、`DocxGenerator` 的实际读取字段。
5. CITEO 最小字段由 `CiteoPoaService::REQUIRED_FIELDS` 和营业执照号分支确定，不需要 INSEE。
6. 现有 LEKO OSS 目录消费 BusinessSerialNumber；CITEO 生成器只用 NameEng 构造目录。因此 bizParam 仅 LEKO 条件要求 BusinessSerialNumber，BusinessId/Businessld 均非生成必需。

参考：[INSEE Sirene API 目录](https://portail-api.insee.fr/catalog/api/2ba0e549-5587-3ef1-9082-99cd865de66f)、[Laravel Validation](https://laravel.com/docs/11.x/validation)、[Laravel HTTP Client](https://laravel.com/docs/11.x/http-client)。

## Architecture

```mermaid
flowchart LR
    SaaS --> GW[Unified Gateway]
    GW --> API[French File API]
    API --> VAL[Validator]
    VAL --> MAP[DTO Mapper]
    MAP --> FAC[FrenchFileGenerationService]
    FAC -->|LEKO| LI[LekoInseeResolver]
    LI --> IC[InseeApiClient]
    IC --> INSEE[(INSEE SIRENE API)]
    FAC -->|LEKO| LG[LekoApiFileGenerator]
    FAC -->|CITEO| CG[CiteoPoaGenerator]
    LG --> XLSX[XlsxGenerator]
    LG --> DOCX[DocxGenerator]
    CG --> WORD[WordTemplateProcessor]
    LG --> PDF[PdfConverter]
    CG --> PDF
    LG --> OSS[OSS]
    CG --> OSS

    SF[Existing Source_Flow] --> SDB[(Source_DB)]
    SF --> IC
    API -. zero reads/writes .-> SDB
```

### 依赖规则

- API_Flow 允许：请求、模板、字体、临时文件、时钟/随机源、`InseeApiClient`、PDF 工具、OSS。
- API_Flow 禁止：`SourceRepository`、`SourceStatusUpdater`、`SourceAttachmentRepository`、`TargetRepository` 和任何 `sqlsrv_source`/source 附件状态访问。
- `InseeApiClient` 是外部服务适配器，不归入 Repository 禁止清单。
- API_Flow 与 Source_Flow 可共享纯生成器和 `InseeApiClient`，不得共享含数据库副作用的 `EprRegProcessor::processSingleRecord()`。

### 请求时序

```mermaid
sequenceDiagram
    participant G as Gateway
    participant V as Validator/Mapper
    participant S as Service
    participant I as InseeApiClient
    participant F as Generator
    participant O as OSS

    G->>V: POST envelope
    V->>V: validate minimal fields
    V->>S: immutable DTO
    alt LEKO and company country FR and valid 9-digit SIREN
        S->>I: fetchCompanyData(SIREN), up to 5 attempts
        I->>I: GET /siren/{SIREN}
        opt headquarters NIC exists
            I->>I: GET /siret/{SIRET}
        end
        I-->>S: INSEE_Data
    else non-FR or invalid derived SIREN
        S->>S: empty INSEE_Data, no INSEE HTTP call
    end
    S->>F: generate(mapped data)
    F->>O: upload
    O-->>F: absolute URL(s)
    F-->>G: sync file slots + original bizParam
```

### 路由和传输

新增端点仍设计为 `POST /api/epr/fr/file-generation`，仅接受 `application/json`。中间件处理请求体大小、Bearer 与限流；下游再次校验 `Country=FR` 和 PushType 白名单。模板路径、INSEE URL、API Key 和 OSS 目录不接受请求控制。

## Components and Interfaces

### FrenchFileGenerationRequest

公共信封要求 `PushType`、`Country`、对象型 `Data`、对象型 `bizParam`。业务规则：

- LEKO 固定 Data：NameEng、RegAddressEng、CompanyAddressLine1En、CompanyAddressPostcode、CityEngName、CountryTwoCode、RegNumber、LegalPersonFullNamePinYin、LegalPersonEmail、LegalPersonPhone、LegalPersonGender。
- LEKO 条件 Data：CN 需要 AreaName；FR 需要 RegisteredCapital/RegisteredCapitalCurrency；EU 需要 VATNumber。
- LEKO 不为三项旧 INSEE 输入建立校验或映射规则；即使请求额外携带，也不得覆盖服务端官方查询结果。
- CITEO 固定 Data：NameEng、CountryTwoCode、CityEngName、RegAddressEng、LegalPersonFullNamePinYin。
- CITEO 条件 Data：EU 且非 FR 需要 VATNumber；其他国家需要 RegNumber。
- bizParam：LEKO 要求 BusinessSerialNumber；CITEO 无生成必填子字段。对象中已有的 BusinessId/Businessld/其他键原样回传。

### DTO 与 Mapper

```php
final readonly class FrenchFileGenerationInput
{
    public function __construct(
        public FrenchPushType $pushType,
        public string $country,
        public array $data,
        public array $originalBizParam,
        public ?string $businessSerialNumber,
    ) {}
}

final readonly class InseeData
{
    public function __construct(
        public ?string $legalForm,
        public ?string $nafCode,
        public ?string $headOfficeSiret,
    ) {}
}
```

Mapper 只做 trim、国家码大写、HK/其他国家省份默认和 bizParam 的只读提取。它不从 Data 映射任何 `Insee*` 字段。`LekoGenerationData` 在 INSEE 解析完成后组装 `_insee`：`legal_form`、`naf_code`、`siret_siege`。

### LekoInseeResolver

接口：

```php
public function resolve(string $countryCode, string $regNumber): array;
```

算法严格复刻当前实现：

1. 国家不是 `FR`，返回三个 null，不调用客户端。
2. `preg_replace('/\D/', '', $regNumber)` 后取前 9 位。
3. 长度不是 9，返回三个 null，不调用客户端。
4. 长度为 9，调用 `InseeApiClient::fetchCompanyData()`，最多 5 次，失败间隔 6 秒。
5. 最终异常转换为可分类的 INSEE 查询失败；API 不执行数据库状态更新或企业微信通知。

重试器和 Sleeper 应注入，以便测试不真实等待。重试次数与间隔为现有行为常量，不在请求中开放。

### InseeApiClient 映射

- SIREN URL：`https://api.insee.fr/api-sirene/3.11/siren/{siren}`。
- SIRET URL：`https://api.insee.fr/api-sirene/3.11/siret/{siret}`。
- Key：`config('services.insee.api_key')`，请求头 `X-INSEE-Api-Key-Integration`。
- SIREN 响应读取 `uniteLegale.periodesUniteLegale[0]`。
- 法律形式：`categorieJuridiqueUniteLegale` 经既有 `FORMES_JURIDIQUES`；空值为 `Non renseignée`，未知为 `Code juridique inconnu ({code})`。
- NAF：`activitePrincipaleUniteLegale`，缺失为 `N/A`，移除 `.`。
- 总部 SIRET：SIREN + `nicSiegeUniteLegale`；无 NIC 为 `N/A` 且不查 SIRET。
- 地址来自 SIRET 响应，仅保留于返回结构，当前 XLSX 不使用。
- HTTP 非 200 抛 `RuntimeException("INSEE API error (HTTP {code})")`；当前客户端对 JSON 解码失败返回空数组。本功能不虚构更严格语义。

### LekoApiFileGenerator

接收已附加 `_insee` 的记录对象，复用 `XlsxGenerator`、`DocxGenerator`、`PdfConverter`、`OssUploader`。J/L/O 从 `_insee` 读取；空 `_insee` 经当前 `??` 规则得到 `Autre / Other`、空、空。BusinessSerialNumber 只用于受控 OSS 目录。文件名维持现有 LEKO 语义。

### CiteoPoaGenerator

从 `CiteoPoaService` 抽出纯生成部分。EU 且非 FR 使用 VATNumber，否则使用 RegNumber。只消费最小字段、当前日期和签名生成器；PDF 转换参数 `false` 保留全部页面。旧 `CiteoPoaService::generate(EprRegInfoId)` 继续查 SourceRepository 后委托生成器，旧路由与响应不变。

### 响应工厂

LEKO：file1=XLSX，file2=POA；CITEO：file1=POA。其余槽为空字符串。非空槽必须是完整 HTTP(S) URL；完整原始 bizParam 深度原样回传。

## Data Models

### 最小输入字段

| 流程 | 固定 Data | 条件 Data | bizParam |
| --- | --- | --- | --- |
| LEKO | NameEng, RegAddressEng, CompanyAddressLine1En, CompanyAddressPostcode, CityEngName, CountryTwoCode, RegNumber, LegalPersonFullNamePinYin, LegalPersonEmail, LegalPersonPhone, LegalPersonGender | CN: AreaName；FR: RegisteredCapital, RegisteredCapitalCurrency；EU: VATNumber | BusinessSerialNumber 必填；其余键仅透传 |
| CITEO | NameEng, CountryTwoCode, CityEngName, RegAddressEng, LegalPersonFullNamePinYin | EU 且非 FR: VATNumber；其他: RegNumber | 无固定必填子字段，仅透传 |

`CompanyAddressLine2En` 和三个 `Insee*` 字段均不在法国最小输入中。BusinessId/Businessld 不参与当前法国文件内容、INSEE 查询或实际 OSS 目录，不作为固定必填。

### LEKO XLSX 关键映射

| 列 | 来源 |
| --- | --- |
| A-F | NameEng、地址一、邮编、城市、国家码、省份派生值 |
| J | `_insee.legal_form`，无值时 `Autre / Other` |
| L | `_insee.naf_code`，无值时空字符串 |
| M | RegNumber，也是法国公司 INSEE 查询的原始来源 |
| N | 仅 FR 的 RegisteredCapital + RegisteredCapitalCurrency，句点换逗号 |
| O | `_insee.siret_siege`，无值时空字符串 |
| P-S/U-Y/AA | 性别、姓名拆分、邮箱、格式化电话 |
| AC | EU 公司 VATNumber |
| AD/AJ | 当前年份 1 月 1 日；一位小数法式格式 |
| 其他列 | 当前 XlsxGenerator 固定值和随机范围 |

### INSEE 数据模型

| 字段 | 官方来源/规则 | XLSX |
| --- | --- | --- |
| siren | RegNumber 数字化后前 9 位 | 不直接写入 |
| legal_form_code | 首个期间 categorieJuridiqueUniteLegale | 不直接写入 |
| legal_form | 现有法律形式表解析 | J |
| naf_code | 首个期间 activitePrincipaleUniteLegale 去点 | L |
| siret_siege | siren + nicSiegeUniteLegale | O |
| adresse_siege | SIRET 响应地址格式化 | 当前不写入 |

### CITEO 模板映射

| 占位符 | 来源 |
| --- | --- |
| NameEng | Data.NameEng |
| BusinessLicenseNo | EU 且非 FR 为 VATNumber，否则 RegNumber |
| CityEngName | 去除末尾“市”或不区分大小写 `shi` |
| RegAddressEng | Data.RegAddressEng |
| LegalPersonFullNamePinYin / 签名 | 同名 Data 字段 |
| CurrentDay | 运行日期 `Y.m.d` |

### 状态与持久化

API_Flow 无数据库状态和事务。INSEE 查询发生在生成前，最终失败不上传文件。若上传阶段部分成功，返回整体失败，不写 Source_DB 补偿；孤立 OSS 对象由生命周期策略处理。Source_Flow 的既有持久化不变。

## Correctness Properties

*属性是在系统所有有效执行中都应保持为真的特征或行为，本质上是系统应当做什么的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。*

前置分析已完成属性反思：信封与 bizParam 条件合并；Source 零读写相关标准合并；INSEE 调用条件与 SIREN 派生合并；LEKO/CITEO 各自条件必填合并；INSEE 响应缺失与正常映射合并；响应文件槽与 URL/bizParam 合并。具体模板、旧 Source_Flow、文档内容和多页 PDF 使用示例/集成测试，避免重复属性。

### Property 1：按流程的信封与 bizParam 判定

对于所有（For all）JSON 信封，只有公共四字段类型合法、国家与 PushType 一致，并且 LEKO 的 bizParam 含非空字符串或整数 BusinessSerialNumber 时才通过信封校验；CITEO 对任意对象型 bizParam 均不额外要求 BusinessSerialNumber、BusinessId 或 Businessld。

**Validates: Requirements 1.1, 1.4, 1.5, 1.6**

### Property 2：API_Flow 的 Source 隔离

对于所有（For all）合法 LEKO/CITEO 请求，在 Source_DB 和所有 Source/Target Repository 被设为调用即失败时，流程不产生任何 source 查询、写入或组件调用；只要其实际外部依赖可用，流程仍可完成。

**Validates: Requirements 2.1, 2.2, 2.3**

### Property 3：LEKO 条件校验模型

对于所有（For all）LEKO Data 和二字国家码，错误字段集合应等于参考模型：十一项固定字段；CN 增加 AreaName；FR 增加 RegisteredCapital 与 RegisteredCapitalCurrency；EU 增加 VATNumber。

**Validates: Requirements 3.1, 3.2, 3.3, 3.4**

### Property 4：未消费字段不改变契约

对于所有（For all）合法法国请求，增加、删除或改变 CompanyAddressLine2En、InseeLegalForm、InseeNafCode、InseeSiret 以及流程未消费字段，不得改变校验结果、服务端 INSEE 查询结果或生成字段；调用方 Insee 值不得覆盖官方值。

**Validates: Requirements 3.5, 3.9, 6.4**

### Property 5：LEKO 长度约束

对于所有（For all）Unicode 字符串，NameEng 当且仅当字符数不超过 50 且 CompanyAddressLine1En 当且仅当字符数不超过 100 时通过对应长度规则；超限错误包含字段名和上限。

**Validates: Requirements 3.6**

### Property 6：省份默认映射

对于所有（For all）合法国家码与 AreaName，非空 AreaName 保持不变；HK 映射为“香港”；非 CN/HK 的空值映射为大写国家码。

**Validates: Requirements 3.7, 3.8**

### Property 7：INSEE 查询决策与 SIREN 键

对于所有（For all）LEKO 国家码和 RegNumber，仅当国家为 FR 且 RegNumber 去非数字后前 9 位的长度恰为 9 时，InseeApiClient 才恰被调用并收到该 9 位 SIREN；其他情况调用次数为零并返回空 INSEE_Data。

**Validates: Requirements 2.4, 4.1, 4.2, 4.9, 4.10**

### Property 8：INSEE 响应映射

对于所有（For all）结构合法或字段缺失的 SIREN 响应，映射器从首个期间解析法律形式、去点 NAF 和 SIREN+NIC；有 NIC 时恰进行一次 SIRET 查询，无 NIC 时不查询且 siret_siege 为 `N/A`，空/未知法律形式和缺失活动代码均使用规定回退值。

**Validates: Requirements 4.4, 4.5, 4.6, 4.7**

### Property 9：INSEE 重试与安全失败

对于所有（For all）连续失败的 INSEE 调用序列，LEKO API_Flow 最多调用客户端 5 次、前四次失败后各请求一次 6 秒等待；第五次仍失败时不调用生成器、OSS 或 Source 组件，并返回不含 API Key/内部端点的 INSEE 分类错误。

**Validates: Requirements 4.8, 8.3**

### Property 10：LEKO XLSX 参考模型

对于所有（For all）合法 LEKO Data、服务端 INSEE_Data、时钟和随机值，A:AJ 业务列等于当前 XlsxGenerator 参考模型，且 J/L/O 只等于服务端 `_insee` 映射或其现有默认值，与请求中的任何 Insee 字段无关。

**Validates: Requirements 5.2, 5.3**

### Property 11：LEKO POA 与上传契约

对于所有（For all）合法 LEKO 数据，POA 内容包含规定文本、日期和同一法人姓名生成的签名；成功时上传恰为一个 `.xlsx` 与一个 `POA-{净化NameEng}.pdf`。

**Validates: Requirements 5.4, 5.5**

### Property 12：CITEO 条件校验和执照号

对于所有（For all）CITEO Data 和国家码，五项固定字段必填；EU 且非 FR 时 VATNumber 必填且成为 BusinessLicenseNo，否则 RegNumber 必填且成为 BusinessLicenseNo。

**Validates: Requirements 6.1, 6.2, 6.3**

### Property 13：CITEO POA 生成契约

对于所有（For all）合法 CITEO 数据和时钟，生成内容包含最小字段派生的所有占位值与签名，转换使用全页模式，上传文件名严格为 `POA-{净化NameEng}.pdf`。

**Validates: Requirements 6.5**

### Property 14：统一同步响应模型

对于所有（For all）合法绝对 OSS URL、嵌套 bizParam 和两个 PushType，响应恒为 HTTP/code 200、msg=success、ProcessMode=sync 并原样回传 bizParam；LEKO 使用 file1/file2，CITEO 仅用 file1，其余槽为空，非空槽均为完整 HTTP(S) URL。

**Validates: Requirements 7.1, 7.2, 7.3, 7.4**

### Property 15：聚合校验错误

对于所有（For all）同时包含多个缺失、类型、国家条件和长度违规的请求，单次 400 响应的错误键集合包含参考校验模型识别出的全部违规字段。

**Validates: Requirements 8.1**

### Property 16：生成故障安全响应

对于所有（For all）模板、DOCX、PDF、OSS 故障及任意含路径/堆栈/凭证的底层消息，公开响应为分类后的安全 400、data=null，不包含底层敏感文本。

**Validates: Requirements 8.2**

### Property 17：未分类异常安全响应

对于所有（For all）未分类 Throwable，公开响应为 HTTP/code 500、固定通用消息和 data=null，不回显内部消息。

**Validates: Requirements 8.4**

### Property 18：日志脱敏

对于所有（For all）Bearer、INSEE API Key、注册号、VAT 和配置敏感键值，日志中不得出现原值，只能出现掩码或不可逆指纹。

**Validates: Requirements 8.5**

### Property 19：方法、媒体类型与认证

对于所有（For all）方法、Content-Type、认证开关和 Authorization 组合，只有 POST JSON 且认证启用时 Bearer 精确匹配的请求可进入业务校验；其他请求返回对应 405、400 或 401。

**Validates: Requirements 9.1, 9.2**

### Property 20：限流窗口

对于所有（For all）正整数 N 和同一客户端，窗口内前 N 次请求不因限流被拒绝，第 N+1 次起返回 HTTP/code 429，窗口重置后重新允许。

**Validates: Requirements 9.4**

## Error Handling

| 类别 | HTTP/code | 行为 |
| --- | --- | --- |
| JSON/信封/Data/大小/媒体类型 | 400 | 聚合可修复错误 |
| 方法/认证/限流 | 405/401/429 | 统一 JSON |
| INSEE 非 200 或网络失败，5 次后仍失败 | 400 | `INSEE 查询失败` 分类；不生成、不降级到请求 Insee 字段、不查 Source_DB |
| 模板/DOCX/PDF/OSS | 400 | 安全分类消息，无本地路径 |
| 未分类异常 | 500 | 固定通用消息 |

INSEE 重试只覆盖客户端抛出的失败。每次调用仍沿用当前 connect timeout 15 秒、总 timeout 30 秒。日志可记录脱敏 SIREN、HTTP 状态、尝试序号和 request_id，但不得记录 API Key、完整注册号或返回中的敏感地址。现有客户端设置 `CURLOPT_SSL_VERIFYPEER=false` 是已发现的安全债务；本功能保持真实行为，不把更改 TLS 行为混入兼容性需求，实施前应由用户单独确认是否修复。

临时文件在所有成功/失败路径清理。INSEE 最终失败发生于文件生成前，不产生临时生成文件和 OSS 对象。上传阶段部分失败不写数据库补偿，也不返回部分 URL。

## Testing Strategy

采用单元、属性、功能、集成、契约和 Source_Flow 回归测试，覆盖率目标不低于 80%。单元测试验证具体示例、边界、适配器请求和异常；属性测试验证跨大量输入的普遍规则，两者互补。

### 属性测试

实施时使用与 PHP 8.2/PHPUnit 10 兼容并锁定精确版本的 `giorgiosironi/eris`，不自行实现随机框架。每个属性至少 100 次，每个 Correctness Property 由且仅由一个属性测试实现。测试注释格式：

```php
// Feature: lkeo-citeo-api-file-generation, Property 7: INSEE 查询决策与 SIREN 键
```

生成器覆盖 Unicode、空白、带标点/字母的 RegNumber、0/8/9/10+ 位数字、全部 EU/非 EU 国家、嵌套 bizParam、INSEE 字段缺失和多错误组合。

### 单元与边界

- LEKO/CITEO 最小字段集合逐项删除测试；确认三个 Insee 字段和 CompanyAddressLine2En 非必填。
- NameEng 50/51、地址 100/101；请求体 N/N+1 字节。
- SIREN 派生：格式化法国注册号、少于 9 位、超过 9 位只取前 9 位。
- InseeApiClient：准确 SIREN/SIRET URL、API Key header、首个期间、NAF 去点、法律形式已知/未知/空、NIC 有/无、非 200。
- 重试 fake：成功发生在第 1-5 次及连续 5 次失败，断言 6 秒等待次数，不真实 sleep。
- XLSX J/L/O：官方返回值覆盖请求中的伪 Insee 字段；空 INSEE_Data 使用现有默认。
- CITEO 执照号分支、城市后缀、全页转换参数和净化文件名。
- 响应槽、完整 URL、任意 bizParam 深度相等。

### 集成与功能

- 使用真实 LEKO XLSX/DOCX 和 CITEO DOCX 模板检查单元格、XML、media 与占位符。
- 使用 fake INSEE HTTP 层验证两段查询和最终生成；测试不访问真实官方接口。
- 使用多页 CITEO 夹具验证全部页面保留。
- 将 `sqlsrv_source` 设为不可连接并挂 DB listener，分别完成 LEKO/CITEO 请求；LEKO 的 INSEE 使用 fake，证明“Source 不可用”与“外部 INSEE 可用”可以并存。
- 仅 Id/EPRRegInfoId 的新请求返回 400 且 SourceRepository 零调用。
- INSEE 连续失败时生成器、OSS、Source Repository 均零调用。

### Source_Flow 回归

保留现有命令、旧 CITEO Id API、`EprRegProcessorTest`、`InseeApiClientTest`、`XlsxGeneratorTest`、`CiteoPoaServiceTest`。增加 golden 测试确认抽取前后 Source_Flow 的查询条件、INSEE 5×6 秒策略、状态/附件/通知和文件结果不变。

### 文档契约

解析 `UNIFIED_API_DESIGN.md` 的两个法国 JSON 示例并通过生产 Validator；静态断言 LEKO 示例没有三个 Insee 字段，字段总表将其标为服务端派生而非请求字段，最小字段表与 Validator 集合一致，并明确 Source_DB 零读写但 LEKO 法国公司调用 INSEE。

### 需求追踪

| 需求 | 设计/测试 |
| --- | --- |
| 1 | Request/路由/响应；Properties 1 |
| 2 | 依赖图、隔离故障注入、Source 回归；Properties 2, 7 |
| 3 | LEKO Validator/Mapper；Properties 3-6 |
| 4 | Resolver/InseeApiClient；Properties 7-9 |
| 5 | LEKO Generator/XLSX/POA；Properties 10-11 |
| 6 | CITEO Validator/Generator；Properties 12-13 |
| 7 | Response Factory；Property 14 |
| 8 | 异常映射/Redactor/文档契约；Properties 15-18 |
| 9 | 中间件；Properties 19-20 与大小边界测试 |

本设计不创建 `tasks.md`，不实施代码。若后续发现 INSEE API 的认证头、端点版本或重试契约已由运维侧变更，应先返回需求澄清，不得自行改变当前项目行为。
