context-handoff 0.1.1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yauhen Bichel
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.
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: context-handoff
3
+ Version: 0.1.1
4
+ Summary: Keep Claude Code sessions cheap: meter cost, write a handoff note, resume after /clear
5
+ Author: Yauhen Bichel
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YauhenBichel/context-handoff
8
+ Project-URL: Changelog, https://github.com/YauhenBichel/context-handoff/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/YauhenBichel/context-handoff/issues
10
+ Keywords: claude,claude-code,llm,cost,context,handoff
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: license-file
21
+
22
+ # context-handoff
23
+
24
+ [![ci](https://github.com/YauhenBichel/context-handoff/actions/workflows/ci.yml/badge.svg)](https://github.com/YauhenBichel/context-handoff/actions/workflows/ci.yml)
25
+ [![release](https://img.shields.io/github/v/release/YauhenBichel/context-handoff)](https://github.com/YauhenBichel/context-handoff/releases)
26
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
+
28
+ A Claude Code plugin that keeps long sessions from getting expensive: it shows
29
+ what each turn costs, writes a handoff note when the session grows, and resumes
30
+ from it after `/clear`.
31
+
32
+ ```
33
+ /plugin marketplace add YauhenBichel/context-handoff
34
+ /plugin install context-handoff@context-handoff
35
+ ```
36
+
37
+ Python 3.10+ as `python3`, nothing else. Details in [Install](#install).
38
+
39
+ Every Claude Code turn re-sends the whole conversation. While the prompt cache
40
+ is warm that re-read is cheap; after a pause the whole context is written to
41
+ the cache again at up to 2× the input price. A 400K-token session on Claude
42
+ Opus 5.5 pays about $3.20 for one such cold turn, before Claude writes a word.
43
+ Long sessions also get slower and lose focus.
44
+
45
+ context-handoff watches what each turn costs. When a session gets too big or
46
+ too expensive, Claude writes a short handoff note at the end of the current
47
+ task. You run `/clear`, type `go`, and a fresh, small session continues from
48
+ the note.
49
+
50
+ What you see above a prompt once a session is past the warn limit:
51
+
52
+ ```
53
+ [context-handoff] 182K context, last turn $0.61, session $14.20, a cold turn would cost $1.46
54
+ (claude-opus-5-5 prices). Claude will write a handoff note after this task; then /clear and type go.
55
+ ```
56
+
57
+ ## How it works
58
+
59
+ 1. **Meter** (`UserPromptSubmit` hook). Before each prompt it reads the
60
+ session transcript and computes, at API list prices:
61
+ - the context the next turn will re-send,
62
+ - what the last turn cost,
63
+ - what the whole session cost,
64
+ - what one cold turn would cost now.
65
+ 2. **Auto handoff**. Over a warn limit, it asks Claude (once per session) to
66
+ write a handoff note when the current task is done.
67
+ 3. **Block**. Over a block limit, it holds the prompt until you type `handoff`
68
+ or add `#override`.
69
+ 4. **Resume** (`SessionStart` hook, after `/clear`). It loads the project's
70
+ newest unused note into the new session, so you type `go` instead of
71
+ pasting a prompt. Each note is loaded once.
72
+
73
+ ## Install
74
+
75
+ In Claude Code:
76
+
77
+ ```
78
+ /plugin marketplace add YauhenBichel/context-handoff
79
+ /plugin install context-handoff@context-handoff
80
+ ```
81
+
82
+ Or from a terminal:
83
+
84
+ ```
85
+ claude plugin marketplace add YauhenBichel/context-handoff
86
+ claude plugin install context-handoff@context-handoff
87
+ ```
88
+
89
+ Restart Claude Code (or start a new session) to load the hooks. The meter
90
+ line appears from the second prompt of a session.
91
+
92
+ Requires Python 3.10 or newer on `PATH` as `python3`. No other dependencies.
93
+
94
+ ### Without Claude Code plugins
95
+
96
+ The same code is on PyPI. It gives the `context-handoff` command for reports
97
+ and checks, for example in a shell or on a machine where you don't use the
98
+ plugin:
99
+
100
+ ```
101
+ pipx install context-handoff # or: uv tool install context-handoff
102
+ context-handoff report 14
103
+ ```
104
+
105
+ The hooks still come from the plugin above; the package does not install them.
106
+
107
+ Update or remove:
108
+
109
+ ```
110
+ claude plugin marketplace update context-handoff
111
+ claude plugin update context-handoff@context-handoff
112
+ claude plugin uninstall context-handoff@context-handoff
113
+ ```
114
+
115
+ ## Use
116
+
117
+ - **Normal flow**: keep working. When the meter asks, Claude writes the note
118
+ after the current task and tells you. Run `/clear`, then type `go`.
119
+ - **By hand**: `/context-handoff:handoff`, then `/clear` and `go`.
120
+ - **Past a block**: start the prompt with `handoff`, or add `#override`.
121
+ - **Check a session**: `python3 bin/context-handoff check <transcript.jsonl>`.
122
+ - **Report** of the last N days (default 7):
123
+ `python3 bin/context-handoff report 14`.
124
+
125
+ ## Settings
126
+
127
+ Defaults, then `~/.claude/context-handoff.json`, then environment variables
128
+ (`CONTEXT_HANDOFF_` + the key in upper case). A limit of 0 turns it off.
129
+
130
+ | Key | Default | Meaning |
131
+ |---|---|---|
132
+ | `warn_k` | 150 | context size (thousands of tokens) that triggers a handoff |
133
+ | `block_k` | 400 | context size that blocks prompts |
134
+ | `warn_turn_usd` | 0.50 | cost of the last turn that triggers a handoff |
135
+ | `block_turn_usd` | 3.00 | cost of the last turn that blocks prompts |
136
+ | `warn_session_usd` | 20 | session total that triggers a handoff |
137
+ | `auto_handoff` | true | Claude writes the note itself; false = only a warning |
138
+ | `max_age_min` | 180 | notes older than this are not resumed |
139
+ | `handoff_dir` | `~/.claude/handoffs` | where notes go |
140
+ | `state_dir` | `~/.claude/context-handoff` | log (`meter.jsonl`) and per-session markers |
141
+ | `prices` | see [prices.py](src/context_handoff/prices.py) | `{"model-prefix": [input, output, cache_read]}` per million tokens |
142
+
143
+ Example `~/.claude/context-handoff.json`:
144
+
145
+ ```json
146
+ { "warn_turn_usd": 1.0, "warn_session_usd": 50, "prices": { "claude-opus-5-5": [4.0, 20.0, 0.20] } }
147
+ ```
148
+
149
+ ## Prices
150
+
151
+ The default table holds Anthropic's first-party API list prices as of November
152
+ 2026. Cache writes are priced at 1.25× input (5-minute TTL) or 2× input (1-hour
153
+ TTL). On a Claude subscription you do not pay per token, but the same numbers
154
+ show how fast a session uses your limits. Prices change: override them in the
155
+ config file.
156
+
157
+ ## Limits
158
+
159
+ - A plugin cannot run `/clear` for you. That one step stays manual.
160
+ - Notes are matched by the project folder's name, and only those newer than
161
+ `max_age_min` are loaded.
162
+ - The meter reads the transcript, so the first prompt of a session is not
163
+ metered.
164
+ - Costs are estimates from the transcript's usage fields, not your bill.
165
+
166
+ ## Privacy
167
+
168
+ Everything runs locally. The plugin reads your transcripts in `~/.claude` and
169
+ writes notes and a log there. Nothing is sent anywhere. Do not put secrets in
170
+ handoff notes.
171
+
172
+ ## Development
173
+
174
+ ```
175
+ python3 -m unittest discover -s tests
176
+ ```
177
+
178
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
179
+
180
+ ## License
181
+
182
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,161 @@
1
+ # context-handoff
2
+
3
+ [![ci](https://github.com/YauhenBichel/context-handoff/actions/workflows/ci.yml/badge.svg)](https://github.com/YauhenBichel/context-handoff/actions/workflows/ci.yml)
4
+ [![release](https://img.shields.io/github/v/release/YauhenBichel/context-handoff)](https://github.com/YauhenBichel/context-handoff/releases)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ A Claude Code plugin that keeps long sessions from getting expensive: it shows
8
+ what each turn costs, writes a handoff note when the session grows, and resumes
9
+ from it after `/clear`.
10
+
11
+ ```
12
+ /plugin marketplace add YauhenBichel/context-handoff
13
+ /plugin install context-handoff@context-handoff
14
+ ```
15
+
16
+ Python 3.10+ as `python3`, nothing else. Details in [Install](#install).
17
+
18
+ Every Claude Code turn re-sends the whole conversation. While the prompt cache
19
+ is warm that re-read is cheap; after a pause the whole context is written to
20
+ the cache again at up to 2× the input price. A 400K-token session on Claude
21
+ Opus 5.5 pays about $3.20 for one such cold turn, before Claude writes a word.
22
+ Long sessions also get slower and lose focus.
23
+
24
+ context-handoff watches what each turn costs. When a session gets too big or
25
+ too expensive, Claude writes a short handoff note at the end of the current
26
+ task. You run `/clear`, type `go`, and a fresh, small session continues from
27
+ the note.
28
+
29
+ What you see above a prompt once a session is past the warn limit:
30
+
31
+ ```
32
+ [context-handoff] 182K context, last turn $0.61, session $14.20, a cold turn would cost $1.46
33
+ (claude-opus-5-5 prices). Claude will write a handoff note after this task; then /clear and type go.
34
+ ```
35
+
36
+ ## How it works
37
+
38
+ 1. **Meter** (`UserPromptSubmit` hook). Before each prompt it reads the
39
+ session transcript and computes, at API list prices:
40
+ - the context the next turn will re-send,
41
+ - what the last turn cost,
42
+ - what the whole session cost,
43
+ - what one cold turn would cost now.
44
+ 2. **Auto handoff**. Over a warn limit, it asks Claude (once per session) to
45
+ write a handoff note when the current task is done.
46
+ 3. **Block**. Over a block limit, it holds the prompt until you type `handoff`
47
+ or add `#override`.
48
+ 4. **Resume** (`SessionStart` hook, after `/clear`). It loads the project's
49
+ newest unused note into the new session, so you type `go` instead of
50
+ pasting a prompt. Each note is loaded once.
51
+
52
+ ## Install
53
+
54
+ In Claude Code:
55
+
56
+ ```
57
+ /plugin marketplace add YauhenBichel/context-handoff
58
+ /plugin install context-handoff@context-handoff
59
+ ```
60
+
61
+ Or from a terminal:
62
+
63
+ ```
64
+ claude plugin marketplace add YauhenBichel/context-handoff
65
+ claude plugin install context-handoff@context-handoff
66
+ ```
67
+
68
+ Restart Claude Code (or start a new session) to load the hooks. The meter
69
+ line appears from the second prompt of a session.
70
+
71
+ Requires Python 3.10 or newer on `PATH` as `python3`. No other dependencies.
72
+
73
+ ### Without Claude Code plugins
74
+
75
+ The same code is on PyPI. It gives the `context-handoff` command for reports
76
+ and checks, for example in a shell or on a machine where you don't use the
77
+ plugin:
78
+
79
+ ```
80
+ pipx install context-handoff # or: uv tool install context-handoff
81
+ context-handoff report 14
82
+ ```
83
+
84
+ The hooks still come from the plugin above; the package does not install them.
85
+
86
+ Update or remove:
87
+
88
+ ```
89
+ claude plugin marketplace update context-handoff
90
+ claude plugin update context-handoff@context-handoff
91
+ claude plugin uninstall context-handoff@context-handoff
92
+ ```
93
+
94
+ ## Use
95
+
96
+ - **Normal flow**: keep working. When the meter asks, Claude writes the note
97
+ after the current task and tells you. Run `/clear`, then type `go`.
98
+ - **By hand**: `/context-handoff:handoff`, then `/clear` and `go`.
99
+ - **Past a block**: start the prompt with `handoff`, or add `#override`.
100
+ - **Check a session**: `python3 bin/context-handoff check <transcript.jsonl>`.
101
+ - **Report** of the last N days (default 7):
102
+ `python3 bin/context-handoff report 14`.
103
+
104
+ ## Settings
105
+
106
+ Defaults, then `~/.claude/context-handoff.json`, then environment variables
107
+ (`CONTEXT_HANDOFF_` + the key in upper case). A limit of 0 turns it off.
108
+
109
+ | Key | Default | Meaning |
110
+ |---|---|---|
111
+ | `warn_k` | 150 | context size (thousands of tokens) that triggers a handoff |
112
+ | `block_k` | 400 | context size that blocks prompts |
113
+ | `warn_turn_usd` | 0.50 | cost of the last turn that triggers a handoff |
114
+ | `block_turn_usd` | 3.00 | cost of the last turn that blocks prompts |
115
+ | `warn_session_usd` | 20 | session total that triggers a handoff |
116
+ | `auto_handoff` | true | Claude writes the note itself; false = only a warning |
117
+ | `max_age_min` | 180 | notes older than this are not resumed |
118
+ | `handoff_dir` | `~/.claude/handoffs` | where notes go |
119
+ | `state_dir` | `~/.claude/context-handoff` | log (`meter.jsonl`) and per-session markers |
120
+ | `prices` | see [prices.py](src/context_handoff/prices.py) | `{"model-prefix": [input, output, cache_read]}` per million tokens |
121
+
122
+ Example `~/.claude/context-handoff.json`:
123
+
124
+ ```json
125
+ { "warn_turn_usd": 1.0, "warn_session_usd": 50, "prices": { "claude-opus-5-5": [4.0, 20.0, 0.20] } }
126
+ ```
127
+
128
+ ## Prices
129
+
130
+ The default table holds Anthropic's first-party API list prices as of November
131
+ 2026. Cache writes are priced at 1.25× input (5-minute TTL) or 2× input (1-hour
132
+ TTL). On a Claude subscription you do not pay per token, but the same numbers
133
+ show how fast a session uses your limits. Prices change: override them in the
134
+ config file.
135
+
136
+ ## Limits
137
+
138
+ - A plugin cannot run `/clear` for you. That one step stays manual.
139
+ - Notes are matched by the project folder's name, and only those newer than
140
+ `max_age_min` are loaded.
141
+ - The meter reads the transcript, so the first prompt of a session is not
142
+ metered.
143
+ - Costs are estimates from the transcript's usage fields, not your bill.
144
+
145
+ ## Privacy
146
+
147
+ Everything runs locally. The plugin reads your transcripts in `~/.claude` and
148
+ writes notes and a log there. Nothing is sent anywhere. Do not put secrets in
149
+ handoff notes.
150
+
151
+ ## Development
152
+
153
+ ```
154
+ python3 -m unittest discover -s tests
155
+ ```
156
+
157
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
158
+
159
+ ## License
160
+
161
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "context-handoff"
7
+ dynamic = ["version"]
8
+ description = "Keep Claude Code sessions cheap: meter cost, write a handoff note, resume after /clear"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Yauhen Bichel" }]
14
+ dependencies = []
15
+ keywords = ["claude", "claude-code", "llm", "cost", "context", "handoff"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Topic :: Software Development",
23
+ ]
24
+
25
+ [project.scripts]
26
+ context-handoff = "context_handoff.__main__:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/YauhenBichel/context-handoff"
30
+ Changelog = "https://github.com/YauhenBichel/context-handoff/blob/main/CHANGELOG.md"
31
+ Issues = "https://github.com/YauhenBichel/context-handoff/issues"
32
+
33
+ [tool.setuptools.dynamic]
34
+ version = { attr = "context_handoff.__version__" }
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.ruff]
40
+ line-length = 110
41
+ target-version = "py310"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Keep Claude Code sessions cheap: meter cost, hand off, resume after /clear."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,58 @@
1
+ """context-handoff entry point.
2
+
3
+ context-handoff meter UserPromptSubmit hook (reads hook JSON on stdin)
4
+ context-handoff resume SessionStart hook (reads hook JSON on stdin)
5
+ context-handoff report [days]
6
+ context-handoff check <transcript.jsonl> print what the meter sees
7
+
8
+ Hooks never fail the session: any error ends with no output and exit 0.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+
16
+ from . import config, meter, report, resume
17
+
18
+
19
+ def _hook(fn) -> int:
20
+ try:
21
+ event = json.load(sys.stdin)
22
+ out = fn(event, config.load())
23
+ except Exception as exc: # a hook must never break the session
24
+ print(f"context-handoff: {exc}", file=sys.stderr)
25
+ return 0
26
+ if out:
27
+ print(json.dumps(out))
28
+ return 0
29
+
30
+
31
+ def main(argv: list[str] | None = None) -> int:
32
+ argv = sys.argv[1:] if argv is None else argv
33
+ cmd = argv[0] if argv else ""
34
+ if cmd == "meter":
35
+ return _hook(meter.run)
36
+ if cmd == "resume":
37
+ return _hook(resume.run)
38
+ if cmd == "report":
39
+ days = int(argv[1]) if len(argv) > 1 else 7
40
+ print(report.render(config.load(), days))
41
+ return 0
42
+ if cmd == "check" and len(argv) > 1:
43
+ cfg = config.load()
44
+ r = meter.measure(argv[1], cfg)
45
+ if r is None:
46
+ print("no Claude replies in that transcript")
47
+ return 1
48
+ block, warn = meter.reasons(r, cfg)
49
+ print(r.summary())
50
+ print("block:", "; ".join(block) or "no")
51
+ print("warn: ", "; ".join(warn) or "no")
52
+ return 0
53
+ print(__doc__.strip())
54
+ return 2
55
+
56
+
57
+ if __name__ == "__main__":
58
+ sys.exit(main())
@@ -0,0 +1,90 @@
1
+ """Settings: defaults, then ~/.claude/context-handoff.json, then environment.
2
+
3
+ Config file example (all keys optional):
4
+
5
+ {
6
+ "warn_k": 150, "block_k": 400,
7
+ "warn_turn_usd": 0.50, "block_turn_usd": 3.00,
8
+ "warn_session_usd": 20, "auto_handoff": true,
9
+ "prices": {"claude-opus-5-5": [4.0, 20.0, 0.20]}
10
+ }
11
+
12
+ Environment variables use the same names, upper case, with a
13
+ CONTEXT_HANDOFF_ prefix: CONTEXT_HANDOFF_WARN_TURN_USD=1.0 and so on.
14
+ A limit set to 0 is turned off.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ from dataclasses import dataclass, field, fields
22
+ from pathlib import Path
23
+
24
+ from .prices import DEFAULT_PRICES
25
+
26
+
27
+ def _home() -> Path:
28
+ return Path(os.environ.get("HOME") or Path.home())
29
+
30
+
31
+ @dataclass
32
+ class Config:
33
+ warn_k: int = 150 # context size (thousands of tokens) that triggers a handoff
34
+ block_k: int = 400 # context size that blocks prompts until a handoff
35
+ warn_turn_usd: float = 0.50 # cost of the last turn that triggers a handoff
36
+ block_turn_usd: float = 3.00 # cost of the last turn that blocks prompts
37
+ warn_session_usd: float = 20.0 # whole-session cost that triggers a handoff
38
+ auto_handoff: bool = True # ask Claude to write the note itself
39
+ max_age_min: int = 180 # notes older than this are not resumed
40
+ handoff_dir: str = ""
41
+ state_dir: str = ""
42
+ prices: dict = field(default_factory=lambda: dict(DEFAULT_PRICES))
43
+
44
+ @property
45
+ def notes(self) -> Path:
46
+ return Path(self.handoff_dir) if self.handoff_dir else _home() / ".claude" / "handoffs"
47
+
48
+ @property
49
+ def state(self) -> Path:
50
+ return Path(self.state_dir) if self.state_dir else _home() / ".claude" / "context-handoff"
51
+
52
+
53
+ def _coerce(kind, value):
54
+ if kind is bool or kind == "bool":
55
+ return str(value).strip().lower() in ("1", "true", "yes", "on")
56
+ if kind is int or kind == "int":
57
+ return int(float(value))
58
+ if kind is float or kind == "float":
59
+ return float(value)
60
+ return str(value)
61
+
62
+
63
+ def load(path: str | Path | None = None) -> Config:
64
+ cfg = Config()
65
+ path = Path(path) if path else Path(
66
+ os.environ.get("CONTEXT_HANDOFF_CONFIG") or _home() / ".claude" / "context-handoff.json"
67
+ )
68
+ data: dict = {}
69
+ if path.is_file():
70
+ try:
71
+ data = json.loads(path.read_text())
72
+ except (OSError, ValueError):
73
+ data = {}
74
+ for f in fields(Config):
75
+ if f.name == "prices":
76
+ continue
77
+ env = os.environ.get("CONTEXT_HANDOFF_" + f.name.upper())
78
+ raw = env if env not in (None, "") else data.get(f.name)
79
+ if raw is not None:
80
+ try:
81
+ setattr(cfg, f.name, _coerce(f.type, raw))
82
+ except (TypeError, ValueError):
83
+ pass
84
+ for prefix, row in (data.get("prices") or {}).items():
85
+ try:
86
+ i, o, r = (float(x) for x in row)
87
+ cfg.prices[prefix] = (i, o, r)
88
+ except (TypeError, ValueError):
89
+ pass
90
+ return cfg