jstdata 0.2.0__py3-none-any.whl
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.
- jstdata/__init__.py +13 -0
- jstdata/agent_guide.py +530 -0
- jstdata/cli.py +682 -0
- jstdata/client.py +543 -0
- jstdata/models.py +161 -0
- jstdata/session.py +251 -0
- jstdata/utils.py +103 -0
- jstdata/workflows/__init__.py +88 -0
- jstdata/workflows/base.py +356 -0
- jstdata/workflows/bundled/__init__.py +1 -0
- jstdata/workflows/bundled/tutorial.yaml +14 -0
- jstdata/workflows/bundled/tutorial_script.yaml +45 -0
- jstdata/workflows/chart.py +357 -0
- jstdata/workflows/console.py +560 -0
- jstdata/workflows/discover.py +502 -0
- jstdata/workflows/export.py +171 -0
- jstdata/workflows/find.py +386 -0
- jstdata/workflows/host.py +255 -0
- jstdata/workflows/rank.py +658 -0
- jstdata/workflows/session_manager.py +277 -0
- jstdata/workflows/store.py +308 -0
- jstdata/workflows/tutorial.py +163 -0
- jstdata/workflows/tutorial_host.py +178 -0
- jstdata-0.2.0.dist-info/METADATA +227 -0
- jstdata-0.2.0.dist-info/RECORD +28 -0
- jstdata-0.2.0.dist-info/WHEEL +4 -0
- jstdata-0.2.0.dist-info/entry_points.txt +3 -0
- jstdata-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Interactive tutorial coach for the bundled ``tutorial`` workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from importlib.resources import files
|
|
7
|
+
from typing import Any, Callable, Mapping, Optional
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from ..client import JSTDataClient
|
|
12
|
+
from ..session import Session
|
|
13
|
+
from .base import default_session_path
|
|
14
|
+
from .export import ExportModal
|
|
15
|
+
from .store import (
|
|
16
|
+
TUTORIAL_WORKFLOW_ID,
|
|
17
|
+
load_workflow,
|
|
18
|
+
resolve_saved_workflow,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_SCRIPT_PATH = "tutorial_script.yaml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class TutorialPhase:
|
|
26
|
+
text: str
|
|
27
|
+
step_index: Optional[int] = None
|
|
28
|
+
until: Mapping[str, Any] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: dict[str, Any]) -> TutorialPhase:
|
|
32
|
+
until = data.get("until") or {}
|
|
33
|
+
if not isinstance(until, dict):
|
|
34
|
+
raise ValueError("Tutorial phase 'until' must be a mapping.")
|
|
35
|
+
step_index = data.get("step_index")
|
|
36
|
+
if step_index is not None:
|
|
37
|
+
step_index = int(step_index)
|
|
38
|
+
return cls(
|
|
39
|
+
text=str(data.get("text") or "").strip(),
|
|
40
|
+
step_index=step_index,
|
|
41
|
+
until=until,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class TutorialCoach:
|
|
47
|
+
phases: tuple[TutorialPhase, ...]
|
|
48
|
+
phase_index: int = 0
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def load(cls) -> TutorialCoach:
|
|
52
|
+
raw = files("jstdata.workflows.bundled").joinpath(_SCRIPT_PATH).read_text(
|
|
53
|
+
encoding="utf-8"
|
|
54
|
+
)
|
|
55
|
+
data = yaml.safe_load(raw) or {}
|
|
56
|
+
items = data.get("phases") or []
|
|
57
|
+
if not items:
|
|
58
|
+
raise ValueError("Tutorial script has no phases.")
|
|
59
|
+
return cls(phases=tuple(TutorialPhase.from_dict(p) for p in items))
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_complete(self) -> bool:
|
|
63
|
+
return self.phase_index >= len(self.phases)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def current_phase(self) -> Optional[TutorialPhase]:
|
|
67
|
+
if self.is_complete:
|
|
68
|
+
return None
|
|
69
|
+
return self.phases[self.phase_index]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def progress_label(self) -> str:
|
|
73
|
+
total = len(self.phases)
|
|
74
|
+
current = min(self.phase_index + 1, total)
|
|
75
|
+
return f"Step {current} / {total}"
|
|
76
|
+
|
|
77
|
+
def _phase_matches(
|
|
78
|
+
self,
|
|
79
|
+
phase: TutorialPhase,
|
|
80
|
+
session: Session,
|
|
81
|
+
step_index: int,
|
|
82
|
+
action: Optional[str],
|
|
83
|
+
) -> bool:
|
|
84
|
+
until = phase.until or {}
|
|
85
|
+
if until.get("done"):
|
|
86
|
+
return False
|
|
87
|
+
if until.get("auto"):
|
|
88
|
+
return True
|
|
89
|
+
if phase.step_index is not None and phase.step_index != step_index:
|
|
90
|
+
return False
|
|
91
|
+
if "session_entity_count" in until:
|
|
92
|
+
needed = int(until["session_entity_count"])
|
|
93
|
+
return len(session.entity) >= needed
|
|
94
|
+
if "session_metric_count" in until:
|
|
95
|
+
needed = int(until["session_metric_count"])
|
|
96
|
+
return len(session.metric) >= needed
|
|
97
|
+
if "action" in until:
|
|
98
|
+
return action == until["action"]
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def maybe_advance(
|
|
102
|
+
self,
|
|
103
|
+
session: Session,
|
|
104
|
+
step_index: int,
|
|
105
|
+
action: Optional[str] = None,
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""Advance when the current phase's completion rule is satisfied."""
|
|
108
|
+
if self.is_complete:
|
|
109
|
+
return False
|
|
110
|
+
phase = self.current_phase
|
|
111
|
+
assert phase is not None
|
|
112
|
+
if not self._phase_matches(phase, session, step_index, action):
|
|
113
|
+
return False
|
|
114
|
+
self.phase_index += 1
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
def gate_progress(self, session: Session, step_index: int) -> bool:
|
|
118
|
+
"""True when navigation should be blocked (incomplete count-based phase)."""
|
|
119
|
+
phase = self.current_phase
|
|
120
|
+
if phase is None or self.is_complete:
|
|
121
|
+
return False
|
|
122
|
+
if phase.until.get("action") == "next_step":
|
|
123
|
+
return False
|
|
124
|
+
if phase.step_index is not None and phase.step_index != step_index:
|
|
125
|
+
return False
|
|
126
|
+
until = phase.until or {}
|
|
127
|
+
if "session_entity_count" in until:
|
|
128
|
+
return len(session.entity) < int(until["session_entity_count"])
|
|
129
|
+
if "session_metric_count" in until:
|
|
130
|
+
return len(session.metric) < int(until["session_metric_count"])
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TutorialCoachBar:
|
|
135
|
+
"""Mixin-friendly helper; actual widget lives in tutorial_host.py."""
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def format_header(progress: str) -> str:
|
|
139
|
+
return f"TUTORIAL · {progress}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def run_tutorial(
|
|
143
|
+
client: JSTDataClient,
|
|
144
|
+
*,
|
|
145
|
+
session_path: str | None = None,
|
|
146
|
+
output_path: str | None = None,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Load bundled tutorial workflow and launch the coached host."""
|
|
149
|
+
from .tutorial_host import TutorialHost
|
|
150
|
+
|
|
151
|
+
workflow = load_workflow(TUTORIAL_WORKFLOW_ID)
|
|
152
|
+
resolved = resolve_saved_workflow(workflow)
|
|
153
|
+
coach = TutorialCoach.load()
|
|
154
|
+
prefix = "-".join(s.spec.id for s in resolved)
|
|
155
|
+
out = output_path or default_session_path(prefix)
|
|
156
|
+
app = TutorialHost(
|
|
157
|
+
client,
|
|
158
|
+
resolved,
|
|
159
|
+
coach,
|
|
160
|
+
session_path=session_path,
|
|
161
|
+
output_path=out,
|
|
162
|
+
)
|
|
163
|
+
app.run()
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Tutorial host: WorkflowHost plus a persistent coach bar."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.screen import Screen
|
|
7
|
+
from textual.widgets import Label, Static
|
|
8
|
+
|
|
9
|
+
from .export import ExportModal
|
|
10
|
+
from .host import WorkflowHost
|
|
11
|
+
from .tutorial import TutorialCoach, TutorialCoachBar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CoachBarWidget(Static):
|
|
15
|
+
"""Docked instruction strip shown for the whole tutorial run."""
|
|
16
|
+
|
|
17
|
+
DEFAULT_CSS = """
|
|
18
|
+
CoachBarWidget {
|
|
19
|
+
dock: top;
|
|
20
|
+
height: auto;
|
|
21
|
+
min-height: 4;
|
|
22
|
+
max-height: 8;
|
|
23
|
+
background: #1a1a2e;
|
|
24
|
+
border-bottom: thick #38bdf8;
|
|
25
|
+
padding: 0 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#coach-header {
|
|
29
|
+
color: #38bdf8;
|
|
30
|
+
text-style: bold;
|
|
31
|
+
height: 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#coach-body {
|
|
35
|
+
color: #e0e0e0;
|
|
36
|
+
height: auto;
|
|
37
|
+
padding-bottom: 1;
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, header: str = "", body: str = "") -> None:
|
|
42
|
+
super().__init__()
|
|
43
|
+
self._header = header
|
|
44
|
+
self._body = body
|
|
45
|
+
|
|
46
|
+
def compose(self) -> ComposeResult:
|
|
47
|
+
yield Label(self._header, id="coach-header")
|
|
48
|
+
yield Static(self._body, id="coach-body")
|
|
49
|
+
|
|
50
|
+
def update_coach(self, header: str, body: str) -> None:
|
|
51
|
+
if not self.is_mounted:
|
|
52
|
+
self._header = header
|
|
53
|
+
self._body = body
|
|
54
|
+
return
|
|
55
|
+
self.query_one("#coach-header", Label).update(header)
|
|
56
|
+
self.query_one("#coach-body", Static).update(body)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TutorialHost(WorkflowHost):
|
|
60
|
+
"""WorkflowHost with phased on-screen guidance for the bundled tutorial."""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
client,
|
|
65
|
+
steps,
|
|
66
|
+
coach: TutorialCoach,
|
|
67
|
+
session_path: str | None = None,
|
|
68
|
+
output_path: str | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
super().__init__(client, steps, session_path=session_path, output_path=output_path)
|
|
71
|
+
self.coach = coach
|
|
72
|
+
self._coach_bar: CoachBarWidget | None = None
|
|
73
|
+
|
|
74
|
+
def on_mount(self) -> None:
|
|
75
|
+
self.coach.maybe_advance(self.session, self.step_index, None)
|
|
76
|
+
self.set_interval(0.25, self._poll_coach)
|
|
77
|
+
self._push_current_step()
|
|
78
|
+
|
|
79
|
+
def _coach_text(self) -> tuple[str, str]:
|
|
80
|
+
phase = self.coach.current_phase
|
|
81
|
+
if phase is None:
|
|
82
|
+
return (
|
|
83
|
+
"TUTORIAL · complete",
|
|
84
|
+
"Tutorial complete. Press q to quit.",
|
|
85
|
+
)
|
|
86
|
+
return (
|
|
87
|
+
TutorialCoachBar.format_header(self.coach.progress_label),
|
|
88
|
+
phase.text,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _poll_coach(self) -> None:
|
|
92
|
+
if self.coach.maybe_advance(self.session, self.step_index, None):
|
|
93
|
+
self._refresh_coach()
|
|
94
|
+
|
|
95
|
+
def _refresh_coach(self) -> None:
|
|
96
|
+
if self._coach_bar is None or not self._coach_bar.is_mounted:
|
|
97
|
+
return
|
|
98
|
+
header, body = self._coach_text()
|
|
99
|
+
self._coach_bar.update_coach(header, body)
|
|
100
|
+
|
|
101
|
+
def _push_current_step(self) -> None:
|
|
102
|
+
self.push_screen(self._make_current_screen())
|
|
103
|
+
self.call_after_refresh(self._mount_coach_on_active_screen)
|
|
104
|
+
|
|
105
|
+
def _mount_coach_on_active_screen(self) -> None:
|
|
106
|
+
"""Mount coach on the active step screen (App-level mounts sit behind Screens)."""
|
|
107
|
+
screen = self.screen
|
|
108
|
+
if not isinstance(screen, Screen):
|
|
109
|
+
return
|
|
110
|
+
if self._coach_bar is not None and self._coach_bar.is_mounted:
|
|
111
|
+
self._coach_bar.remove()
|
|
112
|
+
header, body = self._coach_text()
|
|
113
|
+
self._coach_bar = CoachBarWidget(header, body)
|
|
114
|
+
screen.mount(self._coach_bar)
|
|
115
|
+
|
|
116
|
+
def _after_step_change(self) -> None:
|
|
117
|
+
self.call_after_refresh(self._mount_coach_on_active_screen)
|
|
118
|
+
|
|
119
|
+
def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
|
|
120
|
+
phase = self.coach.current_phase
|
|
121
|
+
if (
|
|
122
|
+
action == "next_step"
|
|
123
|
+
and phase is not None
|
|
124
|
+
and phase.until.get("action") == "next_step"
|
|
125
|
+
):
|
|
126
|
+
return True
|
|
127
|
+
return super().check_action(action, parameters)
|
|
128
|
+
|
|
129
|
+
def action_next_step(self) -> None:
|
|
130
|
+
phase = self.coach.current_phase
|
|
131
|
+
if phase is not None and not self.coach.is_complete:
|
|
132
|
+
if phase.until.get("action") != "next_step":
|
|
133
|
+
if self.coach.gate_progress(self.session, self.step_index):
|
|
134
|
+
self.notify(
|
|
135
|
+
"Finish the current tutorial step first.",
|
|
136
|
+
severity="warning",
|
|
137
|
+
)
|
|
138
|
+
return
|
|
139
|
+
if self.step_index >= len(self.steps) - 1:
|
|
140
|
+
self.notify("Last step in the pipeline", severity="warning")
|
|
141
|
+
return
|
|
142
|
+
advanced = self.coach.maybe_advance(
|
|
143
|
+
self.session, self.step_index, "next_step"
|
|
144
|
+
)
|
|
145
|
+
self._pop_overlays()
|
|
146
|
+
self.step_index += 1
|
|
147
|
+
self.switch_screen(self._make_current_screen())
|
|
148
|
+
self._after_step_change()
|
|
149
|
+
if advanced:
|
|
150
|
+
self._refresh_coach()
|
|
151
|
+
|
|
152
|
+
def action_prev_step(self) -> None:
|
|
153
|
+
if self.step_index <= 0:
|
|
154
|
+
self.notify("First step in the pipeline", severity="warning")
|
|
155
|
+
return
|
|
156
|
+
self._pop_overlays()
|
|
157
|
+
self.step_index -= 1
|
|
158
|
+
self.switch_screen(self._make_current_screen())
|
|
159
|
+
self._after_step_change()
|
|
160
|
+
|
|
161
|
+
def action_session(self) -> None:
|
|
162
|
+
super().action_session()
|
|
163
|
+
if self.coach.maybe_advance(self.session, self.step_index, "open_session"):
|
|
164
|
+
self._refresh_coach()
|
|
165
|
+
|
|
166
|
+
def action_export(self) -> None:
|
|
167
|
+
self.push_screen(
|
|
168
|
+
ExportModal(
|
|
169
|
+
self.session,
|
|
170
|
+
default_path=self.output_path,
|
|
171
|
+
on_write=self._on_tutorial_session_written,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _on_tutorial_session_written(self, path: str) -> None:
|
|
176
|
+
if self.coach.maybe_advance(self.session, self.step_index, "session_written"):
|
|
177
|
+
self._refresh_coach()
|
|
178
|
+
self.notify(f"Tutorial complete — session saved to {path}")
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jstdata
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python API, CLI, and interactive workflows for Jefferson Street financial and economic data.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: api,cli,data,jefferson street,economics,finance
|
|
8
|
+
Author: Mahfuj Ahbab
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Dist: click (>=8.2.1,<9.0.0)
|
|
21
|
+
Requires-Dist: pandas (>=2.3.1,<3.0.0)
|
|
22
|
+
Requires-Dist: pyperclip (>=1.9.0,<2.0.0)
|
|
23
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
24
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
25
|
+
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
26
|
+
Requires-Dist: textual (>=1.0.0,<2.0.0)
|
|
27
|
+
Project-URL: Homepage, https://github.com/Jefferson-Street-Technologies/jstdata
|
|
28
|
+
Project-URL: Repository, https://github.com/Jefferson-Street-Technologies/jstdata
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Jefferson Street Data (jstdata)
|
|
32
|
+
|
|
33
|
+
A Python interface and Research OS for the Jefferson Street financial and economic data API.
|
|
34
|
+
|
|
35
|
+
## Design Philosophy: From Filing Cabinet to Knowledge Graph
|
|
36
|
+
|
|
37
|
+
`jstdata` is built to minimize the friction between human thought and actionable data.
|
|
38
|
+
|
|
39
|
+
- **Series-First**: The individual data series is the primary resource, carrying its own metadata (frequency, units, source).
|
|
40
|
+
- **Entity Graph**: Entities are nodes in a relational graph — walk from a company to its SIC, a security ticker, or a geography.
|
|
41
|
+
- **Taxonomies**: Named populations (`country`, `sec-central-index-key`, …) scope search, query, and ranking.
|
|
42
|
+
- **Intent over IDs**: CLI and Python favor fuzzy search over memorizing slugs; always resolve before querying.
|
|
43
|
+
- **Sessions & workflows**: Stage resolved ids in a session JSON; compose interactive steps in the shell and save them as named workflows.
|
|
44
|
+
- **Reproducible Discovery**: High-speed discovery in the TUI transitions into immutable Python / CLI snippets.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Using poetry
|
|
52
|
+
poetry install
|
|
53
|
+
|
|
54
|
+
# Manual install
|
|
55
|
+
pip install .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication & Configuration
|
|
59
|
+
|
|
60
|
+
### Quick Start (CLI)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
jst login
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Credentials are saved to `~/.jstdata/config.json` with secure permissions.
|
|
67
|
+
|
|
68
|
+
### Environment Variables
|
|
69
|
+
|
|
70
|
+
These take precedence over the config file:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export JSTDATA_API_KEY="your-api-key-here"
|
|
74
|
+
# Optional: export JSTDATA_BASE_URL="https://api.jeffersonst.io"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Managing Configuration
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
jst config show
|
|
81
|
+
jst config show --verbose
|
|
82
|
+
jst config set api_key XXX
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Interactive investigation (`jst run`)
|
|
86
|
+
|
|
87
|
+
Compose TUI **steps** in the shell. Steps share one **Session** (staged metrics /
|
|
88
|
+
entities / series + query filters) for the lifetime of the run.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Catalog search → stage entities/metrics/series
|
|
92
|
+
jst run console
|
|
93
|
+
|
|
94
|
+
# Scope to a taxonomy and resource type
|
|
95
|
+
jst run console --taxonomy sec-central-index-key --resource-type entity
|
|
96
|
+
|
|
97
|
+
# Filter entities by typed graph edges (repeatable; OR'd)
|
|
98
|
+
jst run console --relation classified_as:sic:7372 --resource-type entity
|
|
99
|
+
|
|
100
|
+
# Chain steps
|
|
101
|
+
jst run console --taxonomy country : discover --mode union : rank --taxonomy country
|
|
102
|
+
|
|
103
|
+
# Preload a session JSON; set default export path
|
|
104
|
+
jst run --session labor.json --output out.json rank --taxonomy country
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Built-in steps:
|
|
108
|
+
|
|
109
|
+
| Step | Role |
|
|
110
|
+
|------|------|
|
|
111
|
+
| `console` | Search catalog; stage into the session |
|
|
112
|
+
| `discover` | Find metrics for session entities; preview coverage |
|
|
113
|
+
| `rank` | Leaderboard entities for a session metric |
|
|
114
|
+
|
|
115
|
+
Host keys (every step): `s` session · `f` find · `e` export · `n`/`p` next/prev · `?` step help · `q` quit.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
jst steps
|
|
119
|
+
jst step console
|
|
120
|
+
jst step rank --json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Saved workflows
|
|
124
|
+
|
|
125
|
+
Persist step pipelines (not session contents) under `~/.jstdata/workflows/`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
jst workflow create --id gdp-rank --description "GDP board" -- \
|
|
129
|
+
console --taxonomy country : rank --taxonomy country
|
|
130
|
+
|
|
131
|
+
jst workflow ls
|
|
132
|
+
jst workflow run gdp-rank --session labor.json
|
|
133
|
+
jst workflow rm gdp-rank
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
(`jst workflows` remains a compatibility alias.)
|
|
137
|
+
|
|
138
|
+
### Tutorial
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
jst tutorial
|
|
142
|
+
# same as: jst workflow run tutorial
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Agents
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
jst agent-guide
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Prints a version-tied operating manual (hard rules, modes, recipes, and the live
|
|
152
|
+
CLI/step reference). Prefer it over guessing command shapes or inventing ids.
|
|
153
|
+
|
|
154
|
+
## The Scriptable CLI (`jst`)
|
|
155
|
+
|
|
156
|
+
Pipe-friendly commands for automation and quick extraction.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Fuzzy / set search (omit QUERY to list the matching set)
|
|
160
|
+
jst metric search "defense spending" --taxonomy country --limit 20 --format json
|
|
161
|
+
jst entity search --relation classified_as:sic:3674 --limit 50 --format json
|
|
162
|
+
jst entity search --metric gdp --metric cpi --mode intersect --format json
|
|
163
|
+
|
|
164
|
+
# Taxonomies
|
|
165
|
+
jst taxonomy ls --format json
|
|
166
|
+
jst taxonomy entities country --limit 20 --format json
|
|
167
|
+
|
|
168
|
+
# Bounded cross-sectional query (head/tail per series)
|
|
169
|
+
jst query --metric inflation --entity "United States" --frequency Monthly --tail 20
|
|
170
|
+
jst query --metric gdp --taxonomy country --tail 1 --sort-by value --limit 50 --format json
|
|
171
|
+
|
|
172
|
+
# Deep history for one known series
|
|
173
|
+
jst series observations ABC123 --start-date 2000-01-01 --limit 1000
|
|
174
|
+
|
|
175
|
+
# Entity graph
|
|
176
|
+
jst entity relations cik:1045810 --format pretty
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## The Python API
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from jstdata import JSTDataClient, Session
|
|
183
|
+
|
|
184
|
+
client = JSTDataClient()
|
|
185
|
+
|
|
186
|
+
# Cross-sectional observations (bounded per series)
|
|
187
|
+
df = client.query_df(
|
|
188
|
+
metric="gross-domestic-product",
|
|
189
|
+
entity=["usa", "gbr"],
|
|
190
|
+
tail=20,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Rank a taxonomy population by latest value
|
|
194
|
+
top = client.query(
|
|
195
|
+
metric="gross-domestic-product",
|
|
196
|
+
taxonomy="country",
|
|
197
|
+
frequency="Annual",
|
|
198
|
+
tail=1,
|
|
199
|
+
sort_by="value",
|
|
200
|
+
limit=50,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Relation-scoped entity search (OR when multiple)
|
|
204
|
+
entities = client.search_entities(
|
|
205
|
+
relation=["classified_as:sic:7372", "classified_as:sic:5961"],
|
|
206
|
+
taxonomy="sec-central-index-key",
|
|
207
|
+
limit=50,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Deep history for one series
|
|
211
|
+
obs = client.get_series_observations("ABC123", start_date="2000-01-01")
|
|
212
|
+
|
|
213
|
+
# Stage intent for a later `jst run` / `jst workflow run`
|
|
214
|
+
session = Session(
|
|
215
|
+
metric=["gross-domestic-product"],
|
|
216
|
+
taxonomy="country",
|
|
217
|
+
tail=1,
|
|
218
|
+
sort_by="value",
|
|
219
|
+
)
|
|
220
|
+
session.save("labor.json")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Extending the product
|
|
224
|
+
|
|
225
|
+
See [AGENTS.md](AGENTS.md) for how steps, the host, and saved workflows are
|
|
226
|
+
structured when adding new interactive surface area.
|
|
227
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
jstdata/__init__.py,sha256=HQVrTOLDbVmhAZVlxPNuysSwp7h02gipLPaPmzCmFPg,257
|
|
2
|
+
jstdata/agent_guide.py,sha256=i4MXf0ra16Do98IWeq2YJ0Tdyd__klCBQGDunPh6sQ4,18539
|
|
3
|
+
jstdata/cli.py,sha256=PeZwk8GZEPHQ-fveA31LSZEKcZS6Bs0DqGGZ9I-sXOU,20349
|
|
4
|
+
jstdata/client.py,sha256=U2HlIsSddLAeRCM2KDhZ9scA73sPP1hB6wfTXG_82Fs,18906
|
|
5
|
+
jstdata/models.py,sha256=bbipJxITf6K_5N2rVdv01QaxJp_8Y--ud92drhKJkLg,4656
|
|
6
|
+
jstdata/session.py,sha256=myRmghmSyZ98Co0tJ2MmO8AMo5HNyoe7OpCK9MLCMZI,8874
|
|
7
|
+
jstdata/utils.py,sha256=ge9nPasdCaJyEgEdH9z9-uZ1wmR-Fur5z798qcLOQLs,3198
|
|
8
|
+
jstdata/workflows/__init__.py,sha256=QVFkujbTsDqqcubbm7s9-fwoxeJllVxOuH_PZ_aZWEE,2033
|
|
9
|
+
jstdata/workflows/base.py,sha256=UvLRA6wDQdYyYZNqqFzhNRo-YK4D2vEiCJUA89Ks6NA,10650
|
|
10
|
+
jstdata/workflows/bundled/__init__.py,sha256=f4FveHIFSbteKjgLqhgnEwpheuigMolIudZqzSdX8YI,58
|
|
11
|
+
jstdata/workflows/bundled/tutorial.yaml,sha256=ECaWCXPvnD2PcHefJHRKsgMuyMyCJAp1bVs8yTClt2c,356
|
|
12
|
+
jstdata/workflows/bundled/tutorial_script.yaml,sha256=DVRtJnmRIAQrgIa2ZqPcwPTyx3TIOJgS4IO4yQsnxAw,952
|
|
13
|
+
jstdata/workflows/chart.py,sha256=jbXt-vuDfrH17zMUGZdYPBnyUk6qMoufufwISaR4fJE,11218
|
|
14
|
+
jstdata/workflows/console.py,sha256=8GkLr9Gp1veTF8LBEkgW7v9PjNxxMzt2_2HI54sWhnQ,18845
|
|
15
|
+
jstdata/workflows/discover.py,sha256=eUWIDf_4vlEsag8B9cEB2HCv5_KmOVlSN5RbEKScSVA,17359
|
|
16
|
+
jstdata/workflows/export.py,sha256=SvovNEf8vRHEUa-reI4YJg5S87gcelukd28b0oAlQrw,4879
|
|
17
|
+
jstdata/workflows/find.py,sha256=aKFlHjItbpB3gAPOdOxBV3GbL--ZSUBq-DjuPFf3Qzg,12104
|
|
18
|
+
jstdata/workflows/host.py,sha256=WfrXv2TIK96lZtkoqDkXjgHe__wK-mtQwkeYu5DWU0w,7772
|
|
19
|
+
jstdata/workflows/rank.py,sha256=vjK4dX1wOriwKUEuO9OWgucXRvMqo9iUzEFzaVPFlFM,22342
|
|
20
|
+
jstdata/workflows/session_manager.py,sha256=HBJfUkKSiKWTJhWxeOi12nPf8ToDnpsHwaHqApWEjbA,8527
|
|
21
|
+
jstdata/workflows/store.py,sha256=iFhaXrC0s5rIFNQDoMrHAYSqhBKS9X6tQtVqzZwDgkk,10531
|
|
22
|
+
jstdata/workflows/tutorial.py,sha256=w2ht5-wpOzUB-YnnbKU68SuHD_PdDBE_pBQn8zL-QK4,5025
|
|
23
|
+
jstdata/workflows/tutorial_host.py,sha256=_b3ycgrZpAhFcUYPQnnmtVZyDfmpP9oUdj4BL3rCHLk,5902
|
|
24
|
+
jstdata-0.2.0.dist-info/METADATA,sha256=fYJl31donCLLoqq8MHsnA94M45ITcte6AbhHyP0JrbE,6679
|
|
25
|
+
jstdata-0.2.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
|
|
26
|
+
jstdata-0.2.0.dist-info/entry_points.txt,sha256=LiXk2o_qORp1dSOntBHUmOm4h1RKB4PFCPVnpw6V69E,39
|
|
27
|
+
jstdata-0.2.0.dist-info/licenses/LICENSE,sha256=6GgMUDMJtgsxaYAeTpyCFH6t6bNVNVUQsgkUKLQxyoo,1068
|
|
28
|
+
jstdata-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mahfuj Ahbab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT of OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|