Skill: kiszly/fred-navigator (version 1.0.0)

--- name: fred-navigator description: Navigate FRED categories and series using fredapi, supporting natural-language queries with intent recognition and double validation. metadata: short-description: FRED navigation and lookup --- # FRED Navigator ## Purpose Provide a reliable workflow to navigate FRED categories and series, with support for: 1. Direct `category_id` 2. Direct `series_id` 3. Natural-language `query` → intent recognition → double validation ## Inputs - `category_id`: FRED category id - `series_id`: FRED series id - `query`: natural language request - `limit`: number of candidates to return (default 5) - `api_key`: read from environment `FRED_API_KEY` only ## Required Resources - `references/fred_categories_tree.json` - `references/fred_categories_flat.json` - Optional: `references/category_paths.json` (precomputed) - Optional: `references/synonyms.json` - Helper script: `scripts/fred_query.py` - Path builder: `scripts/build_paths.py` ### Optional Resource Structure Notes - `references/category_paths.json` format: - `{ "category_id": { "id": <int>, "name": "<str>", "path": "<str>" }, ... }` - `references/synonyms.json` format: - `{ "concept": ["alias1", "alias2", ...], ... }` ## Workflow ### 1. Category Exploration 1. Load `fred_categories_tree.json` for hierarchical browsing. 2. If user provides `category_id`, validate it exists. 3. If user provides `category_name`, fuzzy match against `flat` names and return candidates. ### 2. Series Discovery 1. Use `search_by_category(category_id)` to list available series. 2. Prefer `scripts/fred_query.py category <id>` for consistent output. 2. Return key columns: - `id`, `title`, `frequency`, `units`, `seasonal_adjustment`, `last_updated`. ### 3. Series Retrieval 1. Use `get_series(series_id)` for time series. 2. Use `get_series_info(series_id)` for metadata. 3. Prefer `scripts/fred_query.py series <id>` and `scripts/fred_query.py series-info <id>`. 3. Provide: - data head/tail - missing counts - latest value and date ### 4. Natural Language Query #### 4.1 Intent Identification (Top-K) 1. Use the IDE agent (Codex) to interpret the natural-language intent. 2. Select the single best-matching category. 3. If confidence is low, ask the user to confirm the category before proceeding. 4. Use `references/category_paths.json` and `references/synonyms.json` as supporting context if available. #### 4.2 Double Validation **Structural validation** - Candidate must exist in `fred_categories_tree.json`. - Pass if at least one: - `children` non-empty - `search_by_category(id)` returns >= 1 series - Prefer `scripts/fred_query.py check-category <id>` for a quick check **Semantic validation (agent)** - Compare `query` with candidate `name/path`. - Return `pass/fail` or numeric relevance score. #### 4.3 Decision - If structural + semantic validation both pass → accept category. - Otherwise: - return Top-5 candidates - ask user to choose one explicitly ## Failure Handling - Always provide Top-5 candidates when uncertain. - Never proceed to series retrieval if category validation fails. ## Notes - Do not hardcode API keys. - Keep heavy reference data in `references/`, not in this file. - When running Python functions for querying, execute them inside the sandbox environment. ## Maintenance - Update workflow or constraints: edit `SKILL.md`. - Update category data: replace files in `references/`. - Improve natural-language matching: add or edit `references/synonyms.json` (key → list of related terms). - Regenerate precomputed paths (optional): run `scripts/build_paths.py`. - Add helper scripts (optional): place in `scripts/` and document usage here.

path: requirements.txt
content: # Dependencies for FRED Scout Skill
fredapi
pandas


path: README.md
content: # FRED Navigator

## 英文版本

Navigate FRED (Federal Reserve Economic Data) categories and series using fredapi, supporting natural-language queries with intent recognition and double validation.

### Purpose

Provide a reliable workflow to navigate FRED categories and series, with support for:
1. Direct `category_id` lookup
2. Direct `series_id` retrieval
3. Natural-language `query` → intent recognition → double validation

### Project Structure

```
fred-navigator/
├── references/              # Reference data files
│   ├── category_paths.json  # Precomputed category paths (optional)
│   ├── fred_categories_flat.json  # Flat category list
│   ├── fred_categories_tree.json  # Hierarchical category tree
│   └── synonyms.json        # Concept synonyms (optional)
├── scripts/                 # Helper scripts
│   ├── build_paths.py       # Build category paths index
│   └── fred_query.py        # FRED API query helper
├── SKILL.md                 # Skill configuration and workflow
├── README.md                # This file
├── requirements.txt         # Python dependencies
└── .gitignore               # Git ignore rules
```

