pmlab 0.1.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.
- pmlab-0.1.0/.github/workflows/ci.yml +58 -0
- pmlab-0.1.0/.gitignore +20 -0
- pmlab-0.1.0/AGENTS.md +155 -0
- pmlab-0.1.0/CHANGELOG.md +31 -0
- pmlab-0.1.0/LICENSE +21 -0
- pmlab-0.1.0/PKG-INFO +490 -0
- pmlab-0.1.0/README.md +447 -0
- pmlab-0.1.0/docs/plugin-authoring.md +135 -0
- pmlab-0.1.0/pyproject.toml +91 -0
- pmlab-0.1.0/scripts/pmlab-workspace +17 -0
- pmlab-0.1.0/src/pmlab/__init__.py +63 -0
- pmlab-0.1.0/src/pmlab/backtest/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/backtest/holdout_gate.py +127 -0
- pmlab-0.1.0/src/pmlab/backtest/metrics.py +53 -0
- pmlab-0.1.0/src/pmlab/backtest/rolling_origin.py +142 -0
- pmlab-0.1.0/src/pmlab/cli/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/cli/main.py +83 -0
- pmlab-0.1.0/src/pmlab/core/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/core/edge.py +27 -0
- pmlab-0.1.0/src/pmlab/core/fees.py +21 -0
- pmlab-0.1.0/src/pmlab/core/market_spec.py +73 -0
- pmlab-0.1.0/src/pmlab/core/pnl.py +48 -0
- pmlab-0.1.0/src/pmlab/core/sizing.py +72 -0
- pmlab-0.1.0/src/pmlab/data/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/execution/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/execution/edge_signal.py +23 -0
- pmlab-0.1.0/src/pmlab/execution/live_broker.py +187 -0
- pmlab-0.1.0/src/pmlab/execution/paper_broker.py +141 -0
- pmlab-0.1.0/src/pmlab/execution/settlement.py +131 -0
- pmlab-0.1.0/src/pmlab/features/__init__.py +12 -0
- pmlab-0.1.0/src/pmlab/features/transforms.py +95 -0
- pmlab-0.1.0/src/pmlab/markets/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/markets/async_clob_client.py +43 -0
- pmlab-0.1.0/src/pmlab/markets/async_gamma_client.py +33 -0
- pmlab-0.1.0/src/pmlab/markets/cache.py +70 -0
- pmlab-0.1.0/src/pmlab/markets/clob_client.py +71 -0
- pmlab-0.1.0/src/pmlab/markets/gamma_client.py +106 -0
- pmlab-0.1.0/src/pmlab/modeling/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/modeling/base.py +38 -0
- pmlab-0.1.0/src/pmlab/modeling/calibration.py +45 -0
- pmlab-0.1.0/src/pmlab/modeling/champion.py +113 -0
- pmlab-0.1.0/src/pmlab/modeling/diagnostics.py +67 -0
- pmlab-0.1.0/src/pmlab/modeling/lgbm_baseline.py +69 -0
- pmlab-0.1.0/src/pmlab/plugins/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/plugins/base.py +105 -0
- pmlab-0.1.0/src/pmlab/plugins/registry.py +38 -0
- pmlab-0.1.0/src/pmlab/plugins/sports_f1/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/plugins/sports_f1/plugin.py +103 -0
- pmlab-0.1.0/src/pmlab/plugins/weather_tmax/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/plugins/weather_tmax/_spec_builder.py +78 -0
- pmlab-0.1.0/src/pmlab/plugins/weather_tmax/plugin.py +112 -0
- pmlab-0.1.0/src/pmlab/py.typed +0 -0
- pmlab-0.1.0/src/pmlab/reports/__init__.py +4 -0
- pmlab-0.1.0/src/pmlab/reports/html_report.py +165 -0
- pmlab-0.1.0/src/pmlab/workspace/__init__.py +0 -0
- pmlab-0.1.0/src/pmlab/workspace/context.py +36 -0
- pmlab-0.1.0/tests/__init__.py +0 -0
- pmlab-0.1.0/tests/backtest/__init__.py +0 -0
- pmlab-0.1.0/tests/backtest/test_holdout_gate.py +106 -0
- pmlab-0.1.0/tests/backtest/test_metrics.py +54 -0
- pmlab-0.1.0/tests/backtest/test_rolling_origin.py +133 -0
- pmlab-0.1.0/tests/conftest.py +14 -0
- pmlab-0.1.0/tests/core/__init__.py +0 -0
- pmlab-0.1.0/tests/core/test_edge.py +26 -0
- pmlab-0.1.0/tests/core/test_fees.py +24 -0
- pmlab-0.1.0/tests/core/test_kelly_sizing.py +57 -0
- pmlab-0.1.0/tests/core/test_market_spec.py +135 -0
- pmlab-0.1.0/tests/core/test_pnl.py +48 -0
- pmlab-0.1.0/tests/core/test_sizing.py +26 -0
- pmlab-0.1.0/tests/execution/__init__.py +0 -0
- pmlab-0.1.0/tests/execution/test_edge_signal.py +49 -0
- pmlab-0.1.0/tests/execution/test_live_broker.py +125 -0
- pmlab-0.1.0/tests/execution/test_paper_broker.py +105 -0
- pmlab-0.1.0/tests/execution/test_settlement.py +308 -0
- pmlab-0.1.0/tests/features/__init__.py +0 -0
- pmlab-0.1.0/tests/features/test_transforms.py +91 -0
- pmlab-0.1.0/tests/integration/__init__.py +0 -0
- pmlab-0.1.0/tests/integration/test_full_pipeline.py +146 -0
- pmlab-0.1.0/tests/markets/__init__.py +0 -0
- pmlab-0.1.0/tests/markets/test_async_clients.py +53 -0
- pmlab-0.1.0/tests/markets/test_cache.py +56 -0
- pmlab-0.1.0/tests/markets/test_clob_client.py +43 -0
- pmlab-0.1.0/tests/markets/test_gamma_client.py +59 -0
- pmlab-0.1.0/tests/modeling/__init__.py +0 -0
- pmlab-0.1.0/tests/modeling/test_base.py +39 -0
- pmlab-0.1.0/tests/modeling/test_calibration.py +33 -0
- pmlab-0.1.0/tests/modeling/test_champion.py +162 -0
- pmlab-0.1.0/tests/modeling/test_diagnostics.py +52 -0
- pmlab-0.1.0/tests/modeling/test_lgbm.py +56 -0
- pmlab-0.1.0/tests/plugins/__init__.py +0 -0
- pmlab-0.1.0/tests/plugins/sports_f1/__init__.py +0 -0
- pmlab-0.1.0/tests/plugins/sports_f1/test_sports_f1_plugin.py +109 -0
- pmlab-0.1.0/tests/plugins/test_plugin_base.py +97 -0
- pmlab-0.1.0/tests/plugins/test_registry.py +60 -0
- pmlab-0.1.0/tests/plugins/weather_tmax/__init__.py +0 -0
- pmlab-0.1.0/tests/plugins/weather_tmax/test_weather_tmax_plugin.py +174 -0
- pmlab-0.1.0/tests/reports/__init__.py +0 -0
- pmlab-0.1.0/tests/reports/test_html_report.py +34 -0
- pmlab-0.1.0/tests/test_smoke.py +98 -0
- pmlab-0.1.0/tests/test_workspace.py +60 -0
- pmlab-0.1.0/uv.lock +911 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, "feat/**", "fix/**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Python 3.12)
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v5
|
|
24
|
+
with:
|
|
25
|
+
version: "latest"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --extra dev
|
|
29
|
+
|
|
30
|
+
- name: Lint (ruff)
|
|
31
|
+
run: uv run ruff check src/ tests/
|
|
32
|
+
|
|
33
|
+
- name: Run tests with coverage
|
|
34
|
+
run: uv run pytest --cov=src/pmlab --cov-report=term-missing --cov-fail-under=70
|
|
35
|
+
|
|
36
|
+
build:
|
|
37
|
+
name: Build distribution
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
needs: test
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
|
|
49
|
+
- name: Install uv
|
|
50
|
+
uses: astral-sh/setup-uv@v5
|
|
51
|
+
with:
|
|
52
|
+
version: "latest"
|
|
53
|
+
|
|
54
|
+
- name: Build wheel + sdist
|
|
55
|
+
run: uv build
|
|
56
|
+
|
|
57
|
+
- name: Verify dist contents
|
|
58
|
+
run: ls -la dist/
|
pmlab-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# gitignore para Python library
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.venv/
|
|
11
|
+
.env
|
|
12
|
+
*.lock
|
|
13
|
+
!uv.lock
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
*.log
|
|
20
|
+
.DS_Store
|
pmlab-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# pmlab Agent Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`pmlab` is a **generic, plugin-based ML framework** for Polymarket prediction markets.
|
|
6
|
+
It is designed to be published as a Python library (pip/uv installable).
|
|
7
|
+
|
|
8
|
+
The framework is market-family agnostic: each domain (weather, sports, politics, crypto)
|
|
9
|
+
implements a `MarketPlugin` and the core handles everything else.
|
|
10
|
+
|
|
11
|
+
Use this file as the shared contract for all agents (Codex, Claude, etc).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Module Ownership
|
|
16
|
+
|
|
17
|
+
| Module | Owns |
|
|
18
|
+
|--------|------|
|
|
19
|
+
| `src/pmlab/core/` | MarketSpec, OutcomeBin, PnL, edge, fees, sizing — no domain logic |
|
|
20
|
+
| `src/pmlab/plugins/` | MarketPlugin ABC, PluginRegistry, domain plugin implementations |
|
|
21
|
+
| `src/pmlab/plugins/weather_tmax/` | Weather/temperature market plugin (reference implementation) |
|
|
22
|
+
| `src/pmlab/plugins/sports_f1/` | F1 race outcome plugin |
|
|
23
|
+
| `src/pmlab/backtest/` | Rolling-origin eval, metrics, HoldoutGate — no lookahead |
|
|
24
|
+
| `src/pmlab/modeling/` | MarketForecaster ABC, LGBMForecaster, calibration, ChampionManifest |
|
|
25
|
+
| `src/pmlab/execution/` | EdgeSignal, PaperBroker, SettlementEngine |
|
|
26
|
+
| `src/pmlab/workspace/` | WorkspaceContext — multi-workspace path isolation |
|
|
27
|
+
| `src/pmlab/markets/` | Polymarket Gamma API client, CLOB price client (Phase 4) |
|
|
28
|
+
| `src/pmlab/cli/` | Typer CLI entry points |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Safety Rules
|
|
33
|
+
|
|
34
|
+
1. **Never publish a champion with NO_GO gate.**
|
|
35
|
+
`ChampionManifest.publish()` hard-raises `ValueError` if `gate.decision != "GO"`.
|
|
36
|
+
This is not configurable. A failed gate must be investigated, not bypassed.
|
|
37
|
+
|
|
38
|
+
2. **Never overwrite canonical training data without explicit `--allow-overwrite`.**
|
|
39
|
+
Experiments use variant output paths. Canonical data is for stable baselines only.
|
|
40
|
+
|
|
41
|
+
3. **Never mix workspace data.**
|
|
42
|
+
`ops_daily` is live forward evidence only. `historical_real` is training data.
|
|
43
|
+
A model trained on ops_daily data is contaminated — do not promote it.
|
|
44
|
+
|
|
45
|
+
4. **Always use `scripts/pmlab-workspace <name> <cmd>` for workspace-scoped operations.**
|
|
46
|
+
This sets `PMLAB_ARTIFACTS_DIR` and friends so all paths resolve correctly.
|
|
47
|
+
|
|
48
|
+
5. **City/segment gate always reads from `champion.json`.**
|
|
49
|
+
The `recent_core_benchmark_summary.json` (or equivalent) is overwritten on each retrain.
|
|
50
|
+
`champion.json` reflects the gate at promotion time and is the authoritative source.
|
|
51
|
+
|
|
52
|
+
6. **Never settle trades before `plugin.is_truth_final()` returns True.**
|
|
53
|
+
Some data sources have a lag (e.g. weather stations). Wait for finalization.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Adding a New Plugin
|
|
58
|
+
|
|
59
|
+
1. Create `src/pmlab/plugins/<your_family>/plugin.py`
|
|
60
|
+
2. Subclass `MarketPlugin` from `pmlab.plugins.base`
|
|
61
|
+
3. Set `family = "your_family"`
|
|
62
|
+
4. Implement all 4 abstract methods: `discover_markets`, `fetch_features`, `fetch_truth`, `build_training_row`
|
|
63
|
+
5. Optionally override `is_truth_final()` if your data source has a finalization lag
|
|
64
|
+
6. Add tests in `tests/plugins/<your_family>/`
|
|
65
|
+
7. Register via `PluginRegistry.register(YourPlugin())`
|
|
66
|
+
|
|
67
|
+
See `src/pmlab/plugins/weather_tmax/plugin.py` as the reference implementation.
|
|
68
|
+
See `docs/plugin-authoring.md` for the full guide.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Standard Commands
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Install in development mode
|
|
76
|
+
uv sync --extra dev
|
|
77
|
+
|
|
78
|
+
# Run all tests
|
|
79
|
+
uv run pytest
|
|
80
|
+
|
|
81
|
+
# Run tests with coverage
|
|
82
|
+
uv run pytest --cov=src/pmlab --cov-report=term-missing
|
|
83
|
+
|
|
84
|
+
# Lint
|
|
85
|
+
uv run ruff check src/ tests/
|
|
86
|
+
|
|
87
|
+
# Type check
|
|
88
|
+
uv run mypy src/
|
|
89
|
+
|
|
90
|
+
# Workspace-scoped command
|
|
91
|
+
scripts/pmlab-workspace ops_daily uv run pmlab scan-edge --plugin weather_tmax
|
|
92
|
+
|
|
93
|
+
# Build distribution
|
|
94
|
+
uv build
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Commit Convention
|
|
100
|
+
|
|
101
|
+
Format: `<type>: <subject>` — imperative, no period.
|
|
102
|
+
|
|
103
|
+
Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `ci`, `perf`
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
- `feat: add SportsF1Plugin with categorical outcome bins`
|
|
107
|
+
- `fix: champion gate reads from champion.json not retrain summary`
|
|
108
|
+
- `test: add rolling_origin no-lookahead assertion`
|
|
109
|
+
- `docs: update plugin-authoring guide with is_truth_final`
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## TDD Contract
|
|
114
|
+
|
|
115
|
+
Every new module gets tests BEFORE implementation:
|
|
116
|
+
1. Write test → run → confirm RED (import error or assertion failure)
|
|
117
|
+
2. Implement minimal code → run → confirm GREEN
|
|
118
|
+
3. Refactor → run → confirm still GREEN
|
|
119
|
+
4. Commit
|
|
120
|
+
|
|
121
|
+
Coverage gate: 70% minimum (enforced in pyproject.toml).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Architecture Summary
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Polymarket API ──► MarketPlugin.discover_markets() ──► list[MarketSpec]
|
|
129
|
+
│
|
|
130
|
+
├──► MarketPlugin.fetch_features() ──► features dict
|
|
131
|
+
├──► MarketPlugin.fetch_truth() ──► realized outcome
|
|
132
|
+
└──► MarketPlugin.build_training_row() ──► training row
|
|
133
|
+
│
|
|
134
|
+
┌─────────────────────────┘
|
|
135
|
+
│
|
|
136
|
+
▼
|
|
137
|
+
rolling_origin_eval(panel, model)
|
|
138
|
+
│
|
|
139
|
+
├──► BacktestMetrics (PnL, hit_rate, edge)
|
|
140
|
+
└──► HoldoutGate.evaluate(required_segments) ──► GO / NO_GO
|
|
141
|
+
│
|
|
142
|
+
GO
|
|
143
|
+
│
|
|
144
|
+
ChampionManifest.publish(model, gate)
|
|
145
|
+
(hard-raises on NO_GO)
|
|
146
|
+
│
|
|
147
|
+
champion.json
|
|
148
|
+
champion.pkl
|
|
149
|
+
│
|
|
150
|
+
PaperBroker.record(signals)
|
|
151
|
+
(reads allowed_segments from champion.json)
|
|
152
|
+
│
|
|
153
|
+
SettlementEngine.settle_all(specs)
|
|
154
|
+
(calls plugin.fetch_truth + is_truth_final)
|
|
155
|
+
```
|
pmlab-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. Format: [Keep a Changelog](https://keepachangelog.com/).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-05-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `MarketPlugin` ABC — interface for domain-specific plugins
|
|
9
|
+
- `PluginRegistry` — register and look up plugins by family name
|
|
10
|
+
- `WeatherTmaxPlugin` — reference implementation for temperature markets
|
|
11
|
+
- `SportsF1Plugin` — categorical outcome plugin for F1 race markets
|
|
12
|
+
- `MarketSpec` + `OutcomeBin` — generic market descriptor
|
|
13
|
+
- `Position` + `settle_position` — binary outcome PnL accounting
|
|
14
|
+
- `compute_edge` — after-cost probability edge
|
|
15
|
+
- `estimate_fee` + `flat_stake_size` — trade sizing utilities
|
|
16
|
+
- `rolling_origin_eval` — walk-forward backtest with no-lookahead guarantee
|
|
17
|
+
- `BacktestMetrics` + `compute_metrics` — PnL, hit rate, edge statistics
|
|
18
|
+
- `HoldoutGateResult` — per-segment GO/NO_GO gate
|
|
19
|
+
- `MarketForecaster` ABC — model protocol
|
|
20
|
+
- `LGBMForecaster` — LightGBM binary/multiclass implementation
|
|
21
|
+
- `IsotonicCalibrator` — probability calibration
|
|
22
|
+
- `ChampionManifest` — hard-gate champion publish (raises on NO_GO)
|
|
23
|
+
- `EdgeSignal` — typed scan-edge output dataclass
|
|
24
|
+
- `PaperBroker` — record trades with segment gate, stale guard, dedup
|
|
25
|
+
- `SettlementEngine` — settle open trades via plugin truth resolution
|
|
26
|
+
- `WorkspaceContext` + `scripts/pmlab-workspace` — workspace isolation
|
|
27
|
+
- `GammaClient` + `fetch_gamma_markets` — Polymarket Gamma API
|
|
28
|
+
- `ClobClient` + `fetch_token_prices` — Polymarket CLOB price fetch
|
|
29
|
+
- CLI: `pmlab version`, `status`, `scan-markets`, `record-trades`, `settle-trades`, `backtest`, `promote-champion`
|
|
30
|
+
- GitHub Actions CI: lint (ruff) + pytest (70% coverage gate) + build
|
|
31
|
+
- 123+ tests, 92%+ coverage
|
pmlab-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arthur Breguez
|
|
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.
|