feat: 工资查询助手首版 v1.0.105

基于 skill-template 脚手架开发,包含:
- payroll_config(费率配置,可编辑)+ payslip_snapshots(解析历史,只读)2 张业务表
- 全国默认 + 北上广 4 套费率(社保/公积金/个税起征点/7 级税率表)
- 5 个业务 Action:calculate-payslip-simple / calculate-payslip-full / explain-term / view-config / edit-config
- 个税累计预扣法计算,支持多月历史累加
- 20 个薪资名词库(社保/公积金/个税/全勤/加班费等)
- 完整市场文档(README/TUTORIAL/DEMO/CHANGELOG)
- 296 测试全部通过
This commit is contained in:
skill-dev
2026-09-07 17:02:05 +08:00
commit 3db196e49d
205 changed files with 29927 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
"""名词解释服务。
从 assets/glossary.json 读取词条,按 key/display/category 查询。
不做 LLM 生成,纯静态数据查询。
"""
from __future__ import annotations
import json
from typing import Any, Optional
from util.constants import SKILL_SLUG
from util.logging_config import get_skill_logger
from util.runtime_paths import get_skill_root
log = get_skill_logger()
def _load_glossary() -> list[dict[str, Any]]:
"""从 assets/glossary.json 加载全部词条。"""
glossary_path = get_skill_root() / "assets" / "glossary.json"
try:
with open(glossary_path, "r", encoding="utf-8") as f:
data = json.load(f)
terms = data.get("terms", []) if isinstance(data, dict) else []
return terms if isinstance(terms, list) else []
except (FileNotFoundError, json.JSONDecodeError, OSError):
return []
def explain_term(term: str) -> dict[str, Any]:
"""查询单个名词解释。
支持按 key(精确)或 display(包含)匹配。
未命中返回结构化空(ok=True, found=False)。
"""
log.info("glossary_explain term=%s", term)
terms = _load_glossary()
term_lower = (term or "").strip().lower()
hit: Optional[dict[str, Any]] = None
for t in terms:
if str(t.get("key", "")).lower() == term_lower:
hit = t
break
if term_lower in str(t.get("display", "")).lower():
hit = t
break
if hit is None:
return {
"ok": True,
"skill": SKILL_SLUG,
"found": False,
"term": term,
"explanation": None,
"suggestions": [str(t.get("display", "")) for t in terms[:5]],
}
return {
"ok": True,
"skill": SKILL_SLUG,
"found": True,
"term": term,
"key": hit.get("key"),
"display": hit.get("display"),
"category": hit.get("category"),
"explanation": hit.get("explanation"),
"example": hit.get("example"),
}
def list_terms(category: str | None = None) -> dict[str, Any]:
"""列出全部或按分类的词条。"""
terms = _load_glossary()
if category:
filtered = [t for t in terms if str(t.get("category", "")).lower() == category.lower()]
else:
filtered = terms
return {
"ok": True,
"skill": SKILL_SLUG,
"count": len(filtered),
"categories": sorted({str(t.get("category", "")) for t in terms}),
"terms": [
{
"key": t.get("key"),
"display": t.get("display"),
"category": t.get("category"),
"explanation": t.get("explanation"),
"example": t.get("example"),
}
for t in filtered
],
}