### Installation

1. Clone this repository
2. Install dependencies:
   ```bash
   pip install -r requirements.txt
   ```
3. Set up FRED API key as environment variable:
   ```bash
   export FRED_API_KEY=your_api_key_here
   ```

### Dependencies

- `fredapi`: Python wrapper for FRED API
- `pandas`: Data manipulation library

### Usage

#### Using Helper Scripts

##### List series under a category
```bash
python scripts/fred_query.py category <category_id> [--limit 20] [--output table|json]
```

##### Fetch series data
```bash
python scripts/fred_query.py series <series_id> [--tail 20] [--output table|json]
```

##### Fetch series metadata
```bash
python scripts/fred_query.py series-info <series_id> [--output table|json]
```

##### Show full path for a category
```bash
python scripts/fred_query.py category-path <category_id>
```

##### Check category existence and content
```bash
python scripts/fred_query.py check-category <category_id>
```

##### Build category paths index
```bash
python scripts/build_paths.py
```

#### Skill Inputs

- `category_id`: FRED category id
- `series_id`: FRED series id
- `query`: natural language request
- `limit`: number of candidates to return (default 5)
- `api_key`: read from environment `FRED_API_KEY` only

### Workflow

#### 1. Category Exploration
1. Load `fred_categories_tree.json` for hierarchical browsing
2. Validate `category_id` if provided
3. Fuzzy match `category_name` against flat names if provided

#### 2. Series Discovery
1. Use `search_by_category(category_id)` to list available series
2. Return key columns: `id`, `title`, `frequency`, `units`, `seasonal_adjustment`, `last_updated`

#### 3. Series Retrieval
1. Use `get_series(series_id)` for time series data
2. Use `get_series_info(series_id)` for metadata
3. Provide data head/tail, missing counts, and latest value/date

#### 4. Natural Language Query
1. **Intent Identification**: Interpret natural-language intent and select best-matching category
2. **Double Validation**: 
   - Structural validation: Ensure category exists and has content
   - Semantic validation: Compare query with category name/path
3. **Decision**: Accept category if both validations pass, otherwise return Top-5 candidates

### Failure Handling

- Always provide Top-5 candidates when uncertain
- Never proceed to series retrieval if category validation fails

### Maintenance

- **Update workflow or constraints**: Edit `SKILL.md`
- **Update category data**: Replace files in `references/`
- **Improve natural-language matching**: Add or edit `references/synonyms.json`
- **Regenerate precomputed paths**: Run `scripts/build_paths.py`
- **Add helper scripts**: Place in `scripts/` and document usage

### Notes

- Do not hardcode API keys
- Keep heavy reference data in `references/`, not in `SKILL.md`
- When running Python functions for querying, execute them inside the sandbox environment

## 中文版本

使用 fredapi 导航 FRED（联邦储备经济数据）类别和系列，支持自然语言查询，具有意图识别和双重验证功能。

### 目的

提供可靠的工作流程来导航 FRED 类别和系列，支持：
1. 直接 `category_id` 查找
2. 直接 `series_id` 检索
3. 自然语言 `query` → 意图识别 → 双重验证

### 项目结构

```
fred-navigator/
├── references/              # 参考数据文件
│   ├── category_paths.json  # 预计算的类别路径（可选）
│   ├── fred_categories_flat.json  # 扁平类别列表
│   ├── fred_categories_tree.json  # 层次化类别树
│   └── synonyms.json        # 概念同义词（可选）
├── scripts/                 # 辅助脚本
│   ├── build_paths.py       # 构建类别路径索引
│   └── fred_query.py        # FRED API 查询助手
├── SKILL.md                 # 技能配置和工作流程
├── README.md                # 此文件
├── requirements.txt         # Python 依赖
└── .gitignore               # Git 忽略规则
```

### 安装

1. 克隆此仓库
2. 安装依赖：
   ```bash
   pip install -r requirements.txt
   ```
3. 设置 FRED API 密钥作为环境变量：
   ```bash
   export FRED_API_KEY=your_api_key_here
   ```

### 依赖

- `fredapi`: FRED API 的 Python 包装器
- `pandas`: 数据处理库

### 使用

#### 使用辅助脚本

##### 列出类别下的系列
```bash
python scripts/fred_query.py category <category_id> [--limit 20] [--output table|json]
```

