k-skill seoul-weather-risk 深度解析:基于 ASK 서울 的行政洞级气象风险时段只读查询实践
2026/9/18 9:46:49 网站建设 项目流程

k-skill seoul-weather-risk 深度解析:基于 ASK 서울 的行政洞级气象风险时段只读查询实践

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

本文以开源仓库k-skill中的seoul-weather-risk技能为主线,讲解如何将首尔自然语言行政洞名(행정동)确定性解析为place_id,并通过 hostedk-skill-proxy只读查询 ASK 서울 的weather_place_risk_window单产品,获取按place_idforecast_at为粒度的暴热、寒潮、暴雨、大雪、强风风险候选时段。读完本文,你将掌握该技能的安装方式、fast path 与 full-contract 两条查询路径、preflight/catalog/describe合同诊断流程、行政洞映射与别名规则、时间窗交集重试机制,以及全部失败模式与安全边界,并能对照仓库源码与测试用例理解其底层实现。

技能定位与数据产品

seoul-weather-risk是一个面向韩国用户的只读查询技能(见 skill.json,类别public-data、语言ko-KR、阶段live-client)。它只处理一个数据产品:

  • weather_place_risk_window:场所别气象风险预想时段。基于预报值,将暴热(폭염)、寒潮(한파)、暴雨(호우)、大雪(대설)、强风(강풍)候选通过阈值筛选后生成,属于预报基准参考信息,而非气象厅(기상청)官方特报
  • 数据粒度(grain):每个place_id与每个forecast_at一行。
  • 典型提问"잠실본동에서 오늘 방문·이동에 주의할 기상 위험 시간대와 근거를 알려줘."(查询蚕室本洞今日出行需注意的气象风险时段与依据)。

该技能是单产品契约:若 bundle 中混入其他产品、或缺少该产品,helper 会以响应合同错误(response_contract_invalid)中止,绝不静默降级。核心保证有三条(见 instruction.md):

  1. 默认 helper 只调用 hostedk-skill-proxy
  2. 不读取用户 API Key 或当前工作目录下的.env
  3. 失败或未就绪状态绝不用 fixture 或推测值代替。

整体架构与调用链

从源码 seoul_weather_risk.py 看,helper 是一个纯标准库实现的只读 HTTPS 客户端(urllib.request),调用链为:

CLI / Agent │ npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py ▼ seoul_weather_risk.py(本地:行政洞→place_id 解析、参数校验、响应契约校验) ▼ k-skill-proxy(默认 https://k-skill-proxy.nomadamas.org) ▼ ASK Seoul 上游(proxy 运营环境中保管专用服务密钥)

proxy 仅暴露三条只读 route(DEFAULT_PROXY_BASE_URL 与PROXY_ROUTE_ROOT定义于源码头部):

Route用途
GET /v1/ask-seoul/weather-risk/bundle查询 bundle 及其中产品集合
GET /v1/ask-seoul/weather-risk/product查询单产品 metadata(grain、公开列、blockers)
GET /v1/ask-seoul/weather-risk/data查询数据页(支持place_id等过滤、分页 cursor)

关于该路线的设计动机,可参考 fast path 设计文档:它旨在削减常规"今日风险"提问中重复的 bundle/product metadata 往返,同时不再恢复本地直连或用户 API Key 路径。整体能力说明见 功能指南。

环境要求与安装

前置条件

  • 网络连接;
  • Node.js(含npx);
  • 通用安装与安全/密钥策略请先阅读 install.md 与 security-and-secrets.md。

安装技能

npx --yes skills add NomaDamas/k-skill --skill seoul-weather-risk -g

环境变量

一般用户无需任何环境变量。唯一可选变量是KSKILL_PROXY_BASE_URL,仅在自建/自托管 proxy 时设置:

  • 留空时使用默认 hosted origin:https://k-skill-proxy.nomadamas.org
  • 该值必须是HTTPS origin。源码 _api_config 会严格校验:非 HTTPS 且非本地回环(127.0.0.1/localhost/::1)会被拒绝;带路径、query、fragment 或内嵌用户名密码也会被拒绝为invalid_proxy_base_url
  • 环境变量取值为off/false/0/disable/disabled/none时视为禁用 proxy,抛出proxy_disabled
  • 该值不得写入命令行参数、文档或日志。

用户侧不存在 API Key 流程:ASK 서울 专用服务密钥只放在 proxy 运营环境,并通过 Marketplace 的k-skill-proxy:seoul-weather-riskprincipal 授予skill:seoul-weather-risk:readscope(仅允许 bundle、product、data 读取,拒绝其他 Marketplace API)。任何模式下都不得将密钥打印、写日志或写入技能文件。测试 test_parser_has_no_credential_or_base_url_option 专门断言:解析器没有任何 key/url 相关选项。

行政洞名称到 place_id 的确定性解析

这是本技能最核心的工程点:用户不需要知道内部place_id,helper 在本地将自然语言行政洞名转换为标准place_id(格式seoul_admd_+ 10 位数字,见 _load_location_mapping),且只向 proxy 发送place_id,绝不上送行政洞/自治区字符串。测试 test_query_maps_admin_dong_to_place_id_before_proxy_request 验证了请求 query 中只有place_idlimit

映射 reference

  • 文件:admin-dong-place-map.json;
  • 版本:kma_admin_dong_grid_20260325(源码常量 LOCATION_MAPPING_VERSION);
  • 规模:427 个首尔行政洞place_id全局唯一;
  • 字段契约:每行恰含admin_dongguplace_id三个非空字符串,缺失/多余字段、重复place_id、重复(洞,区)组合都会抛location_mapping_invalid
  • 测试 test_admin_dong_reference_has_expected_version_and_unique_place_ids 断言版本号、427 行数、427 个唯一place_id,并验证신사동同时存在于 강남구 与 관악구。

匹配优先级:规范名 > 确定性别名 > 失败

解析函数 _resolve_admin_dong 的完整规则:

  1. 输入规范化:先做 Unicode NFC 归一化(兼容 NFD 输入,测试 test_resolve_admin_dong_normalizes_unicode_nfc),再去除首尾与内部多余空白(测试 test_resolve_admin_dong_normalizes_internal_whitespace)。
  2. 官方行政洞名精确匹配优先:匹配到的结果优先于任何别名解析(测试 test_location_indexes_retain_colliding_aliases_and_resolve_exact_first 验证别名与规范名冲突时取规范名)。
  3. 仅在无精确匹配时应用确定性别名_alias_keys),别名只允许两类变换:
    • 数字前的"제"可省略:如성수2가제3동성수2가3동(源码用正则제(?=\d),只删数字前的"제";제기동这类非数字"제"绝不省略,见测试 test_resolve_admin_dong_does_not_omit_non_numeric_je);
    • 数字分隔符三种写法等价.(句点)、·(间隔点)、省略,如종로1.2.3.4가동종로1·2·3·4가동종로1234가동三者等价(测试 test_resolve_admin_dong_accepts_numeric_punctuation_aliases)。
    • 超出以上规则的别名一律不生成。
  4. 候选冲突处理:若别名阶段命中多个候选,则用--gu收窄;不带--gu仍冲突时保留为ambiguous_admin_dong绝不任意选择
  5. 无法解析即失败:拼写错误、相似名、生活圈/俗称、部分名称(如성수동종로)一律不做 fuzzy match 或猜测,直接返回unknown_admin_dong

同音/同名人(동명이명)

首尔存在同名的行政洞,典型如신사동(강남구 与 관악구 各有一个)。处理方式是先向用户确认自治区,再带--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

测试 test_resolve_admin_dong_requires_gu_for_duplicate_name 确认不带--gu时会抛出ambiguous_admin_dong,且details.candidates中包含两个候选(含各自guplace_id),供用户选择。

自动化调用:直接使用 place_id

既有自动化脚本可继续用--filter place_id=seoul_admd_...直查,例如:

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query \ --product-id weather_place_risk_window \ --filter place_id=seoul_admd_1171065000 \ --from 2026-08-11 \ --to 2026-08-17 \ --limit 100

--admin-dong--filter place_id=...不得同时使用,否则返回conflicting_location_input(测试 test_query_rejects_admin_dong_with_place_id_filter)。同样,--gu不能脱离--admin-dong单独使用(返回invalid_location_input,测试 test_query_rejects_gu_without_admin_dong)。

查询工作流:fast path 与 full-contract

标准用户查询(fast path)

用户询问"今日风险时段"的默认路径是只执行一次query --fast

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

fast path 的设计要点(详见 fast path 设计文档 与源码 run):

  • 保留本地 bundled 行政洞映射、日期与 limit 校验;
  • 只调用一次 hosted data route/v1/ask-seoul/weather-risk/data),省略 bundle、product metadata 两次往返;
  • --fast不支持--filter(源码 L501-L502 会抛fast_query_filter_unsupported),只允许--admin-dong--gu、日期、--limit--cursor
  • 测试 test_fast_query_uses_only_data_route_without_metadata_round_trips 验证:fast 查询仅命中 data route 一次,且请求中不携带任何Authorization头,place_id已被解析为seoul_admd_1171065000(잠실본동)。

