fd-open-data-protocol 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fd_open_data_protocol-0.2.0/PKG-INFO +232 -0
- fd_open_data_protocol-0.2.0/README.md +219 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol/__init__.py +8 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol/constants.py +196 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol/loader.py +69 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol/provider.py +49 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol/schema.py +216 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/PKG-INFO +232 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/SOURCES.txt +13 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/dependency_links.txt +1 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/requires.txt +5 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/top_level.txt +1 -0
- fd_open_data_protocol-0.2.0/pyproject.toml +25 -0
- fd_open_data_protocol-0.2.0/setup.cfg +4 -0
- fd_open_data_protocol-0.2.0/tests/test_loader.py +132 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fd-open-data-protocol
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: The open-data datasource protocol: a manifest contract a datasource exposes to be ingested by fd-open-data-mcp (or any consumer).
|
|
5
|
+
Author: FindDataOfficial
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pydantic>=2.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# fd-open-data-protocol
|
|
15
|
+
|
|
16
|
+
**English** | [中文](README.zh-CN.md)
|
|
17
|
+
|
|
18
|
+
The **open-data datasource protocol**: a manifest contract a datasource exposes
|
|
19
|
+
(datasource + functions + columns + concept hints + fetch reference) so that
|
|
20
|
+
`fd-open-data-mcp` - or any consumer - can ingest it via `register_datasource`.
|
|
21
|
+
|
|
22
|
+
**Ship one manifest file -> the datasource is added. No consumer-side wiring.**
|
|
23
|
+
|
|
24
|
+
## One-click install
|
|
25
|
+
|
|
26
|
+
This library is a dependency of `fd-open-data-mcp` (pulled in transitively). To
|
|
27
|
+
install the **entire** finddata stack (hub + every datasource + ontology DB):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install "fd-open-data-mcp[data]" fd-polygon fd-cn-report
|
|
31
|
+
|
|
32
|
+
fd-open-data-mcp migrate \
|
|
33
|
+
&& fd-open-data-mcp import-catalog \
|
|
34
|
+
&& fd-open-data-mcp consume-concepts \
|
|
35
|
+
&& fd-open-data-mcp propose-bindings \
|
|
36
|
+
&& fd-open-data-mcp seed-entities \
|
|
37
|
+
&& fd-open-data-mcp generate-schedules \
|
|
38
|
+
&& fd-open-data-mcp register-discovered
|
|
39
|
+
|
|
40
|
+
fd-open-data-mcp serve
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## The manifest
|
|
44
|
+
|
|
45
|
+
A YAML/JSON file (or a Python module exposing `CATALOG`):
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
version: "1"
|
|
49
|
+
name: my-source
|
|
50
|
+
label: My Source
|
|
51
|
+
ranking_seed: [0.7, 0.7] # [quality, accessibility] heuristic seed
|
|
52
|
+
functions:
|
|
53
|
+
- command: get_data
|
|
54
|
+
frequency: daily
|
|
55
|
+
parameters: [{name: symbol, type: str, required: true}]
|
|
56
|
+
columns:
|
|
57
|
+
- {name: close, type: float, frequency: daily}
|
|
58
|
+
concepts: # column -> concept hints (measure/entity_type here)
|
|
59
|
+
- {column: close, concept: price.close, entity_type: stock, unit: currency, frequency: daily}
|
|
60
|
+
fetch:
|
|
61
|
+
runner: my-source # built-in runner name, OR module: "pkg.mod:run"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
See `examples/example_stock.yaml` (declarative) and `examples/example_macro.py`
|
|
65
|
+
(a `DataProvider` class with `run()`).
|
|
66
|
+
|
|
67
|
+
## Load + validate
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fd_open_data_protocol.loader import load_catalog
|
|
71
|
+
manifest = load_catalog("examples/example_stock.yaml")
|
|
72
|
+
print(manifest.name, len(manifest.functions))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`load_catalog` accepts a YAML/JSON file path, a `.py` file exposing `CATALOG`,
|
|
76
|
+
a `"pkg.mod"` module path, or a dict.
|
|
77
|
+
|
|
78
|
+
## Register with fd-open-data-mcp
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
fd-open-data-mcp register-datasource examples/example_stock.yaml
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
or the MCP tool `register_datasource(path)`.
|
|
85
|
+
|
|
86
|
+
## Publish a datasource from another project
|
|
87
|
+
|
|
88
|
+
In your datasource package's `pyproject.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[project.entry-points."fd_open_data_mcp.datasources"]
|
|
92
|
+
my-source = "my_pkg.catalog:CATALOG"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`pip install my-pkg` -> fd-open-data-mcp auto-registers it on `import_catalog`.
|
|
96
|
+
|
|
97
|
+
## Schema
|
|
98
|
+
|
|
99
|
+
- **`DatasourceManifest`**: name, label, source_url, scanner_mode, ranking_seed, functions[], concepts[], entities[], entity_definitions[], relationships[], fetch.
|
|
100
|
+
- **`FunctionSpec`**: command, category, description, parameters[], columns[], frequency, verified.
|
|
101
|
+
- **`ColumnSpec`**: name, type, description, meaning, semantic_type, `frequency` + `datasource` (column-level).
|
|
102
|
+
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence.
|
|
103
|
+
- **`EntitySpec`**: entity_type, coverage ("universe"|"explicit"), codes[] (for explicit coverage).
|
|
104
|
+
- **`Entity`**: entity_type, code, name_en, name_zh, metadata{}, relationships[].
|
|
105
|
+
- **`EntityRelationship`**: target_entity_type, target_code, relation_type, confidence, metadata{}.
|
|
106
|
+
- **`RelationshipSpec`**: relation_type, source_entity_type, target_entity_type, resolver_module.
|
|
107
|
+
- **`FetchRef`**: runner (built-in name) | module (`"pkg.mod:func"`).
|
|
108
|
+
|
|
109
|
+
`measure` + `entity_type` are **concept-level** (disambiguate GDP-nominal vs
|
|
110
|
+
GDP-PPP; stock close vs fund NAV). Column-level `frequency`/`datasource` support
|
|
111
|
+
composite functions whose columns come from different sources at different cadences.
|
|
112
|
+
|
|
113
|
+
## Entity Definitions
|
|
114
|
+
|
|
115
|
+
The protocol supports two ways to declare entities:
|
|
116
|
+
|
|
117
|
+
### 1. Coverage Declaration (`entities[]`)
|
|
118
|
+
|
|
119
|
+
Declares which entity types the datasource covers:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
entities:
|
|
123
|
+
- entity_type: stock
|
|
124
|
+
coverage: explicit
|
|
125
|
+
codes: [AAPL, MSFT, GOOGL]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- `coverage: "universe"` - datasource can fetch data for all entities of this type
|
|
129
|
+
- `coverage: "explicit"` - datasource only covers the listed codes
|
|
130
|
+
|
|
131
|
+
### 2. Entity Metadata (`entity_definitions[]`)
|
|
132
|
+
|
|
133
|
+
Defines canonical entity metadata (names, attributes, relationships):
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
entity_definitions:
|
|
137
|
+
- entity_type: stock
|
|
138
|
+
code: AAPL
|
|
139
|
+
name_en: Apple Inc.
|
|
140
|
+
name_zh: 苹果公司
|
|
141
|
+
metadata:
|
|
142
|
+
exchange: NASDAQ
|
|
143
|
+
sector: Technology
|
|
144
|
+
relationships:
|
|
145
|
+
- target_entity_type: industry
|
|
146
|
+
target_code: gics_10
|
|
147
|
+
relation_type: belongs_to
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
When included, entities are registered in the ontology database during `register_datasource()`.
|
|
151
|
+
|
|
152
|
+
### Canonical Entity Types
|
|
153
|
+
|
|
154
|
+
All `entity_type` values must be from this vocabulary:
|
|
155
|
+
|
|
156
|
+
| Type | Description | Example IDs |
|
|
157
|
+
|------|-------------|-------------|
|
|
158
|
+
| `country` | ISO codes | CN, US, JP |
|
|
159
|
+
| `city` | Municipalities | beijing, shanghai |
|
|
160
|
+
| `stock` | A-shares | 600000.SH, 000001.SZ |
|
|
161
|
+
| `fund` | ETFs/funds | etf_code, fund_code |
|
|
162
|
+
| `bond` | Bonds | bond_code |
|
|
163
|
+
| `index` | Indices | SH000001, SZ399001 |
|
|
164
|
+
| `future` | Futures | cu2412, rb2401 |
|
|
165
|
+
| `crypto` | Cryptocurrencies | btc, eth |
|
|
166
|
+
| `organization` | General orgs | org_code |
|
|
167
|
+
| `industry` | Classifications | shenwan_1_01, gics_10 |
|
|
168
|
+
| `company` | Public companies | AAPL, TSLA |
|
|
169
|
+
|
|
170
|
+
## Manifest Declaration Requirement
|
|
171
|
+
|
|
172
|
+
**Every fd-* datasource package MUST declare a `DatasourceManifest` via one of the following mechanisms:**
|
|
173
|
+
|
|
174
|
+
1. **Python module**: Expose a `CATALOG` dict in a module (e.g., `catalog.py`) that conforms to the `DatasourceManifest` schema
|
|
175
|
+
2. **YAML/JSON file**: Place a manifest file at the package root (e.g., `catalog.yaml` or `catalog.json`)
|
|
176
|
+
3. **Entry-point declaration**: Register the manifest path in `pyproject.toml` under `[project.entry-points."fd_open_data_mcp.datasources"]`
|
|
177
|
+
|
|
178
|
+
The declaration **SHALL** be discoverable by `fd-open-data-mcp`'s auto-discovery mechanism (`register-discovered` command). Packages without a CATALOG declaration **SHALL NOT** be considered compliant with the fd-open-data-protocol.
|
|
179
|
+
|
|
180
|
+
### Recommended Package Structure
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
my-datasource/
|
|
184
|
+
├── pyproject.toml # declares entry-point
|
|
185
|
+
└── my_pkg/
|
|
186
|
+
├── __init__.py
|
|
187
|
+
└── catalog.py # exposes CATALOG = { ... }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Entry-Point Declaration
|
|
191
|
+
|
|
192
|
+
In your `pyproject.toml`:
|
|
193
|
+
|
|
194
|
+
```toml
|
|
195
|
+
[project.entry-points."fd_open_data_mcp.datasources"]
|
|
196
|
+
my-source = "my_pkg.catalog:CATALOG"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
After `pip install my-pkg`, the package is automatically discoverable:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
fd-open-data-mcp register-discovered
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Auto-Discovery Flow
|
|
206
|
+
|
|
207
|
+
1. **Install package** → `pip install my-datasource`
|
|
208
|
+
2. **Entry-point registered** → setuptools records `my-source = "my_pkg.catalog:CATALOG"`
|
|
209
|
+
3. **Auto-discover** → `fd-open-data-mcp register-discovered` scans all entry-points
|
|
210
|
+
4. **Load manifest** → `load_catalog()` validates and parses the CATALOG dict
|
|
211
|
+
5. **Register to ontology** → `register_datasource()` upserts sources/functions/columns/concepts
|
|
212
|
+
|
|
213
|
+
### Compliance Checklist
|
|
214
|
+
|
|
215
|
+
Before publishing a new datasource package, ensure:
|
|
216
|
+
|
|
217
|
+
- [ ] Package exposes a `CATALOG` dict or manifest file
|
|
218
|
+
- [ ] `pyproject.toml` declares entry-point under `fd_open_data_mcp.datasources` group
|
|
219
|
+
- [ ] CATALOG conforms to `DatasourceManifest` schema (version, name, label, functions[], concepts[], fetch)
|
|
220
|
+
- [ ] `load_catalog()` can successfully parse the manifest
|
|
221
|
+
- [ ] `fd-open-data-mcp register-discovered` discovers and registers the package
|
|
222
|
+
|
|
223
|
+
### Working Examples
|
|
224
|
+
|
|
225
|
+
- **fd-world**: `fd_world/catalog.py` + entry-point in `pyproject.toml`
|
|
226
|
+
- **fd-cn-gov**: `fd_cn_gov/catalog.py` + entry-point in `pyproject.toml`
|
|
227
|
+
- **fd-cn-report**: `catalog.py` + entry-point in `pyproject.toml`
|
|
228
|
+
|
|
229
|
+
## Template
|
|
230
|
+
|
|
231
|
+
Copy `template/datasource.template.yaml` (declarative) or
|
|
232
|
+
`template/provider_template.py` (a `BaseDataProvider` class with `run()`).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# fd-open-data-protocol
|
|
2
|
+
|
|
3
|
+
**English** | [中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
The **open-data datasource protocol**: a manifest contract a datasource exposes
|
|
6
|
+
(datasource + functions + columns + concept hints + fetch reference) so that
|
|
7
|
+
`fd-open-data-mcp` - or any consumer - can ingest it via `register_datasource`.
|
|
8
|
+
|
|
9
|
+
**Ship one manifest file -> the datasource is added. No consumer-side wiring.**
|
|
10
|
+
|
|
11
|
+
## One-click install
|
|
12
|
+
|
|
13
|
+
This library is a dependency of `fd-open-data-mcp` (pulled in transitively). To
|
|
14
|
+
install the **entire** finddata stack (hub + every datasource + ontology DB):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install "fd-open-data-mcp[data]" fd-polygon fd-cn-report
|
|
18
|
+
|
|
19
|
+
fd-open-data-mcp migrate \
|
|
20
|
+
&& fd-open-data-mcp import-catalog \
|
|
21
|
+
&& fd-open-data-mcp consume-concepts \
|
|
22
|
+
&& fd-open-data-mcp propose-bindings \
|
|
23
|
+
&& fd-open-data-mcp seed-entities \
|
|
24
|
+
&& fd-open-data-mcp generate-schedules \
|
|
25
|
+
&& fd-open-data-mcp register-discovered
|
|
26
|
+
|
|
27
|
+
fd-open-data-mcp serve
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## The manifest
|
|
31
|
+
|
|
32
|
+
A YAML/JSON file (or a Python module exposing `CATALOG`):
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
version: "1"
|
|
36
|
+
name: my-source
|
|
37
|
+
label: My Source
|
|
38
|
+
ranking_seed: [0.7, 0.7] # [quality, accessibility] heuristic seed
|
|
39
|
+
functions:
|
|
40
|
+
- command: get_data
|
|
41
|
+
frequency: daily
|
|
42
|
+
parameters: [{name: symbol, type: str, required: true}]
|
|
43
|
+
columns:
|
|
44
|
+
- {name: close, type: float, frequency: daily}
|
|
45
|
+
concepts: # column -> concept hints (measure/entity_type here)
|
|
46
|
+
- {column: close, concept: price.close, entity_type: stock, unit: currency, frequency: daily}
|
|
47
|
+
fetch:
|
|
48
|
+
runner: my-source # built-in runner name, OR module: "pkg.mod:run"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
See `examples/example_stock.yaml` (declarative) and `examples/example_macro.py`
|
|
52
|
+
(a `DataProvider` class with `run()`).
|
|
53
|
+
|
|
54
|
+
## Load + validate
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from fd_open_data_protocol.loader import load_catalog
|
|
58
|
+
manifest = load_catalog("examples/example_stock.yaml")
|
|
59
|
+
print(manifest.name, len(manifest.functions))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`load_catalog` accepts a YAML/JSON file path, a `.py` file exposing `CATALOG`,
|
|
63
|
+
a `"pkg.mod"` module path, or a dict.
|
|
64
|
+
|
|
65
|
+
## Register with fd-open-data-mcp
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
fd-open-data-mcp register-datasource examples/example_stock.yaml
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
or the MCP tool `register_datasource(path)`.
|
|
72
|
+
|
|
73
|
+
## Publish a datasource from another project
|
|
74
|
+
|
|
75
|
+
In your datasource package's `pyproject.toml`:
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[project.entry-points."fd_open_data_mcp.datasources"]
|
|
79
|
+
my-source = "my_pkg.catalog:CATALOG"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`pip install my-pkg` -> fd-open-data-mcp auto-registers it on `import_catalog`.
|
|
83
|
+
|
|
84
|
+
## Schema
|
|
85
|
+
|
|
86
|
+
- **`DatasourceManifest`**: name, label, source_url, scanner_mode, ranking_seed, functions[], concepts[], entities[], entity_definitions[], relationships[], fetch.
|
|
87
|
+
- **`FunctionSpec`**: command, category, description, parameters[], columns[], frequency, verified.
|
|
88
|
+
- **`ColumnSpec`**: name, type, description, meaning, semantic_type, `frequency` + `datasource` (column-level).
|
|
89
|
+
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence.
|
|
90
|
+
- **`EntitySpec`**: entity_type, coverage ("universe"|"explicit"), codes[] (for explicit coverage).
|
|
91
|
+
- **`Entity`**: entity_type, code, name_en, name_zh, metadata{}, relationships[].
|
|
92
|
+
- **`EntityRelationship`**: target_entity_type, target_code, relation_type, confidence, metadata{}.
|
|
93
|
+
- **`RelationshipSpec`**: relation_type, source_entity_type, target_entity_type, resolver_module.
|
|
94
|
+
- **`FetchRef`**: runner (built-in name) | module (`"pkg.mod:func"`).
|
|
95
|
+
|
|
96
|
+
`measure` + `entity_type` are **concept-level** (disambiguate GDP-nominal vs
|
|
97
|
+
GDP-PPP; stock close vs fund NAV). Column-level `frequency`/`datasource` support
|
|
98
|
+
composite functions whose columns come from different sources at different cadences.
|
|
99
|
+
|
|
100
|
+
## Entity Definitions
|
|
101
|
+
|
|
102
|
+
The protocol supports two ways to declare entities:
|
|
103
|
+
|
|
104
|
+
### 1. Coverage Declaration (`entities[]`)
|
|
105
|
+
|
|
106
|
+
Declares which entity types the datasource covers:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
entities:
|
|
110
|
+
- entity_type: stock
|
|
111
|
+
coverage: explicit
|
|
112
|
+
codes: [AAPL, MSFT, GOOGL]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `coverage: "universe"` - datasource can fetch data for all entities of this type
|
|
116
|
+
- `coverage: "explicit"` - datasource only covers the listed codes
|
|
117
|
+
|
|
118
|
+
### 2. Entity Metadata (`entity_definitions[]`)
|
|
119
|
+
|
|
120
|
+
Defines canonical entity metadata (names, attributes, relationships):
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
entity_definitions:
|
|
124
|
+
- entity_type: stock
|
|
125
|
+
code: AAPL
|
|
126
|
+
name_en: Apple Inc.
|
|
127
|
+
name_zh: 苹果公司
|
|
128
|
+
metadata:
|
|
129
|
+
exchange: NASDAQ
|
|
130
|
+
sector: Technology
|
|
131
|
+
relationships:
|
|
132
|
+
- target_entity_type: industry
|
|
133
|
+
target_code: gics_10
|
|
134
|
+
relation_type: belongs_to
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
When included, entities are registered in the ontology database during `register_datasource()`.
|
|
138
|
+
|
|
139
|
+
### Canonical Entity Types
|
|
140
|
+
|
|
141
|
+
All `entity_type` values must be from this vocabulary:
|
|
142
|
+
|
|
143
|
+
| Type | Description | Example IDs |
|
|
144
|
+
|------|-------------|-------------|
|
|
145
|
+
| `country` | ISO codes | CN, US, JP |
|
|
146
|
+
| `city` | Municipalities | beijing, shanghai |
|
|
147
|
+
| `stock` | A-shares | 600000.SH, 000001.SZ |
|
|
148
|
+
| `fund` | ETFs/funds | etf_code, fund_code |
|
|
149
|
+
| `bond` | Bonds | bond_code |
|
|
150
|
+
| `index` | Indices | SH000001, SZ399001 |
|
|
151
|
+
| `future` | Futures | cu2412, rb2401 |
|
|
152
|
+
| `crypto` | Cryptocurrencies | btc, eth |
|
|
153
|
+
| `organization` | General orgs | org_code |
|
|
154
|
+
| `industry` | Classifications | shenwan_1_01, gics_10 |
|
|
155
|
+
| `company` | Public companies | AAPL, TSLA |
|
|
156
|
+
|
|
157
|
+
## Manifest Declaration Requirement
|
|
158
|
+
|
|
159
|
+
**Every fd-* datasource package MUST declare a `DatasourceManifest` via one of the following mechanisms:**
|
|
160
|
+
|
|
161
|
+
1. **Python module**: Expose a `CATALOG` dict in a module (e.g., `catalog.py`) that conforms to the `DatasourceManifest` schema
|
|
162
|
+
2. **YAML/JSON file**: Place a manifest file at the package root (e.g., `catalog.yaml` or `catalog.json`)
|
|
163
|
+
3. **Entry-point declaration**: Register the manifest path in `pyproject.toml` under `[project.entry-points."fd_open_data_mcp.datasources"]`
|
|
164
|
+
|
|
165
|
+
The declaration **SHALL** be discoverable by `fd-open-data-mcp`'s auto-discovery mechanism (`register-discovered` command). Packages without a CATALOG declaration **SHALL NOT** be considered compliant with the fd-open-data-protocol.
|
|
166
|
+
|
|
167
|
+
### Recommended Package Structure
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
my-datasource/
|
|
171
|
+
├── pyproject.toml # declares entry-point
|
|
172
|
+
└── my_pkg/
|
|
173
|
+
├── __init__.py
|
|
174
|
+
└── catalog.py # exposes CATALOG = { ... }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Entry-Point Declaration
|
|
178
|
+
|
|
179
|
+
In your `pyproject.toml`:
|
|
180
|
+
|
|
181
|
+
```toml
|
|
182
|
+
[project.entry-points."fd_open_data_mcp.datasources"]
|
|
183
|
+
my-source = "my_pkg.catalog:CATALOG"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
After `pip install my-pkg`, the package is automatically discoverable:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
fd-open-data-mcp register-discovered
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Auto-Discovery Flow
|
|
193
|
+
|
|
194
|
+
1. **Install package** → `pip install my-datasource`
|
|
195
|
+
2. **Entry-point registered** → setuptools records `my-source = "my_pkg.catalog:CATALOG"`
|
|
196
|
+
3. **Auto-discover** → `fd-open-data-mcp register-discovered` scans all entry-points
|
|
197
|
+
4. **Load manifest** → `load_catalog()` validates and parses the CATALOG dict
|
|
198
|
+
5. **Register to ontology** → `register_datasource()` upserts sources/functions/columns/concepts
|
|
199
|
+
|
|
200
|
+
### Compliance Checklist
|
|
201
|
+
|
|
202
|
+
Before publishing a new datasource package, ensure:
|
|
203
|
+
|
|
204
|
+
- [ ] Package exposes a `CATALOG` dict or manifest file
|
|
205
|
+
- [ ] `pyproject.toml` declares entry-point under `fd_open_data_mcp.datasources` group
|
|
206
|
+
- [ ] CATALOG conforms to `DatasourceManifest` schema (version, name, label, functions[], concepts[], fetch)
|
|
207
|
+
- [ ] `load_catalog()` can successfully parse the manifest
|
|
208
|
+
- [ ] `fd-open-data-mcp register-discovered` discovers and registers the package
|
|
209
|
+
|
|
210
|
+
### Working Examples
|
|
211
|
+
|
|
212
|
+
- **fd-world**: `fd_world/catalog.py` + entry-point in `pyproject.toml`
|
|
213
|
+
- **fd-cn-gov**: `fd_cn_gov/catalog.py` + entry-point in `pyproject.toml`
|
|
214
|
+
- **fd-cn-report**: `catalog.py` + entry-point in `pyproject.toml`
|
|
215
|
+
|
|
216
|
+
## Template
|
|
217
|
+
|
|
218
|
+
Copy `template/datasource.template.yaml` (declarative) or
|
|
219
|
+
`template/provider_template.py` (a `BaseDataProvider` class with `run()`).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""fd-open-data-protocol: the open-data datasource protocol.
|
|
2
|
+
|
|
3
|
+
A manifest contract a datasource exposes (datasource + functions + columns +
|
|
4
|
+
concept hints + fetch reference) so that fd-open-data-mcp - or any consumer -
|
|
5
|
+
can ingest it via ``register_datasource``. Ship one manifest file -> the
|
|
6
|
+
datasource is added; no consumer-side wiring.
|
|
7
|
+
"""
|
|
8
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Industry classification constants and helpers.
|
|
2
|
+
|
|
3
|
+
This module defines canonical industry classification systems and provides
|
|
4
|
+
helper functions for parsing industry codes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Canonical industry classification systems
|
|
12
|
+
CLASSIFICATION_SYSTEMS = {
|
|
13
|
+
"shenwan": {
|
|
14
|
+
"name": "申万行业分类",
|
|
15
|
+
"name_en": "Shenwan Industry Classification",
|
|
16
|
+
"levels": 3,
|
|
17
|
+
"prefix": "shenwan_",
|
|
18
|
+
"code_pattern": r"^shenwan_(\d)_(\d{2,6})$",
|
|
19
|
+
"description": "Chinese industry classification by Shenwan Research"
|
|
20
|
+
},
|
|
21
|
+
"gics": {
|
|
22
|
+
"name": "全球行业分类标准",
|
|
23
|
+
"name_en": "Global Industry Classification Standard",
|
|
24
|
+
"levels": 4,
|
|
25
|
+
"prefix": "gics_",
|
|
26
|
+
"code_pattern": r"^gics_(\d{2,8})$",
|
|
27
|
+
"description": "Global industry classification by MSCI and S&P"
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_industry_code(code: str) -> Optional[dict]:
|
|
33
|
+
"""Parse an industry code and extract classification system and level.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
code: Industry code (e.g., "shenwan_1_01", "gics_50")
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict with keys: system, level, numeric_code, or None if invalid
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> parse_industry_code("shenwan_1_01")
|
|
43
|
+
{'system': 'shenwan', 'level': 1, 'numeric_code': '01'}
|
|
44
|
+
>>> parse_industry_code("gics_50")
|
|
45
|
+
{'system': 'gics', 'level': None, 'numeric_code': '50'}
|
|
46
|
+
"""
|
|
47
|
+
for system, config in CLASSIFICATION_SYSTEMS.items():
|
|
48
|
+
pattern = re.compile(config["code_pattern"])
|
|
49
|
+
match = pattern.match(code)
|
|
50
|
+
if match:
|
|
51
|
+
groups = match.groups()
|
|
52
|
+
if system == "shenwan":
|
|
53
|
+
# shenwan_1_01 -> level=1, numeric_code=01
|
|
54
|
+
return {
|
|
55
|
+
"system": system,
|
|
56
|
+
"level": int(groups[0]),
|
|
57
|
+
"numeric_code": groups[1]
|
|
58
|
+
}
|
|
59
|
+
elif system == "gics":
|
|
60
|
+
# gics_50 -> level=None (determined by code length), numeric_code=50
|
|
61
|
+
numeric = groups[0]
|
|
62
|
+
# GICS levels: 2-digit=sector, 4-digit=industry group, 6-digit=industry, 8-digit=sub-industry
|
|
63
|
+
if len(numeric) == 2:
|
|
64
|
+
level = 1
|
|
65
|
+
elif len(numeric) == 4:
|
|
66
|
+
level = 2
|
|
67
|
+
elif len(numeric) == 6:
|
|
68
|
+
level = 3
|
|
69
|
+
elif len(numeric) == 8:
|
|
70
|
+
level = 4
|
|
71
|
+
else:
|
|
72
|
+
level = None
|
|
73
|
+
return {
|
|
74
|
+
"system": system,
|
|
75
|
+
"level": level,
|
|
76
|
+
"numeric_code": numeric
|
|
77
|
+
}
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_industry_code(code: str) -> bool:
|
|
82
|
+
"""Validate an industry code against canonical patterns.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
code: Industry code to validate
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if valid, False otherwise
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> validate_industry_code("shenwan_1_01")
|
|
92
|
+
True
|
|
93
|
+
>>> validate_industry_code("invalid_code")
|
|
94
|
+
False
|
|
95
|
+
"""
|
|
96
|
+
return parse_industry_code(code) is not None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_classification_system(code: str) -> Optional[str]:
|
|
100
|
+
"""Get the classification system name from an industry code.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
code: Industry code
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
System name (e.g., "shenwan", "gics") or None if invalid
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
>>> get_classification_system("shenwan_1_01")
|
|
110
|
+
'shenwan'
|
|
111
|
+
"""
|
|
112
|
+
result = parse_industry_code(code)
|
|
113
|
+
return result["system"] if result else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_industry_level(code: str) -> Optional[int]:
|
|
117
|
+
"""Get the hierarchy level from an industry code.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
code: Industry code
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Level (1, 2, 3, or 4) or None if invalid
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> get_industry_level("shenwan_1_01")
|
|
127
|
+
1
|
|
128
|
+
>>> get_industry_level("gics_50")
|
|
129
|
+
1
|
|
130
|
+
"""
|
|
131
|
+
result = parse_industry_code(code)
|
|
132
|
+
return result["level"] if result else None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def format_industry_code(system: str, level: Optional[int], numeric_code: str) -> str:
|
|
136
|
+
"""Format an industry code from components.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
system: Classification system (e.g., "shenwan", "gics")
|
|
140
|
+
level: Hierarchy level (for shenwan only)
|
|
141
|
+
numeric_code: Numeric code portion
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Formatted industry code
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
>>> format_industry_code("shenwan", 1, "01")
|
|
148
|
+
'shenwan_1_01'
|
|
149
|
+
>>> format_industry_code("gics", None, "50")
|
|
150
|
+
'gics_50'
|
|
151
|
+
"""
|
|
152
|
+
if system == "shenwan":
|
|
153
|
+
if level is None:
|
|
154
|
+
raise ValueError("Shenwan classification requires level parameter")
|
|
155
|
+
return f"shenwan_{level}_{numeric_code}"
|
|
156
|
+
elif system == "gics":
|
|
157
|
+
return f"gics_{numeric_code}"
|
|
158
|
+
else:
|
|
159
|
+
raise ValueError(f"Unknown classification system: {system}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Common industry codes for reference
|
|
163
|
+
COMMON_INDUSTRIES = {
|
|
164
|
+
"shenwan": {
|
|
165
|
+
"1": {
|
|
166
|
+
"01": "农林牧渔",
|
|
167
|
+
"02": "采掘",
|
|
168
|
+
"03": "化工",
|
|
169
|
+
"04": "钢铁",
|
|
170
|
+
"05": "有色金属",
|
|
171
|
+
"06": "建筑材料",
|
|
172
|
+
"07": "建筑装饰",
|
|
173
|
+
"08": "电气设备",
|
|
174
|
+
"09": "国防军工",
|
|
175
|
+
"10": "汽车",
|
|
176
|
+
"11": "机械设备",
|
|
177
|
+
"12": "休闲服务",
|
|
178
|
+
"13": "家用电器",
|
|
179
|
+
"14": "纺织服装",
|
|
180
|
+
"15": "轻工制造",
|
|
181
|
+
"16": "商业贸易",
|
|
182
|
+
"17": "食品饮料",
|
|
183
|
+
"18": "医药生物",
|
|
184
|
+
"19": "公用事业",
|
|
185
|
+
"20": "交通运输",
|
|
186
|
+
"21": "房地产",
|
|
187
|
+
"22": "电子",
|
|
188
|
+
"23": "通信",
|
|
189
|
+
"24": "计算机",
|
|
190
|
+
"25": "传媒",
|
|
191
|
+
"26": "银行",
|
|
192
|
+
"27": "非银金融",
|
|
193
|
+
"28": "综合",
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|