Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:
- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
- docx family: kept docx (official, full toolchain) + docx-cn
(GB/T 9704 Chinese official-document constants),
dropped docx_writer (no scripts, name collided with docx)
- humanizer family: kept humanizer-zh (6 zh reference docs),
dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
live credentials
Result: 25 skills, 572 files, ~7.5 MB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
389 lines
12 KiB
Python
389 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
养老金产品 SQLite 查询脚本
|
|
|
|
提供命令行和函数接口查询养老金产品数据库。
|
|
|
|
用法:
|
|
# 按名称模糊搜索(全文搜索)
|
|
python query_pension.py search "平安"
|
|
python query_pension.py search "稳健增值"
|
|
|
|
# 按代码精确查找
|
|
python query_pension.py code PAA001.YLJ
|
|
|
|
# 按管理人筛选
|
|
python query_pension.py manager PA
|
|
python query_pension.py manager 平安养老
|
|
|
|
# 按产品类型筛选
|
|
python query_pension.py type A # 权益型
|
|
python query_pension.py type C # 债券型
|
|
python query_pension.py type Cb # 债券型-信用债子类
|
|
|
|
# 统计概览
|
|
python query_pension.py stats
|
|
|
|
# 导出全部数据为 CSV
|
|
python query_pension.py export pension_products.csv
|
|
|
|
# 组合筛选(管理人 + 类型)
|
|
python query_pension.py filter --manager PA --type A
|
|
|
|
# 自定义 SQL 查询
|
|
python query_pension.py sql "SELECT code, name FROM pension_products WHERE name LIKE '%增值%'"
|
|
"""
|
|
|
|
import argparse
|
|
import csv
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def get_db_path(custom_path: str = None) -> str:
|
|
"""获取数据库路径。"""
|
|
if custom_path:
|
|
return custom_path
|
|
default = Path(__file__).parent.parent / "data" / "pension_products.db"
|
|
if default.exists():
|
|
return str(default)
|
|
raise FileNotFoundError(
|
|
f"数据库不存在: {default}\n请先运行 build_pension_db.py 构建数据库"
|
|
)
|
|
|
|
|
|
def get_conn(db_path: str) -> sqlite3.Connection:
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
|
|
# ---------- 查询函数(可被外部 import 使用) ----------
|
|
|
|
def search(db_path: str, keyword: str, limit: int = 50) -> list[dict]:
|
|
"""全文搜索:按名称、代码、管理人等关键词搜索。"""
|
|
conn = get_conn(db_path)
|
|
# 先尝试 FTS5 全文搜索
|
|
try:
|
|
rows = conn.execute("""
|
|
SELECT p.* FROM pension_products p
|
|
JOIN pension_fts f ON p.rowid = f.rowid
|
|
WHERE pension_fts MATCH ?
|
|
ORDER BY rank
|
|
LIMIT ?
|
|
""", (keyword, limit)).fetchall()
|
|
if rows:
|
|
return [dict(r) for r in rows]
|
|
except sqlite3.OperationalError:
|
|
pass
|
|
|
|
# 回退到 LIKE 搜索
|
|
like_kw = f"%{keyword}%"
|
|
rows = conn.execute("""
|
|
SELECT * FROM pension_products
|
|
WHERE name LIKE ? OR official_name LIKE ? OR code LIKE ?
|
|
OR manager_name LIKE ? OR product_type_name LIKE ?
|
|
ORDER BY code
|
|
LIMIT ?
|
|
""", (like_kw, like_kw, like_kw, like_kw, like_kw, limit)).fetchall()
|
|
conn.close()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
def get_by_code(db_path: str, code: str) -> dict | None:
|
|
"""按代码精确查找。"""
|
|
if not code.endswith(".YLJ"):
|
|
code += ".YLJ"
|
|
conn = get_conn(db_path)
|
|
row = conn.execute("SELECT * FROM pension_products WHERE code = ?", (code,)).fetchone()
|
|
conn.close()
|
|
return dict(row) if row else None
|
|
|
|
|
|
def get_by_manager(db_path: str, manager: str, limit: int = 200) -> list[dict]:
|
|
"""按管理人查询。支持前缀代码或中文名。"""
|
|
conn = get_conn(db_path)
|
|
rows = conn.execute("""
|
|
SELECT * FROM pension_products
|
|
WHERE manager_prefix = ? OR manager_name LIKE ?
|
|
ORDER BY product_type, code
|
|
LIMIT ?
|
|
""", (manager, f"%{manager}%", limit)).fetchall()
|
|
conn.close()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
def get_by_type(db_path: str, ptype: str, limit: int = 500) -> list[dict]:
|
|
"""按产品类型查询。ptype: A/B/C/D 或 Ca/Cb/Cf 等子类。"""
|
|
conn = get_conn(db_path)
|
|
if len(ptype) == 1:
|
|
rows = conn.execute("""
|
|
SELECT * FROM pension_products
|
|
WHERE product_type = ?
|
|
ORDER BY manager_prefix, code
|
|
LIMIT ?
|
|
""", (ptype.upper(), limit)).fetchall()
|
|
else:
|
|
rows = conn.execute("""
|
|
SELECT * FROM pension_products
|
|
WHERE bond_subtype = ?
|
|
ORDER BY manager_prefix, code
|
|
LIMIT ?
|
|
""", (ptype, limit)).fetchall()
|
|
conn.close()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
def filter_products(db_path: str, manager: str = None, ptype: str = None,
|
|
keyword: str = None, limit: int = 500) -> list[dict]:
|
|
"""组合筛选。"""
|
|
conn = get_conn(db_path)
|
|
conditions = []
|
|
params = []
|
|
|
|
if manager:
|
|
conditions.append("(manager_prefix = ? OR manager_name LIKE ?)")
|
|
params.extend([manager, f"%{manager}%"])
|
|
if ptype:
|
|
if len(ptype) == 1:
|
|
conditions.append("product_type = ?")
|
|
params.append(ptype.upper())
|
|
else:
|
|
conditions.append("bond_subtype = ?")
|
|
params.append(ptype)
|
|
if keyword:
|
|
conditions.append("(name LIKE ? OR official_name LIKE ?)")
|
|
params.extend([f"%{keyword}%", f"%{keyword}%"])
|
|
|
|
where = " AND ".join(conditions) if conditions else "1=1"
|
|
params.append(limit)
|
|
|
|
rows = conn.execute(f"""
|
|
SELECT * FROM pension_products
|
|
WHERE {where}
|
|
ORDER BY manager_prefix, product_type, code
|
|
LIMIT ?
|
|
""", params).fetchall()
|
|
conn.close()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
def get_stats(db_path: str) -> dict:
|
|
"""获取数据库统计概览。"""
|
|
conn = get_conn(db_path)
|
|
total = conn.execute("SELECT COUNT(*) FROM pension_products").fetchone()[0]
|
|
|
|
type_stats = conn.execute("""
|
|
SELECT product_type, product_type_name, COUNT(*) as cnt
|
|
FROM pension_products
|
|
GROUP BY product_type
|
|
ORDER BY cnt DESC
|
|
""").fetchall()
|
|
|
|
manager_stats = conn.execute("""
|
|
SELECT manager_prefix, manager_name, COUNT(*) as cnt
|
|
FROM pension_products
|
|
GROUP BY manager_prefix
|
|
ORDER BY cnt DESC
|
|
""").fetchall()
|
|
|
|
bond_stats = conn.execute("""
|
|
SELECT bond_subtype, bond_subtype_name, COUNT(*) as cnt
|
|
FROM pension_products
|
|
WHERE bond_subtype IS NOT NULL
|
|
GROUP BY bond_subtype
|
|
ORDER BY cnt DESC
|
|
""").fetchall()
|
|
|
|
last_updated = conn.execute(
|
|
"SELECT MAX(last_updated) FROM pension_products"
|
|
).fetchone()[0]
|
|
|
|
conn.close()
|
|
return {
|
|
"total": total,
|
|
"last_updated": last_updated,
|
|
"by_type": [dict(r) for r in type_stats],
|
|
"by_manager": [dict(r) for r in manager_stats],
|
|
"by_bond_subtype": [dict(r) for r in bond_stats],
|
|
}
|
|
|
|
|
|
def export_csv(db_path: str, output_path: str):
|
|
"""导出全部数据为 CSV。"""
|
|
conn = get_conn(db_path)
|
|
rows = conn.execute("SELECT * FROM pension_products ORDER BY code").fetchall()
|
|
conn.close()
|
|
|
|
if not rows:
|
|
print("数据库为空")
|
|
return
|
|
|
|
with open(output_path, "w", newline="", encoding="utf-8-sig") as f:
|
|
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
|
|
writer.writeheader()
|
|
for row in rows:
|
|
writer.writerow(dict(row))
|
|
print(f"已导出 {len(rows)} 条记录到 {output_path}")
|
|
|
|
|
|
def run_sql(db_path: str, query: str, limit: int = 100) -> list[dict]:
|
|
"""执行自定义 SQL 查询。"""
|
|
conn = get_conn(db_path)
|
|
rows = conn.execute(query).fetchall()[:limit]
|
|
conn.close()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
# ---------- 格式化输出 ----------
|
|
|
|
def print_products(products: list[dict], verbose: bool = False):
|
|
"""格式化打印产品列表。"""
|
|
if not products:
|
|
print("未找到匹配的产品")
|
|
return
|
|
|
|
print(f"\n共 {len(products)} 个产品:")
|
|
print(f"{'代码':<18} {'名称':<30} {'管理人':<14} {'类型':<10} {'成立日':<12}")
|
|
print("-" * 84)
|
|
for p in products:
|
|
name = (p.get("name") or p.get("official_name") or "")[:28]
|
|
mgr = (p.get("manager_name") or p.get("manager_prefix") or "")[:12]
|
|
ptype = p.get("product_type_name", "")
|
|
if p.get("bond_subtype"):
|
|
ptype = f"{ptype}({p['bond_subtype']})"
|
|
est = p.get("establishment_date") or ""
|
|
print(f"{p['code']:<18} {name:<30} {mgr:<14} {ptype:<10} {est:<12}")
|
|
|
|
if verbose and products:
|
|
print(f"\n详细信息 (第一条):")
|
|
for k, v in products[0].items():
|
|
if v is not None:
|
|
print(f" {k}: {v}")
|
|
|
|
|
|
def print_stats(stats: dict):
|
|
"""格式化打印统计信息。"""
|
|
print(f"\n养老金产品数据库概览")
|
|
print(f"{'=' * 50}")
|
|
print(f"总产品数: {stats['total']}")
|
|
print(f"最后更新: {stats['last_updated']}")
|
|
|
|
print(f"\n按产品类型:")
|
|
for t in stats["by_type"]:
|
|
print(f" {t['product_type_name']} ({t['product_type']}): {t['cnt']} 个")
|
|
|
|
print(f"\n按管理人:")
|
|
for m in stats["by_manager"]:
|
|
print(f" {m['manager_name']} ({m['manager_prefix']}): {m['cnt']} 个")
|
|
|
|
if stats["by_bond_subtype"]:
|
|
print(f"\n债券型子类分布:")
|
|
for b in stats["by_bond_subtype"]:
|
|
name = b["bond_subtype_name"] or b["bond_subtype"]
|
|
print(f" {b['bond_subtype']} ({name}): {b['cnt']} 个")
|
|
|
|
|
|
# ---------- CLI ----------
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="养老金产品数据库查询工具")
|
|
parser.add_argument("--db", default=None, help="数据库路径")
|
|
parser.add_argument("-v", "--verbose", action="store_true", help="详细输出")
|
|
|
|
sub = parser.add_subparsers(dest="command", help="查询命令")
|
|
|
|
# search
|
|
p_search = sub.add_parser("search", help="全文搜索")
|
|
p_search.add_argument("keyword", help="搜索关键词")
|
|
p_search.add_argument("-n", "--limit", type=int, default=50)
|
|
|
|
# code
|
|
p_code = sub.add_parser("code", help="按代码查找")
|
|
p_code.add_argument("code", help="产品代码 (如 PAA001.YLJ)")
|
|
|
|
# manager
|
|
p_mgr = sub.add_parser("manager", help="按管理人筛选")
|
|
p_mgr.add_argument("manager", help="管理人前缀或名称")
|
|
|
|
# type
|
|
p_type = sub.add_parser("type", help="按产品类型筛选")
|
|
p_type.add_argument("ptype", help="类型: A/B/C/D 或子类 Ca/Cb/Cf...")
|
|
|
|
# filter
|
|
p_filter = sub.add_parser("filter", help="组合筛选")
|
|
p_filter.add_argument("--manager", "-m", help="管理人")
|
|
p_filter.add_argument("--type", "-t", dest="ptype", help="类型")
|
|
p_filter.add_argument("--keyword", "-k", help="名称关键词")
|
|
|
|
# stats
|
|
sub.add_parser("stats", help="统计概览")
|
|
|
|
# export
|
|
p_export = sub.add_parser("export", help="导出 CSV")
|
|
p_export.add_argument("output", help="输出文件路径")
|
|
|
|
# sql
|
|
p_sql = sub.add_parser("sql", help="自定义 SQL 查询")
|
|
p_sql.add_argument("query", help="SQL 语句")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
try:
|
|
db_path = get_db_path(args.db)
|
|
except FileNotFoundError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
|
|
if args.command == "search":
|
|
results = search(db_path, args.keyword, args.limit)
|
|
print_products(results, args.verbose)
|
|
|
|
elif args.command == "code":
|
|
result = get_by_code(db_path, args.code)
|
|
if result:
|
|
print_products([result], verbose=True)
|
|
else:
|
|
print(f"未找到产品: {args.code}")
|
|
|
|
elif args.command == "manager":
|
|
results = get_by_manager(db_path, args.manager)
|
|
print_products(results, args.verbose)
|
|
|
|
elif args.command == "type":
|
|
results = get_by_type(db_path, args.ptype)
|
|
print_products(results, args.verbose)
|
|
|
|
elif args.command == "filter":
|
|
results = filter_products(db_path, args.manager, args.ptype, args.keyword)
|
|
print_products(results, args.verbose)
|
|
|
|
elif args.command == "stats":
|
|
stats = get_stats(db_path)
|
|
print_stats(stats)
|
|
|
|
elif args.command == "export":
|
|
export_csv(db_path, args.output)
|
|
|
|
elif args.command == "sql":
|
|
results = run_sql(db_path, args.query)
|
|
if results:
|
|
# 简单表格输出
|
|
keys = list(results[0].keys())
|
|
print("\t".join(keys))
|
|
for r in results:
|
|
print("\t".join(str(r.get(k, "")) for k in keys))
|
|
print(f"\n共 {len(results)} 条结果")
|
|
else:
|
|
print("无结果")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|