##### 获取系列数据
```bash
python scripts/fred_query.py series <series_id> [--tail 20] [--output table|json]
```

##### 获取系列元数据
```bash
python scripts/fred_query.py series-info <series_id> [--output table|json]
```

##### 显示类别的完整路径
```bash
python scripts/fred_query.py category-path <category_id>
```

##### 检查类别是否存在及其内容
```bash
python scripts/fred_query.py check-category <category_id>
```

##### 构建类别路径索引
```bash
python scripts/build_paths.py
```

#### 技能输入

- `category_id`: FRED 类别 ID
- `series_id`: FRED 系列 ID
- `query`: 自然语言请求
- `limit`: 返回的候选数量（默认 5）
- `api_key`: 仅从环境变量 `FRED_API_KEY` 读取

### 工作流程

#### 1. 类别探索
1. 加载 `fred_categories_tree.json` 进行层次浏览
2. 如果提供了 `category_id`，验证其是否存在
3. 如果提供了 `category_name`，对扁平名称进行模糊匹配

#### 2. 系列发现
1. 使用 `search_by_category(category_id)` 列出可用系列
2. 返回关键列：`id`、`title`、`frequency`、`units`、`seasonal_adjustment`、`last_updated`

#### 3. 系列检索
1. 使用 `get_series(series_id)` 获取时间序列数据
2. 使用 `get_series_info(series_id)` 获取元数据
3. 提供数据的头部/尾部、缺失计数以及最新值和日期

#### 4. 自然语言查询
1. **意图识别**：解释自然语言意图并选择最佳匹配类别
2. **双重验证**：
   - 结构验证：确保类别存在且有内容
   - 语义验证：将查询与类别名称/路径进行比较
3. **决策**：如果两个验证都通过，则接受类别；否则返回前 5 个候选

### 故障处理

- 不确定时始终提供前 5 个候选
- 如果类别验证失败，永远不要进行系列检索

### 维护

- **更新工作流程或约束**：编辑 `SKILL.md`
- **更新类别数据**：替换 `references/` 中的文件
- **改进自然语言匹配**：添加或编辑 `references/synonyms.json`
- **重新生成预计算路径**：运行 `scripts/build_paths.py`
- **添加辅助脚本**：放在 `scripts/` 中并记录用法

### 注意事项

- 不要硬编码 API 密钥
- 将大量参考数据保存在 `references/` 中，而不是 `SKILL.md` 中
- 运行用于查询的 Python 函数时，在沙盒环境中执行它们

## License

MIT License


path: scripts/build_paths.py
content: #!/usr/bin/env python3
"""Build category path index from fred_categories_flat.json.

Outputs references/category_paths.json
"""
from __future__ import annotations

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
REFS = ROOT / "references"
FLAT_PATH = REFS / "fred_categories_flat.json"
OUT_PATH = REFS / "category_paths.json"


def load_flat() -> dict:
    if not FLAT_PATH.exists():
        raise SystemExit(f"Missing required file: {FLAT_PATH}")
    return json.loads(FLAT_PATH.read_text())


def build_path(flat: dict, cid: int) -> str:
    cur = flat.get(str(cid))
    if not cur:
        return ""
    parts = []
    while True:
        parts.append(cur.get("name", ""))
        parent_id = cur.get("parent_id")
        if parent_id is None or parent_id == -1:
            break
        cur = flat.get(str(parent_id))
        if not cur:
            break
    return " / ".join(reversed([p for p in parts if p]))


def main() -> int:
    flat = load_flat()
    out = {}
    for cid_str, info in flat.items():
        try:
            cid = int(info.get("id", cid_str))
        except Exception:
            continue
        name = info.get("name", "")
        path = build_path(flat, cid)
        out[str(cid)] = {"id": cid, "name": name, "path": path}

    OUT_PATH.write_text(json.dumps(out, ensure_ascii=False, indent=2))
    print(f"Wrote {OUT_PATH} with {len(out)} entries")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/fred_query.py
content: #!/usr/bin/env python3
"""Small wrapper around fredapi for common queries used in this skill.

Requires: FRED_API_KEY in environment.
"""
from __future__ import annotations

import argparse
import json
import os
import ssl
from pathlib import Path

# Fix SSL certificate verification issue
ssl._create_default_https_context = ssl._create_stdlib_context

from fredapi import Fred


ROOT = Path(__file__).resolve().parents[1]
REFS = ROOT / "references"
TREE_PATH = REFS / "fred_categories_tree.json"
FLAT_PATH = REFS / "fred_categories_flat.json"


