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.
@@ -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
+ }