需要任意--filter、或需要检查发布契约时,去掉--fast走 full-contract 查询(先查 bundle,再查 product,最后查 data,见测试 test_query_uses_narrow_proxy_paths_without_user_bearer_auth):

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- query \ --product-id weather_place_risk_window \ --admin-dong 잠실본동 \ --from 2026-08-11 \ --to 2026-08-17 \ --limit 100

日期范围的语义

  • --from YYYY-MM-DD扩展为当日00:00:00--to YYYY-MM-DD扩展为当日23:59:59(源码 _time_bound);
  • 显式给出具体时刻(如2026-08-11 09:00:00)则原样保留不扩展(测试 test_query_keeps_explicit_datetime_bounds_unchanged);
  • 自然语言中的"今天/明天/本周"应在调用前由 Agent 转换为 KST 显式区间。

服务窗口交集重试(422 处理)

ASK 서울 的 serving window 并非从午夜开始(例如数据当日 17:00 才开放)。当请求区间不被接受、上游返回422 query_window_unavailable时,helper 会自动做一次交集重试:

  1. 从错误响应的detail中提取requested_from_at/requested_to_at/available_from_at/available_to_at/publication_id(字段清单见源码 QUERY_WINDOW_DETAIL_FIELDS);
  2. 通过 _intersect_query_window 计算请求区间与可用窗口的交集(支持 ISO 与YYYY-MM-DD HH:MM:SS两种时间戳排序键,见 _window_sort_key);
  3. 若交集存在则仅重试一次,用交集覆盖from/to(_retry_query_after_unavailable_window);若请求区间与可用窗口无交集,则直接以query_window_unavailable中止并携带available_from_at/available_to_at

测试覆盖完整:如请求2026-08-24全天、可用窗口2026-08-24 17:00:00起,则自动以17:00:00–23:59:59重查(test_query_clips_calendar_day_to_available_window);完全无交集时失败并保留可用窗口信息(test_query_window_without_overlap_fails_with_available_bounds)。注意:--cursor的分页请求不做窗口交集重试(测试 test_paged_query_does_not_retry_unavailable_window),错误详情中缺失可用窗口信息时也不重试(test_query_window_problem_without_available_bounds_does_not_retry)。绝不填充不存在时间段的推测数据。

合同诊断(仅在需要时)

fast path 返回product_not_ready或契约错误时,不要用 fixture/推测值替代,而是按下述顺序诊断(仅在这类场景执行,普通提问不执行):

1. preflight:仅检查环境配置,不发起网络请求

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- preflight

返回status: okmode: hosted_proxylive_network: falseproxy_base_url_configured: true(源码 run)。live_network: false仅表示未联网,不代表数据已就绪。测试 test_preflight_is_user_secret_free_and_offline 还验证 preflight 不会泄露任何用户密钥。

2. catalog:检查 bundle 就绪状态

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- catalog

重点查看:registration_ready(bundle 是否可用于当前查询)、products(是否恰为weather_place_risk_window单产品)、blockers(未解决的发版阻塞原因)、publication_id(当前发布版本标识)。bundle 校验函数 _validate_bundle 要求产品集合严格等于单产品集合,任何 drift 都会以response_contract_invalid失败关闭(测试 test_bundle_single_product_drift_fails_closed)。

3. describe:查看产品契约

npx -y @nomadamas/k-skill@0 exec seoul-weather-risk scripts/seoul_weather_risk.py -- describe --product-id weather_place_risk_window

返回 grain、主键、时间轴、公开列及证据 metadata。metadata.columns列出可过滤的公开列(测试 fixture 中包含place_idforecast_atrisk_labels)。registration_readyfalseblockers非空时,不得视为查询成功

分页与 cursor 纪律

  • data 响应中next_cursor只能原样复用于同一产品的下一页
  • publication 变更后 cursor 会以409 cursor_expired过期——此时应回到第一页重新查询,不要无限重试旧 cursor。

响应解读

数据响应是单页 JSON,字段含义如下:

字段含义
publication_id该页数据所属的发布版本,续页时须保持一致
row_count实际行数,必须等于rows长度(源码 _validate_data 强校验)
rows行列表,含place_idforecast_atrisk_labels等公开列
has_more/next_cursor是否还有下一页 / 下一页游标;两者必须一致
forecast_at风险候选的预报时刻,即本产品的时间轴
risk_labels暴热/寒潮/暴雨/大雪/强风等风险候选标签

向用户汇报时,"Done when" 要求(见 instruction.md)必须包含:实际响应的publication_id、时间轴forecast_atusage与行数;同时绝不把503未就绪产品及认证/权限/配额错误表述为成功。