def _get_fred() -> Fred:
    [REDACTED_SECRET]"FRED_API_KEY")
    if not api_key:
        raise SystemExit("Missing FRED_API_KEY in environment")
    return Fred([REDACTED_SECRET])


def _read_json(path: Path):
    if not path.exists():
        raise SystemExit(f"Missing required file: {path}")
    return json.loads(path.read_text())


def cmd_category(args: argparse.Namespace) -> int:
    fred = _get_fred()
    df = fred.search_by_category(args.category_id)
    if args.limit:
        df = df.head(args.limit)
    if args.output == "json":
        print(df.to_json(orient="records"))
    else:
        print(df)
    return 0


def cmd_series(args: argparse.Namespace) -> int:
    fred = _get_fred()
    s = fred.get_series(args.series_id)
    if args.tail:
        s = s.tail(args.tail)
    if args.output == "json":
        print(s.to_json())
    else:
        print(s)
    return 0


def cmd_series_info(args: argparse.Namespace) -> int:
    fred = _get_fred()
    info = fred.get_series_info(args.series_id)
    if args.output == "json":
        print(info.to_json())
    else:
        print(info)
    return 0


def cmd_category_path(args: argparse.Namespace) -> int:
    flat = _read_json(FLAT_PATH)
    target = str(args.category_id)
    if target not in flat:
        raise SystemExit(f"category_id not found: {args.category_id}")
    path = []
    cur = flat[target]
    while True:
        path.append(cur["name"])
        parent_id = cur.get("parent_id")
        if parent_id is None or parent_id == -1:
            break
        cur = flat.get(str(parent_id))
        if not cur:
            break
    path_str = " / ".join(reversed(path))
    print(path_str)
    return 0


def cmd_check_category(args: argparse.Namespace) -> int:
    flat = _read_json(FLAT_PATH)
    target = str(args.category_id)
    exists = target in flat
    if not exists:
        print(json.dumps({"exists": False, "children": False, "series": False}))
        return 0

    # check children via flat info
    children_flag = bool(flat[target].get("checked_children"))

    # check series count by calling FRED
    fred = _get_fred()
    try:
        df = fred.search_by_category(args.category_id)
        series_flag = len(df) > 0
    except Exception:
        series_flag = False

    print(json.dumps({"exists": True, "children": children_flag, "series": series_flag}))
    return 0


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="FRED helper queries")
    sub = p.add_subparsers(dest="cmd", required=True)

    p_cat = sub.add_parser("category", help="List series under a category")
    p_cat.add_argument("category_id", type=int)
    p_cat.add_argument("--limit", type=int, default=20)
    p_cat.add_argument("--output", choices=["table", "json"], default="table")
    p_cat.set_defaults(func=cmd_category)

    p_ser = sub.add_parser("series", help="Fetch series values")
    p_ser.add_argument("series_id")
    p_ser.add_argument("--tail", type=int, default=20)
    p_ser.add_argument("--output", choices=["table", "json"], default="table")
    p_ser.set_defaults(func=cmd_series)

    p_info = sub.add_parser("series-info", help="Fetch series metadata")
    p_info.add_argument("series_id")
    p_info.add_argument("--output", choices=["table", "json"], default="table")
    p_info.set_defaults(func=cmd_series_info)

    p_path = sub.add_parser("category-path", help="Show full path for a category")
    p_path.add_argument("category_id", type=int)
    p_path.set_defaults(func=cmd_category_path)

    p_chk = sub.add_parser("check-category", help="Check category existence and series")
    p_chk.add_argument("category_id", type=int)
    p_chk.set_defaults(func=cmd_check_category)

    return p


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    return args.func(args)


if __name__ == "__main__":
    raise SystemExit(main())


path: references/synonyms.json
content: {
  "banking": [
    "bank",
    "banks",
    "banking system",
    "financial institutions"
  ],
  "interest rate": [
    "rate",
    "rates",
    "yield",
    "yields"
  ],
  "inflation": [
    "cpi",
    "price level",
    "consumer prices"
  ],
  "employment": [
    "jobs",
    "labor market",
    "unemployment"
  ],
  "housing": [
    "house prices",
    "mortgage",
    "real estate"
  ],
  "gdp": [
    "gross domestic product",
    "economic output"
  ],
  "money supply": [
    "m1",
    "m2",
    "liquidity"
  ],
  "credit": [
    "loans",
    "lending",
    "consumer credit"
  ]
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (MEDIUM)