fd-open-data-protocol 0.2.0__tar.gz → 0.3.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 → fd_open_data_protocol-0.3.0}/PKG-INFO +89 -3
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/README.md +88 -2
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol/__init__.py +1 -1
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol/schema.py +84 -16
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/concepts.yaml +230 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/crosswalks/datacommons-worldbank.yaml +51 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/entity_types.yaml +99 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/event_types.yaml +17 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/qualifiers.yaml +51 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/relation_types.yaml +73 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/units.yaml +94 -0
- fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary.py +349 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol.egg-info/PKG-INFO +89 -3
- fd_open_data_protocol-0.3.0/fd_open_data_protocol.egg-info/SOURCES.txt +23 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/pyproject.toml +5 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/tests/test_loader.py +35 -0
- fd_open_data_protocol-0.3.0/tests/test_schema_vocabulary.py +86 -0
- fd_open_data_protocol-0.3.0/tests/test_vocabulary.py +186 -0
- fd_open_data_protocol-0.2.0/fd_open_data_protocol.egg-info/SOURCES.txt +0 -13
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol/constants.py +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol/loader.py +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol/provider.py +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol.egg-info/dependency_links.txt +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol.egg-info/requires.txt +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/fd_open_data_protocol.egg-info/top_level.txt +0 -0
- {fd_open_data_protocol-0.2.0 → fd_open_data_protocol-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fd-open-data-protocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
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
5
|
Author: FindDataOfficial
|
|
6
6
|
License: MIT
|
|
@@ -99,7 +99,7 @@ my-source = "my_pkg.catalog:CATALOG"
|
|
|
99
99
|
- **`DatasourceManifest`**: name, label, source_url, scanner_mode, ranking_seed, functions[], concepts[], entities[], entity_definitions[], relationships[], fetch.
|
|
100
100
|
- **`FunctionSpec`**: command, category, description, parameters[], columns[], frequency, verified.
|
|
101
101
|
- **`ColumnSpec`**: name, type, description, meaning, semantic_type, `frequency` + `datasource` (column-level).
|
|
102
|
-
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence.
|
|
102
|
+
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence, `concept_family` (optional).
|
|
103
103
|
- **`EntitySpec`**: entity_type, coverage ("universe"|"explicit"), codes[] (for explicit coverage).
|
|
104
104
|
- **`Entity`**: entity_type, code, name_en, name_zh, metadata{}, relationships[].
|
|
105
105
|
- **`EntityRelationship`**: target_entity_type, target_code, relation_type, confidence, metadata{}.
|
|
@@ -149,9 +149,33 @@ entity_definitions:
|
|
|
149
149
|
|
|
150
150
|
When included, entities are registered in the ontology database during `register_datasource()`.
|
|
151
151
|
|
|
152
|
+
#### Reserved metadata key: `external_ids`
|
|
153
|
+
|
|
154
|
+
An entity definition may anchor itself in an external entity graph under the
|
|
155
|
+
reserved metadata key `external_ids`. Registration persists each entry as a
|
|
156
|
+
per-source identifier, so consumers can resolve the external id back to the
|
|
157
|
+
local entity.
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
entity_definitions:
|
|
161
|
+
- entity_type: country
|
|
162
|
+
code: CN
|
|
163
|
+
name_en: China
|
|
164
|
+
metadata:
|
|
165
|
+
external_ids:
|
|
166
|
+
wikidata: Q148 # Wikidata QID (Q followed by digits)
|
|
167
|
+
datacommons: country/CHN # Data Commons DCID
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Supported anchor sources are `wikidata` (QID, validated as `Q<digits>`) and
|
|
171
|
+
`datacommons` (non-empty DCID). A malformed or unknown anchor is logged and
|
|
172
|
+
skipped — it never fails registration.
|
|
173
|
+
|
|
152
174
|
### Canonical Entity Types
|
|
153
175
|
|
|
154
|
-
|
|
176
|
+
The entity-type vocabulary is defined by
|
|
177
|
+
[`fd_open_data_protocol/vocabulary/entity_types.yaml`](fd_open_data_protocol/vocabulary/entity_types.yaml)
|
|
178
|
+
(14 types) and is the authority for manifest validation:
|
|
155
179
|
|
|
156
180
|
| Type | Description | Example IDs |
|
|
157
181
|
|------|-------------|-------------|
|
|
@@ -165,7 +189,69 @@ All `entity_type` values must be from this vocabulary:
|
|
|
165
189
|
| `crypto` | Cryptocurrencies | btc, eth |
|
|
166
190
|
| `organization` | General orgs | org_code |
|
|
167
191
|
| `industry` | Classifications | shenwan_1_01, gics_10 |
|
|
192
|
+
| `exchange` | Exchanges / venues | XNYS, XNAS, SSE |
|
|
168
193
|
| `company` | Public companies | AAPL, TSLA |
|
|
194
|
+
| `commodity` | Raw goods | steel, oil, grain |
|
|
195
|
+
| `person` | Humans (fund managers) | person_code |
|
|
196
|
+
|
|
197
|
+
## Semantic vocabulary
|
|
198
|
+
|
|
199
|
+
The protocol ships the publishable semantic core of FindData as versioned YAML
|
|
200
|
+
under [`fd_open_data_protocol/vocabulary/`](fd_open_data_protocol/vocabulary/):
|
|
201
|
+
|
|
202
|
+
| File | Registry |
|
|
203
|
+
|------|----------|
|
|
204
|
+
| `entity_types.yaml` | entity types (the manifest vocabulary above) |
|
|
205
|
+
| `concepts.yaml` | concept families + their seeded Variables |
|
|
206
|
+
| `qualifiers.yaml` | qualifiers (nominal_current, real_constant, ppp, per_capita, growth, ...) |
|
|
207
|
+
| `units.yaml` | units of measure (UN/CEFACT codes + ISO 4217 currencies) |
|
|
208
|
+
| `relation_types.yaml` | entity relationship types |
|
|
209
|
+
| `event_types.yaml` | event types (empty in this release) |
|
|
210
|
+
| `crosswalks/*.yaml` | Variable ↔ external-vocabulary assertions |
|
|
211
|
+
|
|
212
|
+
The vocabulary files ship as package data (`0.3.0`+), so `load_vocabulary()`
|
|
213
|
+
works from an installed wheel with no checkout on disk.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from fd_open_data_protocol.vocabulary import load_vocabulary, validate_vocabulary
|
|
217
|
+
|
|
218
|
+
vocab = load_vocabulary() # shipped files
|
|
219
|
+
vocab = load_vocabulary("/path/to/dir") # or an explicit directory
|
|
220
|
+
vocab.version # release version of the file set
|
|
221
|
+
vocab.get("concept", "GDP").uri # https://schema.finddata.tech/concept/GDP
|
|
222
|
+
vocab.entity_type_ids() # the manifest entity-type vocabulary
|
|
223
|
+
vocab.seeded_variables() # Variables a family declares
|
|
224
|
+
errors = validate_vocabulary() # [] when the file set is valid
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Every entry carries a stable URI of the form
|
|
228
|
+
`https://schema.finddata.tech/<kind>/<Id>`, where `<kind>` is one of
|
|
229
|
+
`entity-type`, `concept`, `qualifier`, `unit`, `relation-type`, `event-type`.
|
|
230
|
+
|
|
231
|
+
**Stability rules.** A URI is never reassigned to a different meaning and
|
|
232
|
+
entries are never deleted. Retirement is expressed with a `superseded_by`
|
|
233
|
+
pointer to the successor entry, which keeps the URI resolvable. The file set
|
|
234
|
+
carries one release `version`, and the loader rejects a file set whose files
|
|
235
|
+
disagree.
|
|
236
|
+
|
|
237
|
+
## Concept families and Variables
|
|
238
|
+
|
|
239
|
+
Concepts are two-level. A **concept family** (`fd:GDP`, `fd:Population`) is the
|
|
240
|
+
small curated vocabulary from `concepts.yaml`; a **Variable** is the qualified
|
|
241
|
+
combination `(code, entity_type, measure, unit, frequency)` — the identity a
|
|
242
|
+
datasource actually binds columns to.
|
|
243
|
+
|
|
244
|
+
A manifest concept hint may point at its family with the optional
|
|
245
|
+
`concept_family` field, which the loader validates against `concepts.yaml`:
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
concepts:
|
|
249
|
+
- {column: close, concept: price.close, entity_type: stock, unit: currency,
|
|
250
|
+
frequency: daily, concept_family: PriceClose}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Omitting it preserves the previous behaviour — no family validation occurs, and
|
|
254
|
+
the consumer derives a family from the concept code.
|
|
169
255
|
|
|
170
256
|
## Manifest Declaration Requirement
|
|
171
257
|
|
|
@@ -86,7 +86,7 @@ my-source = "my_pkg.catalog:CATALOG"
|
|
|
86
86
|
- **`DatasourceManifest`**: name, label, source_url, scanner_mode, ranking_seed, functions[], concepts[], entities[], entity_definitions[], relationships[], fetch.
|
|
87
87
|
- **`FunctionSpec`**: command, category, description, parameters[], columns[], frequency, verified.
|
|
88
88
|
- **`ColumnSpec`**: name, type, description, meaning, semantic_type, `frequency` + `datasource` (column-level).
|
|
89
|
-
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence.
|
|
89
|
+
- **`ConceptHint`**: column, concept, `entity_type`, `measure`, unit, frequency, confidence, `concept_family` (optional).
|
|
90
90
|
- **`EntitySpec`**: entity_type, coverage ("universe"|"explicit"), codes[] (for explicit coverage).
|
|
91
91
|
- **`Entity`**: entity_type, code, name_en, name_zh, metadata{}, relationships[].
|
|
92
92
|
- **`EntityRelationship`**: target_entity_type, target_code, relation_type, confidence, metadata{}.
|
|
@@ -136,9 +136,33 @@ entity_definitions:
|
|
|
136
136
|
|
|
137
137
|
When included, entities are registered in the ontology database during `register_datasource()`.
|
|
138
138
|
|
|
139
|
+
#### Reserved metadata key: `external_ids`
|
|
140
|
+
|
|
141
|
+
An entity definition may anchor itself in an external entity graph under the
|
|
142
|
+
reserved metadata key `external_ids`. Registration persists each entry as a
|
|
143
|
+
per-source identifier, so consumers can resolve the external id back to the
|
|
144
|
+
local entity.
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
entity_definitions:
|
|
148
|
+
- entity_type: country
|
|
149
|
+
code: CN
|
|
150
|
+
name_en: China
|
|
151
|
+
metadata:
|
|
152
|
+
external_ids:
|
|
153
|
+
wikidata: Q148 # Wikidata QID (Q followed by digits)
|
|
154
|
+
datacommons: country/CHN # Data Commons DCID
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Supported anchor sources are `wikidata` (QID, validated as `Q<digits>`) and
|
|
158
|
+
`datacommons` (non-empty DCID). A malformed or unknown anchor is logged and
|
|
159
|
+
skipped — it never fails registration.
|
|
160
|
+
|
|
139
161
|
### Canonical Entity Types
|
|
140
162
|
|
|
141
|
-
|
|
163
|
+
The entity-type vocabulary is defined by
|
|
164
|
+
[`fd_open_data_protocol/vocabulary/entity_types.yaml`](fd_open_data_protocol/vocabulary/entity_types.yaml)
|
|
165
|
+
(14 types) and is the authority for manifest validation:
|
|
142
166
|
|
|
143
167
|
| Type | Description | Example IDs |
|
|
144
168
|
|------|-------------|-------------|
|
|
@@ -152,7 +176,69 @@ All `entity_type` values must be from this vocabulary:
|
|
|
152
176
|
| `crypto` | Cryptocurrencies | btc, eth |
|
|
153
177
|
| `organization` | General orgs | org_code |
|
|
154
178
|
| `industry` | Classifications | shenwan_1_01, gics_10 |
|
|
179
|
+
| `exchange` | Exchanges / venues | XNYS, XNAS, SSE |
|
|
155
180
|
| `company` | Public companies | AAPL, TSLA |
|
|
181
|
+
| `commodity` | Raw goods | steel, oil, grain |
|
|
182
|
+
| `person` | Humans (fund managers) | person_code |
|
|
183
|
+
|
|
184
|
+
## Semantic vocabulary
|
|
185
|
+
|
|
186
|
+
The protocol ships the publishable semantic core of FindData as versioned YAML
|
|
187
|
+
under [`fd_open_data_protocol/vocabulary/`](fd_open_data_protocol/vocabulary/):
|
|
188
|
+
|
|
189
|
+
| File | Registry |
|
|
190
|
+
|------|----------|
|
|
191
|
+
| `entity_types.yaml` | entity types (the manifest vocabulary above) |
|
|
192
|
+
| `concepts.yaml` | concept families + their seeded Variables |
|
|
193
|
+
| `qualifiers.yaml` | qualifiers (nominal_current, real_constant, ppp, per_capita, growth, ...) |
|
|
194
|
+
| `units.yaml` | units of measure (UN/CEFACT codes + ISO 4217 currencies) |
|
|
195
|
+
| `relation_types.yaml` | entity relationship types |
|
|
196
|
+
| `event_types.yaml` | event types (empty in this release) |
|
|
197
|
+
| `crosswalks/*.yaml` | Variable ↔ external-vocabulary assertions |
|
|
198
|
+
|
|
199
|
+
The vocabulary files ship as package data (`0.3.0`+), so `load_vocabulary()`
|
|
200
|
+
works from an installed wheel with no checkout on disk.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from fd_open_data_protocol.vocabulary import load_vocabulary, validate_vocabulary
|
|
204
|
+
|
|
205
|
+
vocab = load_vocabulary() # shipped files
|
|
206
|
+
vocab = load_vocabulary("/path/to/dir") # or an explicit directory
|
|
207
|
+
vocab.version # release version of the file set
|
|
208
|
+
vocab.get("concept", "GDP").uri # https://schema.finddata.tech/concept/GDP
|
|
209
|
+
vocab.entity_type_ids() # the manifest entity-type vocabulary
|
|
210
|
+
vocab.seeded_variables() # Variables a family declares
|
|
211
|
+
errors = validate_vocabulary() # [] when the file set is valid
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Every entry carries a stable URI of the form
|
|
215
|
+
`https://schema.finddata.tech/<kind>/<Id>`, where `<kind>` is one of
|
|
216
|
+
`entity-type`, `concept`, `qualifier`, `unit`, `relation-type`, `event-type`.
|
|
217
|
+
|
|
218
|
+
**Stability rules.** A URI is never reassigned to a different meaning and
|
|
219
|
+
entries are never deleted. Retirement is expressed with a `superseded_by`
|
|
220
|
+
pointer to the successor entry, which keeps the URI resolvable. The file set
|
|
221
|
+
carries one release `version`, and the loader rejects a file set whose files
|
|
222
|
+
disagree.
|
|
223
|
+
|
|
224
|
+
## Concept families and Variables
|
|
225
|
+
|
|
226
|
+
Concepts are two-level. A **concept family** (`fd:GDP`, `fd:Population`) is the
|
|
227
|
+
small curated vocabulary from `concepts.yaml`; a **Variable** is the qualified
|
|
228
|
+
combination `(code, entity_type, measure, unit, frequency)` — the identity a
|
|
229
|
+
datasource actually binds columns to.
|
|
230
|
+
|
|
231
|
+
A manifest concept hint may point at its family with the optional
|
|
232
|
+
`concept_family` field, which the loader validates against `concepts.yaml`:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
concepts:
|
|
236
|
+
- {column: close, concept: price.close, entity_type: stock, unit: currency,
|
|
237
|
+
frequency: daily, concept_family: PriceClose}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Omitting it preserves the previous behaviour — no family validation occurs, and
|
|
241
|
+
the consumer derives a family from the concept code.
|
|
156
242
|
|
|
157
243
|
## Manifest Declaration Requirement
|
|
158
244
|
|
|
@@ -10,26 +10,70 @@ entity metadata (names, attributes, relationships) in the ontology database.
|
|
|
10
10
|
"""
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from typing import Any, Iterable, Optional
|
|
14
15
|
|
|
15
16
|
from pydantic import BaseModel, Field, field_validator
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
@lru_cache(maxsize=1)
|
|
20
|
+
def _concept_family_ids() -> frozenset[str]:
|
|
21
|
+
"""Concept family ids from the shipped vocabulary (loaded once per process)."""
|
|
22
|
+
from fd_open_data_protocol.vocabulary import load_vocabulary
|
|
23
|
+
|
|
24
|
+
return frozenset(load_vocabulary().ids("concept"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _EntityTypeVocabulary:
|
|
28
|
+
"""Lazy, file-sourced view of ``vocabulary/entity_types.yaml``.
|
|
29
|
+
|
|
30
|
+
Replaces the hardcoded tuple of entity types while keeping the public name
|
|
31
|
+
and the operations callers use (membership, iteration, ``len``, ``join``).
|
|
32
|
+
The file is read on first use, not at import time. An override can be
|
|
33
|
+
pinned for tests via :func:`set_entity_type_vocabulary`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self._override: Optional[tuple[str, ...]] = None
|
|
38
|
+
self._cache: Optional[tuple[str, ...]] = None
|
|
39
|
+
|
|
40
|
+
def _values(self) -> tuple[str, ...]:
|
|
41
|
+
if self._override is not None:
|
|
42
|
+
return self._override
|
|
43
|
+
if self._cache is None:
|
|
44
|
+
from fd_open_data_protocol.vocabulary import load_vocabulary
|
|
45
|
+
|
|
46
|
+
self._cache = load_vocabulary().entity_type_ids()
|
|
47
|
+
return self._cache
|
|
48
|
+
|
|
49
|
+
def set(self, values: Optional[Iterable[str]] = None) -> None:
|
|
50
|
+
"""Pin the vocabulary to ``values``, or reset to file-sourced when None."""
|
|
51
|
+
self._override = tuple(values) if values is not None else None
|
|
52
|
+
|
|
53
|
+
def __contains__(self, item: object) -> bool:
|
|
54
|
+
return item in self._values()
|
|
55
|
+
|
|
56
|
+
def __iter__(self):
|
|
57
|
+
return iter(self._values())
|
|
58
|
+
|
|
59
|
+
def __len__(self) -> int:
|
|
60
|
+
return len(self._values())
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, index):
|
|
63
|
+
return self._values()[index]
|
|
64
|
+
|
|
65
|
+
def __repr__(self) -> str:
|
|
66
|
+
return repr(self._values())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Canonical entity type vocabulary - all entity_type values must be in this set.
|
|
70
|
+
# Sourced from the shipped vocabulary file; see fd_open_data_protocol.vocabulary.
|
|
71
|
+
ENTITY_TYPE_VOCABULARY = _EntityTypeVocabulary()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def set_entity_type_vocabulary(values: Optional[Iterable[str]] = None) -> None:
|
|
75
|
+
"""Test escape hatch: pin the entity-type vocabulary, or reset to file-sourced."""
|
|
76
|
+
ENTITY_TYPE_VOCABULARY.set(values)
|
|
33
77
|
|
|
34
78
|
|
|
35
79
|
class EntityRelationship(BaseModel):
|
|
@@ -156,6 +200,9 @@ class ConceptHint(BaseModel):
|
|
|
156
200
|
unit: Optional[str] = None
|
|
157
201
|
frequency: Optional[str] = None
|
|
158
202
|
confidence: float = 0.9
|
|
203
|
+
# Optional reference to a concept family in vocabulary/concepts.yaml, tying
|
|
204
|
+
# this concept to the two-level semantic model. Omitted = no family check.
|
|
205
|
+
concept_family: Optional[str] = None
|
|
159
206
|
|
|
160
207
|
@field_validator("entity_type")
|
|
161
208
|
@classmethod
|
|
@@ -166,6 +213,18 @@ class ConceptHint(BaseModel):
|
|
|
166
213
|
)
|
|
167
214
|
return v
|
|
168
215
|
|
|
216
|
+
@field_validator("concept_family")
|
|
217
|
+
@classmethod
|
|
218
|
+
def validate_concept_family(cls, v: Optional[str]) -> Optional[str]:
|
|
219
|
+
if v is None:
|
|
220
|
+
return v
|
|
221
|
+
if v not in _concept_family_ids():
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Unknown concept family '{v}'. Must be one of: "
|
|
224
|
+
f"{', '.join(sorted(_concept_family_ids()))}"
|
|
225
|
+
)
|
|
226
|
+
return v
|
|
227
|
+
|
|
169
228
|
|
|
170
229
|
class FetchRef(BaseModel):
|
|
171
230
|
runner: Optional[str] = None # built-in runner name
|
|
@@ -183,6 +242,15 @@ class EntitySpec(BaseModel):
|
|
|
183
242
|
coverage: str = "universe" # "universe" or "explicit"
|
|
184
243
|
codes: Optional[list[str]] = None # required if coverage="explicit"
|
|
185
244
|
|
|
245
|
+
@field_validator("entity_type")
|
|
246
|
+
@classmethod
|
|
247
|
+
def validate_entity_type(cls, v: str) -> str:
|
|
248
|
+
if v not in ENTITY_TYPE_VOCABULARY:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Invalid entity_type '{v}'. Must be one of: {', '.join(ENTITY_TYPE_VOCABULARY)}"
|
|
251
|
+
)
|
|
252
|
+
return v
|
|
253
|
+
|
|
186
254
|
|
|
187
255
|
class RelationshipSpec(BaseModel):
|
|
188
256
|
"""Relationship resolution declaration.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# FindData vocabulary — concept families.
|
|
2
|
+
#
|
|
3
|
+
# The first level of the two-level concept model. A *Concept family* is the
|
|
4
|
+
# small, curated semantic vocabulary (`fd:GDP`, `fd:Population`, ...); a
|
|
5
|
+
# *Variable* is the qualified five-tuple identity that lives in the MCP
|
|
6
|
+
# ontology store `concepts` table and carries `concept_code` pointing back here.
|
|
7
|
+
#
|
|
8
|
+
# URIs: https://schema.finddata.tech/concept/<id>
|
|
9
|
+
#
|
|
10
|
+
# `variables:` blocks are the initial seed content — the five-tuple identities
|
|
11
|
+
# `consume-concepts` materializes for that family. Families without a
|
|
12
|
+
# `variables:` block are declarative only (derivation links to them by code).
|
|
13
|
+
#
|
|
14
|
+
# Entries are never deleted — retirement is expressed with `superseded_by`.
|
|
15
|
+
version: "2026.09"
|
|
16
|
+
kind: concept
|
|
17
|
+
entries:
|
|
18
|
+
- id: GDP
|
|
19
|
+
label_en: Gross Domestic Product
|
|
20
|
+
label_zh: 国内生产总值
|
|
21
|
+
description: >-
|
|
22
|
+
The market value of all final goods and services produced within a
|
|
23
|
+
country's borders in a period. The qualified variants (nominal, real,
|
|
24
|
+
PPP, per-capita, growth) are distinct Variables of this family.
|
|
25
|
+
value_type: currency
|
|
26
|
+
unit_type: currency
|
|
27
|
+
dimensions: [nominal_current, real_constant, ppp, per_capita, per_capita_ppp, growth]
|
|
28
|
+
variables:
|
|
29
|
+
- {code: gdp, entity_type: country, measure: nominal_current, unit: usd, frequency: yearly}
|
|
30
|
+
- {code: gdp, entity_type: country, measure: real_constant, unit: usd, frequency: yearly}
|
|
31
|
+
- {code: gdp, entity_type: country, measure: growth, unit: "%", frequency: yearly}
|
|
32
|
+
- {code: gdp, entity_type: country, measure: per_capita, unit: usd, frequency: yearly}
|
|
33
|
+
- {code: gdp, entity_type: country, measure: per_capita_ppp, unit: international, frequency: yearly}
|
|
34
|
+
|
|
35
|
+
- id: Population
|
|
36
|
+
label_en: Population
|
|
37
|
+
label_zh: 人口
|
|
38
|
+
description: >-
|
|
39
|
+
Total resident population of an entity at a point in time. Aligns with
|
|
40
|
+
Data Commons Count_Person and World Bank SP.POP.TOTL.
|
|
41
|
+
value_type: count
|
|
42
|
+
unit_type: count
|
|
43
|
+
dimensions: []
|
|
44
|
+
variables:
|
|
45
|
+
- {code: population.total, entity_type: country, measure: "", unit: persons, frequency: yearly}
|
|
46
|
+
|
|
47
|
+
- id: PriceClose
|
|
48
|
+
label_en: Closing Price
|
|
49
|
+
label_zh: 收盘价
|
|
50
|
+
description: >-
|
|
51
|
+
Price of the last trade in a session — the canonical closing quote for a
|
|
52
|
+
listed instrument.
|
|
53
|
+
value_type: currency
|
|
54
|
+
unit_type: currency
|
|
55
|
+
dimensions: []
|
|
56
|
+
variables:
|
|
57
|
+
- {code: price.close, entity_type: stock, measure: "", unit: currency, frequency: daily}
|
|
58
|
+
|
|
59
|
+
- id: PriceOpen
|
|
60
|
+
label_en: Opening Price
|
|
61
|
+
label_zh: 开盘价
|
|
62
|
+
description: Price of the first trade in a session.
|
|
63
|
+
value_type: currency
|
|
64
|
+
unit_type: currency
|
|
65
|
+
dimensions: []
|
|
66
|
+
variables:
|
|
67
|
+
- {code: price.open, entity_type: stock, measure: "", unit: currency, frequency: daily}
|
|
68
|
+
|
|
69
|
+
- id: PriceHigh
|
|
70
|
+
label_en: Session High Price
|
|
71
|
+
label_zh: 最高价
|
|
72
|
+
description: Highest traded price during a session.
|
|
73
|
+
value_type: currency
|
|
74
|
+
unit_type: currency
|
|
75
|
+
dimensions: []
|
|
76
|
+
variables:
|
|
77
|
+
- {code: price.high, entity_type: stock, measure: "", unit: currency, frequency: daily}
|
|
78
|
+
|
|
79
|
+
- id: PriceLow
|
|
80
|
+
label_en: Session Low Price
|
|
81
|
+
label_zh: 最低价
|
|
82
|
+
description: Lowest traded price during a session.
|
|
83
|
+
value_type: currency
|
|
84
|
+
unit_type: currency
|
|
85
|
+
dimensions: []
|
|
86
|
+
variables:
|
|
87
|
+
- {code: price.low, entity_type: stock, measure: "", unit: currency, frequency: daily}
|
|
88
|
+
|
|
89
|
+
- id: PriceVolume
|
|
90
|
+
label_en: Traded Volume
|
|
91
|
+
label_zh: 成交量
|
|
92
|
+
description: Number of units traded during a session.
|
|
93
|
+
value_type: count
|
|
94
|
+
unit_type: count
|
|
95
|
+
dimensions: []
|
|
96
|
+
variables:
|
|
97
|
+
- {code: price.volume, entity_type: stock, measure: "", unit: shares, frequency: daily}
|
|
98
|
+
|
|
99
|
+
- id: PriceAmount
|
|
100
|
+
label_en: Traded Turnover
|
|
101
|
+
label_zh: 成交额
|
|
102
|
+
description: Monetary value traded during a session.
|
|
103
|
+
value_type: currency
|
|
104
|
+
unit_type: currency
|
|
105
|
+
dimensions: []
|
|
106
|
+
variables:
|
|
107
|
+
- {code: price.amount, entity_type: stock, measure: "", unit: currency, frequency: daily}
|
|
108
|
+
|
|
109
|
+
- id: NavUnit
|
|
110
|
+
label_en: Net Asset Value per Unit
|
|
111
|
+
label_zh: 单位净值
|
|
112
|
+
description: >-
|
|
113
|
+
Net asset value of one fund unit — the canonical daily price series for
|
|
114
|
+
funds and ETFs.
|
|
115
|
+
value_type: currency
|
|
116
|
+
unit_type: currency
|
|
117
|
+
dimensions: []
|
|
118
|
+
variables:
|
|
119
|
+
- {code: nav.unit, entity_type: fund, measure: "", unit: currency, frequency: daily}
|
|
120
|
+
|
|
121
|
+
- id: Revenue
|
|
122
|
+
label_en: Revenue
|
|
123
|
+
label_zh: 营业收入
|
|
124
|
+
description: Operating revenue recognized in a reporting period.
|
|
125
|
+
value_type: currency
|
|
126
|
+
unit_type: currency
|
|
127
|
+
dimensions: [growth]
|
|
128
|
+
variables:
|
|
129
|
+
- {code: financials.revenue, entity_type: stock, measure: "", unit: currency, frequency: yearly}
|
|
130
|
+
|
|
131
|
+
- id: NetProfit
|
|
132
|
+
label_en: Net Profit
|
|
133
|
+
label_zh: 净利润
|
|
134
|
+
description: Profit attributable to the reporting entity after tax.
|
|
135
|
+
value_type: currency
|
|
136
|
+
unit_type: currency
|
|
137
|
+
dimensions: [growth]
|
|
138
|
+
variables:
|
|
139
|
+
- {code: financials.net_profit, entity_type: stock, measure: "", unit: currency, frequency: yearly}
|
|
140
|
+
|
|
141
|
+
- id: TotalAssets
|
|
142
|
+
label_en: Total Assets
|
|
143
|
+
label_zh: 资产总计
|
|
144
|
+
description: Carrying amount of all assets at the reporting date.
|
|
145
|
+
value_type: currency
|
|
146
|
+
unit_type: currency
|
|
147
|
+
dimensions: []
|
|
148
|
+
variables:
|
|
149
|
+
- {code: financials.total_assets, entity_type: stock, measure: "", unit: currency, frequency: yearly}
|
|
150
|
+
|
|
151
|
+
- id: TotalLiabilities
|
|
152
|
+
label_en: Total Liabilities
|
|
153
|
+
label_zh: 负债合计
|
|
154
|
+
description: Carrying amount of all liabilities at the reporting date.
|
|
155
|
+
value_type: currency
|
|
156
|
+
unit_type: currency
|
|
157
|
+
dimensions: []
|
|
158
|
+
variables:
|
|
159
|
+
- {code: financials.total_liabilities, entity_type: stock, measure: "", unit: currency, frequency: yearly}
|
|
160
|
+
|
|
161
|
+
- id: Inflation
|
|
162
|
+
label_en: Inflation Rate
|
|
163
|
+
label_zh: 通货膨胀率
|
|
164
|
+
description: >-
|
|
165
|
+
Rate of change of a general price level, typically consumer prices.
|
|
166
|
+
Aligns with World Bank FP.CPI.TOTL.ZG.
|
|
167
|
+
value_type: ratio
|
|
168
|
+
unit_type: percent
|
|
169
|
+
dimensions: []
|
|
170
|
+
variables: []
|
|
171
|
+
|
|
172
|
+
- id: Unemployment
|
|
173
|
+
label_en: Unemployment Rate
|
|
174
|
+
label_zh: 失业率
|
|
175
|
+
description: >-
|
|
176
|
+
Share of the labour force that is without work and seeking it. Aligns
|
|
177
|
+
with World Bank SL.UEM.TOTL.ZS.
|
|
178
|
+
value_type: ratio
|
|
179
|
+
unit_type: percent
|
|
180
|
+
dimensions: []
|
|
181
|
+
variables: []
|
|
182
|
+
|
|
183
|
+
- id: LifeExpectancy
|
|
184
|
+
label_en: Life Expectancy
|
|
185
|
+
label_zh: 预期寿命
|
|
186
|
+
description: >-
|
|
187
|
+
Expected years of life at birth for a given population. Aligns with
|
|
188
|
+
World Bank SP.DYN.LE00.IN.
|
|
189
|
+
value_type: duration
|
|
190
|
+
unit_type: count
|
|
191
|
+
dimensions: []
|
|
192
|
+
variables: []
|
|
193
|
+
|
|
194
|
+
- id: CO2Emissions
|
|
195
|
+
label_en: CO2 Emissions
|
|
196
|
+
label_zh: 二氧化碳排放
|
|
197
|
+
description: >-
|
|
198
|
+
Carbon dioxide emissions from fuel combustion, total or per capita.
|
|
199
|
+
Aligns with World Bank EN.ATM.CO2E.KT and EN.ATM.CO2E.PC.
|
|
200
|
+
value_type: count
|
|
201
|
+
unit_type: count
|
|
202
|
+
dimensions: [per_capita]
|
|
203
|
+
variables: []
|
|
204
|
+
|
|
205
|
+
- id: DocTitle
|
|
206
|
+
label_en: Document Title
|
|
207
|
+
label_zh: 文档标题
|
|
208
|
+
description: Title of a published document (notice, report, filing).
|
|
209
|
+
value_type: text
|
|
210
|
+
unit_type: none
|
|
211
|
+
dimensions: []
|
|
212
|
+
variables: []
|
|
213
|
+
|
|
214
|
+
- id: DocDate
|
|
215
|
+
label_en: Document Date
|
|
216
|
+
label_zh: 文档日期
|
|
217
|
+
description: Publication or effective date of a document.
|
|
218
|
+
value_type: date
|
|
219
|
+
unit_type: none
|
|
220
|
+
dimensions: []
|
|
221
|
+
variables: []
|
|
222
|
+
|
|
223
|
+
- id: DocUrl
|
|
224
|
+
label_en: Document URL
|
|
225
|
+
label_zh: 文档链接
|
|
226
|
+
description: Canonical web address of a published document.
|
|
227
|
+
value_type: text
|
|
228
|
+
unit_type: none
|
|
229
|
+
dimensions: []
|
|
230
|
+
variables: []
|
fd_open_data_protocol-0.3.0/fd_open_data_protocol/vocabulary/crosswalks/datacommons-worldbank.yaml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# FindData vocabulary — crosswalk assertions (Data Commons + World Bank).
|
|
2
|
+
#
|
|
3
|
+
# Each mapping asserts that a FindData Variable (identified by its five-field
|
|
4
|
+
# identity tuple) corresponds to a term in an external vocabulary, with a
|
|
5
|
+
# SKOS-style relation and a confidence. `import-crosswalks` ingests these into
|
|
6
|
+
# the MCP `concept_mappings` table with provenance `registry`.
|
|
7
|
+
#
|
|
8
|
+
# Relation types: exact | close | broader | narrower | related
|
|
9
|
+
# (skos:exactMatch / closeMatch / broadMatch / narrowMatch / relatedMatch).
|
|
10
|
+
#
|
|
11
|
+
# URIs for the mapped Variables themselves are Phase 4; this release carries
|
|
12
|
+
# only family URIs.
|
|
13
|
+
version: "2026.09"
|
|
14
|
+
kind: crosswalk
|
|
15
|
+
mappings:
|
|
16
|
+
# --- Population -------------------------------------------------------------
|
|
17
|
+
- code: population.total
|
|
18
|
+
entity_type: country
|
|
19
|
+
measure: ""
|
|
20
|
+
unit: persons
|
|
21
|
+
frequency: yearly
|
|
22
|
+
vocabulary: datacommons
|
|
23
|
+
term: Count_Person
|
|
24
|
+
relation: exact
|
|
25
|
+
confidence: 1.0
|
|
26
|
+
note: Data Commons' generic population StatisticalVariable.
|
|
27
|
+
|
|
28
|
+
- code: population.total
|
|
29
|
+
entity_type: country
|
|
30
|
+
measure: ""
|
|
31
|
+
unit: persons
|
|
32
|
+
frequency: yearly
|
|
33
|
+
vocabulary: worldbank
|
|
34
|
+
term: SP.POP.TOTL
|
|
35
|
+
relation: exact
|
|
36
|
+
confidence: 1.0
|
|
37
|
+
note: World Bank "Population, total".
|
|
38
|
+
|
|
39
|
+
# --- GDP --------------------------------------------------------------------
|
|
40
|
+
- code: gdp
|
|
41
|
+
entity_type: country
|
|
42
|
+
measure: nominal_current
|
|
43
|
+
unit: usd
|
|
44
|
+
frequency: yearly
|
|
45
|
+
vocabulary: worldbank
|
|
46
|
+
term: NY.GDP.MKTP.CD
|
|
47
|
+
relation: exact
|
|
48
|
+
confidence: 1.0
|
|
49
|
+
note: World Bank "GDP (current US$)" — the nominal current-USD variable only;
|
|
50
|
+
the real/PPP/per-capita variants are separate Variables with their own
|
|
51
|
+
external terms (NY.GDP.MKTP.KD, NY.GDP.PCAP.CD, NY.GDP.PCAP.PP.CD).
|