k-skill seoul-weather-risk 行政洞解析设计:如何把「행정동 이름 + 자연어」确定性转换为 ASK Seoul 的 place_id
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
本文围绕仓库 docs/superpowers/specs/2026-08-09-seoul-weather-risk-admin-dong-resolution-design.md 展开,并结合 实施计划、helper 源码、单元测试 与 功能指南 进行纵深讲解。
一、设计目标:让普通用户不再需要知道 place_id
seoul-weather-risk是 k-skill 仓库中面向「서울(首尔)气象风险」的只读技能:它以weather_place_risk_window这一单产品为唯一数据出口,返回指定地点在特定时间窗内的 폭염(酷暑)、한파(寒潮)、호우(暴雨)、대설(大雪)、강풍(大风)等风险候选时段。
该产品在数据契约层面以place_id作为地点主键,格式为seoul_admd_<10자리 행정동 코드>(10 位行政洞代码)。但普通用户并不知道这一内部 ID。因此本次设计的核心目标是:
让用户即使不知道
place_id,也能通过「행정동 이름(行政洞名称)+ 자연어 질문(自然语言提问)」使用该技能。Skill helper 在本地把行政洞名称确定性地解析为 ASK Seoul 的正规place_id,再调用既有 hosted proxy 的只读 data 路径。
换句话说,这是一次「输入层人文化、数据契约零改动」的增强:用户层只接触人类可读的地名,而下游 proxy 与 ASK Seoul 服务仍然只收到place_id。
二、不改变的安全边界(设计红线)
设计文档首先明确划定了「变更边界」,这是整套方案安全性的前提,原文逐条保留如下:
- 目标产品维持
weather_place_risk_window单一产品不变; - 不拓宽 hosted proxy 的认证、scope、单产品 allowlist 边界;
- 不修改 D1、Worker、Publisher、dbt 模型及 Marketplace 数据契约;
- 不新增任意 SQL、fuzzy search、坐标 geocoding、实时位置推断;
- 既有
--filter place_id=...高级用户路径保持向下兼容。
从源码看,这些边界都有对应实现佐证。例如 seoul_weather_risk.py 中EXACT_PRODUCT_IDS仅包含weather_place_risk_window一个产品;bundle 校验函数 会校验 bundle 返回的产品集合与该集合完全一致,出现漂移(drift)时直接以response_contract_invalid契约错误中止(对应测试test_bundle_single_product_drift_fails_closed)。这正是「单产品 allowlist 不拓宽」的实现证据。
三、基准数据与版本:427 个行政洞的冻结快照
解析所依赖的基准数据来自外部数据管线,设计文档给出的关键事实如下:
| 项 | 值 |
|---|---|
| 基准源 | ASAC-DBTweather_place_grid_mapping.csv |
| 基准版本 | mapping_method=kma_admin_dong_grid_20260325 |
| 快照规模 | 서울 행정동 427 个,唯一place_id427 个 |
place_id格式 | seoul_admd_<10자리 행정동 코드> |
| 名称重复 | 신사동1 组同名(강남구 / 관악구) |
关键约束:技能必须携带一份版本冻结的 JSON reference,运行时不依赖任何外部仓库或 API。reference 只保存最小字段:mapping_version、source、generated_at,以及每条记录admin_dong、gu、place_id。
仓库中的实际快照位于 seoul-weather-risk/references/admin-dong-place-map.json,头部即包含"mapping_version":"kma_admin_dong_grid_20260325"、"source":"ASAC-DBT/domains/traffic_weather/seeds/weather/weather_place_grid_mapping.csv"、"generated_at":"2026-08-09"。数据按place_id排序,例如잠실본동(송파구)→seoul_admd_1171065000,신사동分别出现在 강남구(seoul_admd_1168051000)与 관악구(seoul_admd_1162068500)两条记录中。
reference 的加载与校验实现在 源码_load_location_mapping:逐项检查版本号、source、generated_at非空、行数必须等于 427、每行字段集合必须精确为{admin_dong, gu, place_id}且均为非空字符串、place_id必须匹配seoul_admd_+ 10 位数字、不得存在重复place_id或重复(行政洞, 自治区)组合;任何违反都以location_mapping_invalid失败。常量LOCATION_MAPPING_VERSION与LOCATION_MAPPING_SIZE定义于 源码第 26-27 行。
四、输入契约:位置输入「三选一」
query命令新增以下可选参数(实现在 parser 定义处):
--admin-dong <행정동명>:普通用户默认位置输入;--gu <자치구명>:仅用于解决同名歧义,必须与--admin-dong同时使用;- 既有
--filter place_id=<정규 ID>:高级用户与既有调用的兼容路径。
互斥规则:位置输入只允许「恰好一种方式」。
| 输入组合 | 结果 |
|---|---|
--admin-dong与place_idfilter 同时给出 | conflicting_location_input失败 |
仅--gu,无--admin-dong | invalid_location_input失败 |
仅--admin-dong(无歧义) | 解析为唯一place_id |
仅--admin-dong(同名歧义) | ambiguous_admin_dong+ 候选自治区列表 |
--admin-dong+--gu(可确认唯一) | 解析为对应place_id |
源码 run 函数中的校验段 完整实现了这一契约:先检查--gu是否伴随--admin-dong,再检查两者是否为空串(invalid_location_input),随后若place_id已存在于 filters 中则抛conflicting_location_input,最后调用_resolve_admin_dong并把解析出的正规 ID 写入filters["place_id"]。对应的 测试用例 验证了--gu无--admin-dong与冲突输入的失败路径,且确保失败时不会把admin_dong、gu字符串泄漏进上游请求参数。
五、规范化与解析规则:精确匹配优先、安全失败
设计文档明确规定了 4 步字符串规范化流程:
- Unicode NFC 规范化;
- 去除首尾空白;
- 连续空白折叠为单空格;
- 仅接受规范化字符串的精确匹配。
同时明确不做的事:不做后缀去除、不做 초성(初声)搜索、不做相似字符串匹配、不做自动错别字修正。设计哲学是「宁可安全失败,也不查错地点」——错误地返回另一个洞的数据比报错危害更大。
源码中对应实现为_normalize_location_name:
def _normalize_location_name(value: str) -> str: return " ".join(unicodedata.normalize("NFC", value).strip().split())解析结果矩阵
| 候选情况 | 结果 |
|---|---|
| 候选 1 个 | 转换为该place_id |
候选多个、未给--gu | 返回ambiguous_admin_dong+ 候选自治区列表 |
候选多个、--gu确认 1 个 | 转换为该place_id |
| 候选 0 个 | unknown_admin_dong |
--gu未知 | unknown_gu |
确定性的书写别名(来自 instruction.md 的补充约定)
虽然「精确匹配」是主路径,但韩国行政洞名的书写变体是现实问题。instruction.md 与实现补充了一套可确定性生成的别名规则(设计文档未展开、但在最终产物中落地),值得完整继承:
- 数字前紧邻的
제可省略:如성수2가제3동→성수2가3동; - 数字分隔点允许「마침표
./ 가운데점·/ 省略」三种形式:如종로1.2.3.4가동、종로1·2·3·4가동、종로1234가동均可解析到同一 ID; - 除此之外不生成任何别名:오타、유사 이름、생활권·통칭、部分名称(如
성수동)一律unknown_admin_dong,不做 fuzzy 猜测。
实现位于_alias_keys(用正则제(?=\d)只删数字前的제,并枚举./·/省略的组合)与_location_indexes(建立正名索引与别名索引,正名命中优先于别名)。测试文件 对每个含제、含.的 reference 行逐一验证所有可生成别名均能带--gu解析成功,且제기동这类「非数字前 제」不会被误删(기동应报unknown_admin_dong)。
解析主函数_resolve_admin_dong
核心实现位于 源码第 395-424 行,逻辑为:规范化输入 → 加载并校验 reference → 先查正名索引、未命中再查别名索引 → 候选为空抛unknown_admin_dong→ 若给了--gu则先校验自治区是否已知(否则unknown_gu),再用自治区过滤候选(过滤后为空抛unknown_admin_dong)→ 按(自治区, place_id)排序后若仍多于 1 个则抛ambiguous_admin_dong(details.candidates带完整候选),否则返回唯一记录。测试 test_resolve_admin_dong_requires_gu_for_duplicate_name 验证신사동不带--gu时精确返回 강남구/관악구两个候选。
六、错误契约:统一的 typed JSON envelope
所有新错误码统一走既有 JSON error envelope,exit code 为 2:
| 错误码 | 含义 |
|---|---|
invalid_location_input | 位置输入组合非法(如仅--gu) |
conflicting_location_input | --admin-dong与place_idfilter 冲突 |
unknown_admin_dong | reference 中不存在该行政洞 |
ambiguous_admin_dong | 同名/别名候选冲突,details.candidates含自治区与place_id |
unknown_gu | 未知自治区 |
location_mapping_invalid | reference 缺失、ID 重复或 schema 错误 |
实现上,SkillError携带code/message/details,run 函数 在捕获后向 stderr 输出{"error": {"code", "message", "details"}}并返回 exit code 2。这与该技能既有invalid_limit、query_window_unavailable、product_not_ready等错误处于同一契约体系。
七、执行流程:解析留在本地,proxy 边界不变
设计文档给出的执行流共 5 步,逐条保留:
- helper 照常校验 bundle 与 product metadata 契约;
- 在本地 reference中解析
--admin-dong; - 用解析结果校验既有公开 projection filter;
- 仅向 proxy 发送
place_id、时间范围、limit、cursor; - 响应行按既有 data 契约校验后原样输出。
由此得到两个关键推论(源码均有印证):
- proxy 与 ASK Seoul 服务不承担行政洞字符串解析职责:上游请求中只有
place_id,绝无admin_dong/gu字符串。测试 test_query_maps_admin_dong_to_place_id_before_proxy_request 断言最终 query 精确等于{"place_id": ["seoul_admd_1171065000"], "limit": ["1"]},且不含admin_dong/gu键。 - 无需用户 API Key 的安全模型保持不变:helper 不发送
Authorization头,ASK Seoul 专用服务密钥只存在于 proxy 运行环境。测试 test_query_uses_narrow_proxy_paths_without_user_bearer_auth 断言三次请求的Authorization均为None。进一步地,test_disabled_proxy_never_echoes_legacy_user_credentials 验证即便环境变量里存在旧式用户密钥,输出中也不会出现它。
八、文档与 Agent 行为约定
设计文档为 skill helper 的使用方式定下了行为准则,最终在 instruction.md 中落地:
- 默认示例使用人类可读名称,如
--admin-dong 잠실본동; - 从自然语言问题中提取行政洞名后,原样传给
--admin-dong; - 遇到
신사동这类歧义时,先向用户询问一次自治区,收到答复后用--gu重新调用; - 在解释响应时,优先使用用户输入的行政洞名与产品的预报时刻、风险依据,而非内部
place_id。
日常快速路径是query --fast(见 fast path 设计):该路径跳过 bundle/product metadata 往返,只调用 data 路由一次,同时保留本地行政洞映射、日期与 limit 校验。典型命令如下(来自 instruction.md):
npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query --fast \ --product-id weather_place_risk_window \ --admin-dong 잠실본동 \ --from 2026-08-12 \ --to 2026-08-12 \ --limit 100同名歧义场景则追加--gu:
npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query --fast \ --product-id weather_place_risk_window \ --admin-dong 신사동 \ --gu 강남구 \ --limit 100需要--filter或检查发布契约时才退回 full-contract query(先preflight→catalog→describe)。日期输入约定:--from YYYY-MM-DD扩展为当日00:00:00,--to扩展为23:59:59;若 ASK Seoul serving window 未覆盖自午夜起全天导致422 query_window_unavailable,helper 会按available_from_at/available_to_at与请求区间求交仅重试一次,无交集则返回该错误的 available window 信息并中止(实现见 源码_retry_query_after_unavailable_window与测试 test_query_clips_calendar_day_to_available_window)。
九、验证计划:从单元到全量 CI
设计文档列出 8 项验证,最终在 tests/test_seoul_weather_risk.py 中全部落地:
잠실본동正确转换为seoul_admd_1171065000并传入 proxy data query(test_query_maps_admin_dong_to_place_id_before_proxy_request);- 首尾/连续空白与 Unicode NFC 规范化(
test_resolve_admin_dong_normalizes_unicode_nfc、test_resolve_admin_dong_normalizes_internal_whitespace:NFD 输入与" 잠실 본동 "均解析到同一 ID); 신사동单独输入以含候选自治区的ambiguous_admin_dong失败(test_resolve_admin_dong_requires_gu_for_duplicate_name);신사동 + 강남구收敛为单一 ID(test_resolve_admin_dong_uses_gu_to_disambiguate);- 未登记洞、错误自治区、冲突位置输入均为 typed error(
test_resolve_admin_dong_rejects_unknown_or_broad_dong_and_gu、test_query_rejects_*系列); - 既有
place_idfilter 调用行为不变(回归:test_query_uses_narrow_proxy_paths_without_user_bearer_auth); - reference 的 427 行、唯一 ID、版本与必填字段不变量(
test_admin_dong_reference_has_expected_version_and_unique_place_ids,以及损坏 reference 的location_mapping_invalid用例test_load_location_mapping_rejects_invalid_reference); - 源码与 CLI bundled copy 完全同步,且整体
npm run ci通过(由实施计划 Task 5 规定,通过npm run generate:skill-stubs -- --check、npm run sync:cli-skills -- --check、git diff --check校验后执行全量 CI)。
此外测试还覆盖了别名生成的不变量:test_resolve_admin_dong_resolves_every_generated_map_alias_with_gu遍历 reference 中每一行生成的每一个别名并断言可解析回原记录,test_resolve_admin_dong_does_not_omit_non_numeric_je则防止对非数字前的제误删。
十、完成条件与设计价值
设计文档定义的完成条件为:
- 用户仅凭行政洞名称即可查询非歧义地点的气象风险数据;
- 同名地不因缺少自治区而被任意选取;
- proxy 不新增任何 query field 或用户凭证;
- 生成桩、CLI bundle、单元测试与全量 CI 全部通过。
从仓库现状看,该设计已完整落地:reference 快照、helper 解析实现、typed 错误契约、单元测试、instruction.md、skill.json 与生成的 SKILL.md 均已就位,且packages/k-skill-cli/skills/seoul-weather-risk同步资产由scripts/generate-skill-stubs.js与scripts/sync-cli-skills.js负责生成与校验。
这一设计的核心价值在于在零数据契约改动、零安全模型放宽的前提下,把「人类可读的行政洞名称」与「机器可查的 place_id」之间的鸿沟,用一份 427 行的冻结 JSON 和一段纯标准库 Python 确定性解析逻辑安全地弥合起来——既照顾了普通用户的自然语言输入体验,也保住了高级用户的place_id兼容路径,同时把「查错地点」的风险降到最低。
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考