失败模式全表

helper 把所有错误归一为结构化 JSON({"error": {"code", "message", "details"}},写入 stderr,进程退出码 2,见 run)。各 code 及含义:

错误码HTTP说明与应对
invalid_limit--limit超出1..500(默认100
invalid_location_input/conflicting_location_input行政洞·自治区·直接place_id输入组合错误(如--gu--admin-dong,或两者与place_id过滤器混用)
unknown_admin_dong/unknown_gureference 中不存在的行政洞/自治区;错别字、生活圈、部分名(如성수동)一律归为unknown_admin_dong,不猜测
ambiguous_admin_dong同名人或别名候选冲突,需--gu;从details.candidates中查看可选自治区
location_mapping_invalidbundled 行政洞 reference 的版本/模式/行数契约错误
proxy_disabled/invalid_proxy_base_urlproxy 环境配置错误(KSKILL_PROXY_BASE_URL被禁用或非合法 HTTPS origin)
unauthorized/api_key_missing401认证失败/缺少密钥
forbidden/api_key_forbidden403权限不足
unknown_product404未知产品
cursor_expired409publication 变更导致 cursor 失效,需从头重新分页
query_window_unavailable422请求区间与当前可用预报窗口无交集;查看details.available_from_at/available_to_at在可用区间内查询。若本可相交,则此错误已是 helper 自动交集重试一次后的结果
rate_limited429超限流;遵守响应中的Retry-After后重试
product_not_ready503产品未发布就绪;用catalogregistration_readyblockers
upstream_not_configured503proxy 运营环境未配置 ASK 서울 专用服务密钥或 origin
response_contract_invalid/malformed_response单产品契约或 API 响应契约漂移(如非 JSON、缺字段、row_countrows长度不符、has_morenext_cursor不一致)
network_error无法连接 proxy

HTTP 错误到 code 的映射见源码 STATUS_CODES,测试 test_http_problem_statuses_are_typed_and_preserve_safe_details 验证 401/403/404/409/429/503 均被类型化并安全保留request_idproduct_idRetry-After等详情。

安全与使用边界

该技能在设计上刻意收窄了攻击面,边界包括:

  • 不接受table name、SQL、join、sort、aggregate 等任意查询输入;
  • 不猜测未知产品或过滤器进行纠正;
  • 不做行政洞模糊匹配或从歧义候选中任意挑选;生活圈/俗称/部分名不当作行政洞;helper 在本地 reference 解析place_id,proxy 侧只收到place_id
  • 不跟随重定向:源码 _NoRedirect 阻断所有重定向,防止读请求被静默转发到未审查的 origin(测试 test_redirect_is_not_followed_through_proxy_client);
  • 代理仅暴露bundle、单产品及数据查询,不向上游传递非允许字段;
  • bundle 产品集合不等于单产品时以response_contract_invalid中止(_validate_bundle);
  • live 失败绝不用 fixture/synthetic 结果替代
  • 响应中必须明确:该产品是预报阈值参考信息,不替代气象厅官方特报(响应头Content-Type非 JSON 也会以malformed_response失败,见 _request_json)。

测试 test_local_direct_settings_are_ignored_and_hosted_proxy_remains_the_only_route 还证明:即便环境里存在KSKILL_LOCAL_DIRECTASK_SEOUL_SKILL_API_BASE_URLMARKETPLACE_API_KEY等遗留配置,helper 仍只走 hosted proxy 且不携带任何Authorization头。

使用前检查清单

  • 常规用户查询只执行一次query --fast
  • 仅当 fast path 失败或需显式检查发布契约/就绪状态时,才按preflight → catalog → describe顺序诊断;
  • 诊断时确认catalogregistration_ready=trueblockers为空,describe中确认公开列与forecast_at时间轴;
  • 行政洞名歧义时指定--gu
  • 汇报时包含publication_idrow_countforecast_atrisk_labels
  • 明确告知用户这是预报参考信息而非官方特报;
  • 不将错误响应改写为成功数据或推测值。

延伸阅读

  • 技能详细执行契约:seoul-weather-risk/instruction.md
  • 功能使用指南:docs/features/seoul-weather-risk.md
  • Helper 源码:seoul-weather-risk/scripts/seoul_weather_risk.py
  • 单测用例:seoul-weather-risk/tests/test_seoul_weather_risk.py
  • 行政洞映射 reference:seoul-weather-risk/references/admin-dong-place-map.json
  • fast path 设计文档:docs/superpowers/specs/2026-08-12-seoul-weather-risk-fast-path-design.md
  • 通用安装指南:docs/install.md;安全与密钥策略:docs/security-and-secrets.md

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询