bitexoft-verify 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.
@@ -0,0 +1,300 @@
1
+ """Checks reused across deliveries and projects.
2
+
3
+ House rule: a check never fails for something the person cannot fix. If an optional
4
+ dependency or an external fixture is missing, it is SKIPPED with a message saying what
5
+ to do; failing would be lying about their code.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import importlib
12
+ import subprocess
13
+ import sys
14
+ from dataclasses import dataclass
15
+ from enum import Enum
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from ..manifest import Manifest
20
+ from ..report import Check, Problems, issue
21
+
22
+ FRAMEWORKS = (
23
+ "fastapi",
24
+ "pydantic",
25
+ "pydantic_settings",
26
+ "starlette",
27
+ "uvicorn",
28
+ "matplotlib",
29
+ "sqlalchemy",
30
+ "alembic",
31
+ "psycopg",
32
+ "flask",
33
+ "django",
34
+ "httpx",
35
+ "requests",
36
+ "numpy",
37
+ "pandas",
38
+ )
39
+
40
+ # Input and output, in every shape the domain might reach for. The domain answers what
41
+ # the problem *is*; how data arrives and leaves is somebody else's job.
42
+ IO_MODULES = ("csv", "json", "argparse", "sys", "pathlib", "os", "io", "logging", "sqlite3", "pickle", "configparser")
43
+
44
+
45
+ @dataclass(slots=True)
46
+ class Context:
47
+ root: Path
48
+ manifest: Manifest
49
+ timeout: int = 180
50
+
51
+ def run(self, args: list[str], timeout: int | None = None) -> subprocess.CompletedProcess[str]:
52
+ """Runs a command at the root of the learner's repository."""
53
+ return subprocess.run(
54
+ args,
55
+ cwd=self.root,
56
+ capture_output=True,
57
+ text=True,
58
+ timeout=timeout or self.timeout,
59
+ check=False,
60
+ )
61
+
62
+ def python(self, args: list[str], timeout: int | None = None) -> subprocess.CompletedProcess[str]:
63
+ return self.run([sys.executable, *args], timeout=timeout)
64
+
65
+ def cli(self, args: list[str], timeout: int | None = None) -> subprocess.CompletedProcess[str]:
66
+ """Runs the learner's entry point, however they declared it."""
67
+ module = self.manifest.get("cli.module")
68
+ if isinstance(module, str) and module.strip():
69
+ return self.python(["-m", module, *args], timeout=timeout)
70
+ return self.python([self.manifest.require("cli.main"), *args], timeout=timeout)
71
+
72
+
73
+ def import_symbol(reference: str) -> Any:
74
+ """`package.module:Symbol` -> the object. Raises if it cannot be imported."""
75
+ module_name, _, symbol = reference.partition(":")
76
+ module = importlib.import_module(module_name)
77
+ if not symbol:
78
+ return module
79
+ if not hasattr(module, symbol):
80
+ raise AttributeError(f"{module_name} does not export '{symbol}'")
81
+ return getattr(module, symbol)
82
+
83
+
84
+ def check_manifest_keys(ctx: Context, keys: list[str], check_id: str = "manifest") -> Check:
85
+ missing = ctx.manifest.missing(keys)
86
+ title = "bitexoft.toml declares what is needed"
87
+ if missing:
88
+ return Check(
89
+ check_id,
90
+ title,
91
+ "fail",
92
+ f"missing keys: {', '.join(missing)}",
93
+ (issue("manifest.missing_keys", keys=list(missing)),),
94
+ )
95
+ return Check(check_id, title, "pass")
96
+
97
+
98
+ def check_symbol(
99
+ ctx: Context,
100
+ reference: str,
101
+ *,
102
+ shape: str | None = None,
103
+ members: list[str] | None = None,
104
+ check_id: str | None = None,
105
+ ) -> Check:
106
+ """Checks that a symbol exists and has the shape the contract asks for."""
107
+ resolved = ctx.manifest.resolve(reference)
108
+ identifier = check_id or resolved.replace(":", ".").replace(".", "_").lower()
109
+ title = f"{resolved} exists with the required shape"
110
+ try:
111
+ obj = import_symbol(resolved)
112
+ except Exception as exc: # noqa: BLE001 - any import failure is a contract failure
113
+ return Check(
114
+ identifier,
115
+ title,
116
+ "fail",
117
+ f"could not import: {type(exc).__name__}: {exc}",
118
+ (issue("symbol.import_failed", symbol=resolved, error=type(exc).__name__, detail=str(exc)),),
119
+ )
120
+
121
+ problems = Problems()
122
+
123
+ if shape == "frozen-dataclass":
124
+ params = getattr(obj, "__dataclass_params__", None)
125
+ if params is None:
126
+ problems.add("not a dataclass", "symbol.not_dataclass", symbol=resolved)
127
+ elif not getattr(params, "frozen", False):
128
+ problems.add("the dataclass is not immutable (frozen=True)", "symbol.not_frozen", symbol=resolved)
129
+ elif shape == "enum":
130
+ if not (isinstance(obj, type) and issubclass(obj, Enum)):
131
+ problems.add("not an Enum", "symbol.not_enum", symbol=resolved)
132
+ elif shape == "exception":
133
+ if not (isinstance(obj, type) and issubclass(obj, BaseException)):
134
+ problems.add("not an exception class", "symbol.not_exception", symbol=resolved)
135
+ elif shape == "protocol-or-abc":
136
+ is_protocol = bool(getattr(obj, "_is_protocol", False))
137
+ is_abc = isinstance(obj, type) and bool(getattr(obj, "__abstractmethods__", frozenset()))
138
+ if not (is_protocol or is_abc):
139
+ problems.add(
140
+ "neither a Protocol nor an abstract base class", "symbol.not_protocol_or_abc", symbol=resolved
141
+ )
142
+
143
+ for member in members or []:
144
+ if isinstance(obj, type) and issubclass(obj, Enum):
145
+ present = member in obj.__members__
146
+ else:
147
+ present = hasattr(obj, member) or member in getattr(obj, "__annotations__", {})
148
+ if not present:
149
+ problems.add(f"'{member}' is missing", "symbol.member_missing", symbol=resolved, member=member)
150
+
151
+ if problems:
152
+ return problems.failing(identifier, title, "; ")
153
+ return Check(identifier, title, "pass")
154
+
155
+
156
+ def module_imports(path: Path) -> list[str]:
157
+ try:
158
+ tree = ast.parse(path.read_text(encoding="utf-8"))
159
+ except (SyntaxError, UnicodeDecodeError):
160
+ return []
161
+ names: list[str] = []
162
+ for node in ast.walk(tree):
163
+ if isinstance(node, ast.Import):
164
+ names += [alias.name for alias in node.names]
165
+ elif isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
166
+ names.append(node.module)
167
+ return names
168
+
169
+
170
+ def check_domain_has_no_io(ctx: Context, check_id: str = "boundary") -> Check:
171
+ """
172
+ The boundary, read from the syntax tree. Nothing is executed: what counts is what
173
+ the code says it depends on. This is the check that keeps the cost of every later
174
+ delivery constant instead of growing.
175
+ """
176
+ title = "The domain has no input or output"
177
+ folder = ctx.manifest.layer_path("domain")
178
+ if folder is None:
179
+ return Check(
180
+ check_id,
181
+ title,
182
+ "fail",
183
+ "I cannot find the domain folder declared in bitexoft.toml",
184
+ (issue("boundary.no_domain_folder"),),
185
+ )
186
+
187
+ forbidden = (*IO_MODULES, *FRAMEWORKS)
188
+ offenders = Problems()
189
+ checked = 0
190
+ for module in sorted(folder.rglob("*.py")):
191
+ checked += 1
192
+ for name in module_imports(module):
193
+ if any(name == entry or name.startswith(entry + ".") for entry in forbidden):
194
+ relative = module.relative_to(ctx.root)
195
+ offenders.add(
196
+ f"{relative} imports {name}",
197
+ "boundary.imports_io",
198
+ module=relative.as_posix(),
199
+ imported=name,
200
+ )
201
+
202
+ if checked == 0:
203
+ return Check(
204
+ check_id,
205
+ title,
206
+ "fail",
207
+ "the domain folder has no Python modules",
208
+ (issue("boundary.no_modules"),),
209
+ )
210
+ if offenders:
211
+ return offenders.failing(check_id, title, "; ", limit=8)
212
+ return Check(check_id, title, "pass")
213
+
214
+
215
+ def check_tests_pass(ctx: Context, check_id: str = "tests") -> Check:
216
+ title = "Your test suite passes"
217
+ try:
218
+ result = ctx.python(["-m", "pytest", "-q", "--no-header", "-x"], timeout=ctx.timeout)
219
+ except subprocess.TimeoutExpired:
220
+ return Check(
221
+ check_id,
222
+ title,
223
+ "fail",
224
+ f"the tests did not finish within {ctx.timeout}s",
225
+ (issue("tests.timeout", seconds=ctx.timeout),),
226
+ )
227
+ if result.returncode == 5:
228
+ return Check(check_id, title, "fail", "no tests were collected", (issue("tests.none_collected"),))
229
+ if result.returncode != 0:
230
+ tail = (result.stdout or result.stderr).strip().splitlines()[-3:]
231
+ return Check(
232
+ check_id,
233
+ title,
234
+ "fail",
235
+ " / ".join(tail) or "pytest returned a non-zero exit code",
236
+ (issue("tests.failed", detail=" / ".join(tail)),),
237
+ )
238
+ return Check(check_id, title, "pass")
239
+
240
+
241
+ def _test_files(ctx: Context) -> list[Path]:
242
+ seen: list[Path] = []
243
+ for pattern in ("test_*.py", "*_test.py", "conftest.py"):
244
+ seen += [
245
+ path
246
+ for path in ctx.root.rglob(pattern)
247
+ if ".venv" not in path.parts and "node_modules" not in path.parts
248
+ ]
249
+ return seen
250
+
251
+
252
+ def check_tests_cover_invariants(ctx: Context, error_names: list[str], check_id: str = "invariant_tests") -> Check:
253
+ """A test that builds something impossible and expects a refusal."""
254
+ title = "At least one test builds an impossible object"
255
+ files = _test_files(ctx)
256
+ if not files:
257
+ return Check(check_id, title, "fail", "I cannot find any test files", (issue("tests.no_files"),))
258
+ for path in files:
259
+ text = path.read_text(encoding="utf-8", errors="ignore")
260
+ if "raises" in text and any(name in text for name in error_names):
261
+ return Check(check_id, title, "pass")
262
+ return Check(
263
+ check_id,
264
+ title,
265
+ "fail",
266
+ f"no test expects {' or '.join(error_names)}: an invariant nobody tests is a comment, not a guarantee",
267
+ (issue("tests.no_invariant_test", errors=list(error_names)),),
268
+ )
269
+
270
+
271
+ def check_determinism(ctx: Context, args: list[str], check_id: str = "determinism") -> Check:
272
+ title = "Same input, same output"
273
+ try:
274
+ first = ctx.cli(args, timeout=120)
275
+ second = ctx.cli(args, timeout=120)
276
+ except subprocess.TimeoutExpired:
277
+ return Check(
278
+ check_id,
279
+ title,
280
+ "fail",
281
+ "one of the two runs did not finish within 120s",
282
+ (issue("determinism.timeout", seconds=120),),
283
+ )
284
+ if first.returncode != 0 or second.returncode != 0:
285
+ return Check(
286
+ check_id,
287
+ title,
288
+ "skip",
289
+ "cannot be checked: the program does not exit cleanly",
290
+ (issue("determinism.not_checkable"),),
291
+ )
292
+ if first.stdout != second.stdout:
293
+ return Check(
294
+ check_id,
295
+ title,
296
+ "fail",
297
+ "two runs over the same file produce different output",
298
+ (issue("determinism.differs"),),
299
+ )
300
+ return Check(check_id, title, "pass")
bitexoft_verify/cli.py ADDED
@@ -0,0 +1,120 @@
1
+ """Command line.
2
+
3
+ bitexoft-verify check A1 # verifies A1 (and everything before it)
4
+ bitexoft-verify check A3 --only # just A3
5
+ bitexoft-verify check A1 --repo ../mine # another directory
6
+ bitexoft-verify deliveries # what this version can verify today
7
+
8
+ Exit codes: 0 all green, 1 something fails, 2 could not even start (missing manifest,
9
+ unknown delivery). All three matter: CI reads them.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ from . import __version__
19
+ from .manifest import ManifestError
20
+ from .report import Check, Gate, Report, detect_surface, issue
21
+ from .runner import UnknownDelivery, available_deliveries, run
22
+
23
+ GREEN, RED, DIM, YELLOW, RESET = "\033[32m", "\033[31m", "\033[2m", "\033[33m", "\033[0m"
24
+ MARKS = {"pass": "ok ", "fail": "FAIL", "skip": "-- "}
25
+
26
+
27
+ def _colour(status: str, text: str, tty: bool) -> str:
28
+ if not tty:
29
+ return text
30
+ return {"pass": GREEN, "fail": RED, "skip": YELLOW}.get(status, "") + text + RESET
31
+
32
+
33
+ def _print(report: Report, tty: bool) -> None:
34
+ for gate in report.gates:
35
+ print(f"\n{gate.id}")
36
+ for check in gate.checks:
37
+ print(f" {_colour(check.status, MARKS[check.status], tty)} {check.title}")
38
+ if check.message:
39
+ print(f" {DIM if tty else ''}{check.message}{RESET if tty else ''}")
40
+ print(
41
+ f"\n{report.passed} green, {report.failed} red, {report.skipped} not checked "
42
+ f"(delivery {report.delivery}, {'CI' if report.surface == 'ci' else 'local'})"
43
+ )
44
+
45
+
46
+ def main(argv: list[str] | None = None) -> int:
47
+ parser = argparse.ArgumentParser(
48
+ prog="bitexoft-verify", description="Verifies a delivery of a Bitexoft project."
49
+ )
50
+ parser.add_argument("--version", action="version", version=f"bitexoft-verify {__version__}")
51
+ sub = parser.add_subparsers(dest="command", required=True)
52
+
53
+ check = sub.add_parser("check", help="verify a delivery")
54
+ check.add_argument("delivery", help="delivery identifier, such as A1")
55
+ check.add_argument("--repo", type=Path, default=Path.cwd(), help="repository root (defaults to the current one)")
56
+ check.add_argument("--out", type=Path, default=Path("verify-report.json"), help="where to write the report")
57
+ check.add_argument("--surface", choices=["auto", "local", "ci"], default="auto")
58
+ check.add_argument("--only", action="store_true", help="do not also verify the earlier deliveries")
59
+ check.add_argument("--timeout", type=int, default=180, help="seconds per command executed")
60
+ check.add_argument("--quiet", action="store_true")
61
+
62
+ sub.add_parser("deliveries", help="list the deliveries this version can verify")
63
+
64
+ args = parser.parse_args(argv)
65
+ tty = sys.stdout.isatty()
66
+
67
+ if args.command == "deliveries":
68
+ print("deliveries this version can verify: " + ", ".join(available_deliveries()))
69
+ return 0
70
+
71
+ root = args.repo.resolve()
72
+ delivery_id = args.delivery.strip().upper()
73
+ surface = detect_surface(None if args.surface == "auto" else args.surface)
74
+
75
+ try:
76
+ report = run(
77
+ root,
78
+ delivery_id,
79
+ cli_version=__version__,
80
+ surface=surface,
81
+ cumulative=not args.only,
82
+ timeout=args.timeout,
83
+ )
84
+ except (ManifestError, UnknownDelivery) as exc:
85
+ print(f"{RED if tty else ''}{exc}{RESET if tty else ''}", file=sys.stderr)
86
+ return 2
87
+ except Exception as exc: # noqa: BLE001 - an unexpected failure must not lose the report
88
+ report = Report(
89
+ project="unknown",
90
+ delivery=delivery_id,
91
+ cli_version=__version__,
92
+ gates=[
93
+ Gate(
94
+ "acceptance",
95
+ [
96
+ Check(
97
+ "runner",
98
+ "Verification could run",
99
+ "fail",
100
+ f"{type(exc).__name__}: {exc}",
101
+ (issue("runner.crashed", error=type(exc).__name__, detail=str(exc)),),
102
+ )
103
+ ],
104
+ )
105
+ ],
106
+ surface=surface, # type: ignore[arg-type]
107
+ )
108
+
109
+ written = report.write(args.out)
110
+ if not args.quiet:
111
+ _print(report, tty)
112
+ print(f"report: {written}")
113
+ if report.surface == "local":
114
+ print("this report is informative; the one that counts is produced by CI")
115
+
116
+ return 0 if report.ok else 1
117
+
118
+
119
+ if __name__ == "__main__":
120
+ raise SystemExit(main())
@@ -0,0 +1,68 @@
1
+ """The codes a check can attach to a problem (ADR 0028).
2
+
3
+ A code names a *kind of problem*, not a check: `symbol.not_frozen`, not `position`.
4
+ It is a public identifier. The platform keeps one sentence per code and language (ADR 0027), and a
5
+ learner's saved report keeps the code it was written with, so a code is never renamed and never
6
+ reused. A wrong code is retired and a new one added.
7
+
8
+ Only the codes something emits are listed. A1 is frozen (ADR 0009) and its checks emit none: the codes
9
+ that were drafted for its own checks were taken out, and a delivery from A2 on adds the ones it needs.
10
+
11
+ Each code lists the parameters it may carry. `expected` and `found` are the values to compare, when
12
+ there are two. `detail` is raw text from the learner's own code, such as an exception message: it is
13
+ shown as it is, never translated.
14
+
15
+ `python -m bitexoft_verify.codes` writes `data/issue-codes.json`, the list the platform's tests read
16
+ to make sure every code has a sentence in every language. A test keeps that file in step with this one.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ from pathlib import Path
23
+
24
+ ISSUE_CODES: dict[str, tuple[str, ...]] = {
25
+ # --- the manifest
26
+ "manifest.missing_keys": ("keys",),
27
+ # --- a symbol the contract names
28
+ "symbol.import_failed": ("symbol", "error", "detail"),
29
+ "symbol.not_dataclass": ("symbol",),
30
+ "symbol.not_frozen": ("symbol",),
31
+ "symbol.not_enum": ("symbol",),
32
+ "symbol.not_exception": ("symbol",),
33
+ "symbol.not_protocol_or_abc": ("symbol",),
34
+ "symbol.member_missing": ("symbol", "member"),
35
+ # --- the boundary
36
+ "boundary.no_domain_folder": (),
37
+ "boundary.no_modules": (),
38
+ "boundary.imports_io": ("module", "imported"),
39
+ # --- the learner's tests
40
+ "tests.timeout": ("seconds",),
41
+ "tests.none_collected": (),
42
+ "tests.failed": ("detail",),
43
+ "tests.no_files": (),
44
+ "tests.no_invariant_test": ("errors",),
45
+ # --- determinism
46
+ "determinism.timeout": ("seconds",),
47
+ "determinism.not_checkable": (),
48
+ "determinism.differs": (),
49
+ # --- the runner itself
50
+ "runner.crashed": ("error", "detail"),
51
+ }
52
+
53
+ CATALOGUE = Path(__file__).resolve().parent / "data" / "issue-codes.json"
54
+
55
+
56
+ def catalogue() -> dict:
57
+ """What the platform reads: every code and the parameters it may carry."""
58
+ return {"codes": {code: list(params) for code, params in sorted(ISSUE_CODES.items())}}
59
+
60
+
61
+ def main() -> int:
62
+ CATALOGUE.write_text(json.dumps(catalogue(), indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
63
+ print(f"{len(ISSUE_CODES)} codes written to {CATALOGUE}")
64
+ return 0
65
+
66
+
67
+ if __name__ == "__main__":
68
+ raise SystemExit(main())
@@ -0,0 +1,13 @@
1
+ # Sample files
2
+
3
+ Synthetic, generated by `verify/tools/make_fixtures.py`. No third-party data ships here.
4
+
5
+ `a1_match_sample.csv` holds 30 instants in metres on a 105x68 pitch. Players sit on a ring
6
+ around the centroid, so the expected spread of each instant is that ring's radius: between
7
+ 12 m and 32 m, crossing the default thresholds in both directions.
8
+
9
+ `a1_impossible_sample.csv` is the same file with one coordinate off the pitch.
10
+
11
+ `a2_isolated_sample.csv` holds ten players on a tight ring plus one stranded on the far
12
+ wing. The mean distance to the centroid stays low while the furthest player is far away, so
13
+ the two indicators of A2 reach opposite verdicts. That conflict is the point of the file.
@@ -0,0 +1,31 @@
1
+ t,p1_x,p1_y,p2_x,p2_y,p3_x,p3_y,p4_x,p4_y,p5_x,p5_y,p6_x,p6_y,p7_x,p7_y,p8_x,p8_y,p9_x,p9_y,p10_x,p10_y,p11_x,p11_y,ball_x,ball_y
2
+ 0.0,64.443,32.944,62.394,40.111,56.601,44.150,51.281,44.744,45.648,42.867,41.004,37.097,41.124,30.563,45.468,25.249,49.647,23.510,56.331,23.765,62.945,28.683,52.500,34.000
3
+ 1.0,64.991,34.427,62.109,41.195,56.784,44.569,50.822,45.148,44.523,42.662,40.040,34.899,40.428,31.081,43.738,25.977,49.603,23.056,57.779,23.803,63.094,28.028,52.600,34.000
4
+ 2.0,65.495,33.677,63.471,40.276,57.641,44.746,49.940,45.471,43.159,42.137,39.967,37.107,39.632,32.339,44.551,24.742,49.204,22.682,57.738,23.292,62.534,26.561,52.700,34.000
5
+ 3.0,66.500,34.002,64.726,40.139,58.097,45.549,46.960,45.572,43.373,43.554,38.832,36.728,38.726,31.748,43.091,24.670,51.244,21.451,58.221,22.500,64.497,27.505,52.800,34.000
6
+ 4.0,67.999,34.115,66.171,40.574,58.297,46.938,51.295,47.908,40.716,43.062,37.816,38.468,37.890,29.341,42.352,23.456,51.468,20.081,58.253,21.046,66.167,27.419,52.900,34.000
7
+ 5.0,69.499,33.848,65.801,43.528,58.781,48.217,49.891,49.119,42.191,46.165,35.925,37.401,36.379,29.143,43.146,21.224,49.574,18.928,60.241,20.378,67.163,26.258,53.000,34.000
8
+ 6.0,71.495,34.387,68.310,43.484,60.140,49.657,50.699,51.023,41.263,47.789,34.449,39.336,34.543,28.413,41.880,19.821,51.273,16.936,61.819,19.098,69.091,25.667,53.100,34.000
9
+ 7.0,73.467,95.000,69.998,44.450,60.029,51.644,46.602,52.139,39.169,48.603,32.710,40.323,33.522,25.909,39.061,19.477,51.133,15.140,62.537,17.398,69.063,22.381,53.200,34.000
10
+ 8.0,75.066,38.004,69.893,47.544,61.757,52.949,47.863,54.275,37.263,49.506,30.945,41.221,30.374,28.346,37.965,17.957,51.269,13.330,63.226,15.689,71.908,22.892,53.300,34.000
11
+ 9.0,76.856,36.385,73.878,44.771,61.245,54.598,46.872,55.460,36.893,50.997,30.219,43.169,28.554,29.337,36.810,17.065,50.040,12.061,65.353,15.228,71.981,20.629,53.400,34.000
12
+ 10.0,78.500,34.004,73.614,47.655,63.598,55.161,53.716,57.374,34.242,50.660,26.632,36.351,28.222,25.627,34.079,17.487,47.953,10.961,63.719,12.890,76.721,25.493,53.500,34.000
13
+ 11.0,79.467,35.200,75.080,47.323,60.577,57.187,48.832,58.075,31.987,49.801,27.049,42.113,26.324,28.044,36.024,14.749,51.721,9.710,64.985,12.454,75.559,21.359,53.600,34.000
14
+ 12.0,79.709,30.406,76.444,46.172,65.132,55.984,49.752,58.626,33.536,51.924,26.331,41.607,26.654,25.546,34.629,15.189,47.863,9.604,64.160,11.585,76.131,21.341,53.700,34.000
15
+ 13.0,81.432,35.790,77.477,47.262,65.036,57.535,51.523,60.085,30.861,51.376,23.915,38.402,24.561,27.006,32.684,14.943,45.560,8.658,64.904,10.408,77.152,20.254,53.800,34.000
16
+ 14.0,82.537,38.767,77.828,49.293,66.871,58.212,54.109,61.412,30.060,52.591,23.109,41.334,22.374,29.712,32.861,12.997,44.321,7.555,72.308,13.126,79.530,21.283,53.900,34.000
17
+ 15.0,83.967,35.298,79.235,48.992,70.275,57.405,48.333,62.101,34.984,57.563,21.887,40.681,21.206,30.761,30.199,13.978,51.076,5.679,69.230,9.979,77.274,16.491,54.000,34.000
18
+ 16.0,84.259,30.475,78.137,51.235,63.777,60.952,50.457,62.741,32.919,56.779,22.344,43.635,21.079,28.548,30.260,13.292,51.133,5.226,67.378,8.502,81.890,22.608,54.100,34.000
19
+ 17.0,83.499,33.807,78.759,48.829,67.552,58.390,48.392,61.654,34.768,56.885,24.294,45.575,23.289,24.660,35.027,10.954,50.151,6.180,68.305,9.998,78.912,19.393,54.200,34.000
20
+ 18.0,81.951,32.475,77.206,48.508,65.552,57.810,50.911,60.511,36.368,56.228,25.377,44.442,24.191,26.531,33.452,13.727,47.196,7.883,67.736,11.265,77.543,19.967,54.300,34.000
21
+ 19.0,79.471,35.133,73.685,49.065,65.302,55.395,52.899,58.297,33.623,51.374,27.052,42.119,27.532,24.751,35.376,15.212,50.820,9.747,65.195,12.554,72.481,17.657,54.400,34.000
22
+ 20.0,77.459,35.294,74.228,45.128,64.241,53.864,49.805,56.369,34.999,50.067,28.075,38.797,29.160,25.937,33.771,19.096,47.777,11.905,58.403,12.136,73.420,21.682,54.500,34.000
23
+ 21.0,75.973,35.015,73.009,44.326,61.842,53.407,48.726,54.875,35.731,48.817,30.435,41.278,29.563,29.399,36.056,18.891,52.439,12.850,64.088,15.600,72.143,22.390,54.600,34.000
24
+ 22.0,74.996,34.365,71.316,45.103,61.513,52.554,49.670,54.089,38.225,49.653,31.657,41.626,30.615,29.300,40.132,17.083,50.257,13.851,61.953,15.624,71.216,22.761,54.700,34.000
25
+ 23.0,73.500,34.095,69.838,44.663,62.199,50.764,49.185,52.663,38.446,48.043,32.741,40.401,32.018,29.830,37.525,20.750,47.756,15.589,62.560,17.410,67.721,20.979,54.800,34.000
26
+ 24.0,71.496,33.635,68.967,42.531,61.240,49.183,49.649,50.906,38.814,45.862,34.237,38.717,34.211,29.367,39.369,21.641,49.602,17.100,61.632,19.005,68.152,24.306,54.900,34.000
27
+ 25.0,69.429,32.599,67.656,40.930,60.902,47.301,51.963,49.292,40.282,44.639,36.059,37.892,36.101,29.969,42.940,21.349,49.199,18.991,60.480,20.491,67.360,26.569,55.000,34.000
28
+ 26.0,67.385,32.334,66.029,39.831,59.726,45.830,49.295,47.188,41.557,43.233,38.591,39.055,38.512,29.125,42.334,24.073,49.899,20.704,58.124,21.485,64.904,26.409,55.100,34.000
29
+ 27.0,65.857,32.239,64.405,39.730,59.293,44.500,50.056,45.949,42.085,41.731,39.030,34.814,39.449,30.892,42.343,25.997,50.145,22.036,57.170,22.600,64.867,29.129,55.200,34.000
30
+ 28.0,64.987,34.514,63.392,39.520,56.828,44.554,50.192,45.057,45.608,43.386,40.667,37.625,40.441,31.038,44.329,25.487,50.852,22.848,57.382,23.644,63.117,28.062,55.300,34.000
31
+ 29.0,64.487,33.494,63.034,39.173,56.128,44.295,50.451,44.641,44.329,41.909,41.510,38.337,40.942,31.095,45.252,25.392,49.577,23.525,58.423,24.607,62.388,27.881,55.400,34.000
@@ -0,0 +1,31 @@
1
+ t,p1_x,p1_y,p2_x,p2_y,p3_x,p3_y,p4_x,p4_y,p5_x,p5_y,p6_x,p6_y,p7_x,p7_y,p8_x,p8_y,p9_x,p9_y,p10_x,p10_y,p11_x,p11_y,ball_x,ball_y
2
+ 0.0,64.443,32.944,62.394,40.111,56.601,44.150,51.281,44.744,45.648,42.867,41.004,37.097,41.124,30.563,45.468,25.249,49.647,23.510,56.331,23.765,62.945,28.683,52.500,34.000
3
+ 1.0,64.991,34.427,62.109,41.195,56.784,44.569,50.822,45.148,44.523,42.662,40.040,34.899,40.428,31.081,43.738,25.977,49.603,23.056,57.779,23.803,63.094,28.028,52.600,34.000
4
+ 2.0,65.495,33.677,63.471,40.276,57.641,44.746,49.940,45.471,43.159,42.137,39.967,37.107,39.632,32.339,44.551,24.742,49.204,22.682,57.738,23.292,62.534,26.561,52.700,34.000
5
+ 3.0,66.500,34.002,64.726,40.139,58.097,45.549,46.960,45.572,43.373,43.554,38.832,36.728,38.726,31.748,43.091,24.670,51.244,21.451,58.221,22.500,64.497,27.505,52.800,34.000
6
+ 4.0,67.999,34.115,66.171,40.574,58.297,46.938,51.295,47.908,40.716,43.062,37.816,38.468,37.890,29.341,42.352,23.456,51.468,20.081,58.253,21.046,66.167,27.419,52.900,34.000
7
+ 5.0,69.499,33.848,65.801,43.528,58.781,48.217,49.891,49.119,42.191,46.165,35.925,37.401,36.379,29.143,43.146,21.224,49.574,18.928,60.241,20.378,67.163,26.258,53.000,34.000
8
+ 6.0,71.495,34.387,68.310,43.484,60.140,49.657,50.699,51.023,41.263,47.789,34.449,39.336,34.543,28.413,41.880,19.821,51.273,16.936,61.819,19.098,69.091,25.667,53.100,34.000
9
+ 7.0,73.467,35.056,69.998,44.450,60.029,51.644,46.602,52.139,39.169,48.603,32.710,40.323,33.522,25.909,39.061,19.477,51.133,15.140,62.537,17.398,69.063,22.381,53.200,34.000
10
+ 8.0,75.066,38.004,69.893,47.544,61.757,52.949,47.863,54.275,37.263,49.506,30.945,41.221,30.374,28.346,37.965,17.957,51.269,13.330,63.226,15.689,71.908,22.892,53.300,34.000
11
+ 9.0,76.856,36.385,73.878,44.771,61.245,54.598,46.872,55.460,36.893,50.997,30.219,43.169,28.554,29.337,36.810,17.065,50.040,12.061,65.353,15.228,71.981,20.629,53.400,34.000
12
+ 10.0,78.500,34.004,73.614,47.655,63.598,55.161,53.716,57.374,34.242,50.660,26.632,36.351,28.222,25.627,34.079,17.487,47.953,10.961,63.719,12.890,76.721,25.493,53.500,34.000
13
+ 11.0,79.467,35.200,75.080,47.323,60.577,57.187,48.832,58.075,31.987,49.801,27.049,42.113,26.324,28.044,36.024,14.749,51.721,9.710,64.985,12.454,75.559,21.359,53.600,34.000
14
+ 12.0,79.709,30.406,76.444,46.172,65.132,55.984,49.752,58.626,33.536,51.924,26.331,41.607,26.654,25.546,34.629,15.189,47.863,9.604,64.160,11.585,76.131,21.341,53.700,34.000
15
+ 13.0,81.432,35.790,77.477,47.262,65.036,57.535,51.523,60.085,30.861,51.376,23.915,38.402,24.561,27.006,32.684,14.943,45.560,8.658,64.904,10.408,77.152,20.254,53.800,34.000
16
+ 14.0,82.537,38.767,77.828,49.293,66.871,58.212,54.109,61.412,30.060,52.591,23.109,41.334,22.374,29.712,32.861,12.997,44.321,7.555,72.308,13.126,79.530,21.283,53.900,34.000
17
+ 15.0,83.967,35.298,79.235,48.992,70.275,57.405,48.333,62.101,34.984,57.563,21.887,40.681,21.206,30.761,30.199,13.978,51.076,5.679,69.230,9.979,77.274,16.491,54.000,34.000
18
+ 16.0,84.259,30.475,78.137,51.235,63.777,60.952,50.457,62.741,32.919,56.779,22.344,43.635,21.079,28.548,30.260,13.292,51.133,5.226,67.378,8.502,81.890,22.608,54.100,34.000
19
+ 17.0,83.499,33.807,78.759,48.829,67.552,58.390,48.392,61.654,34.768,56.885,24.294,45.575,23.289,24.660,35.027,10.954,50.151,6.180,68.305,9.998,78.912,19.393,54.200,34.000
20
+ 18.0,81.951,32.475,77.206,48.508,65.552,57.810,50.911,60.511,36.368,56.228,25.377,44.442,24.191,26.531,33.452,13.727,47.196,7.883,67.736,11.265,77.543,19.967,54.300,34.000
21
+ 19.0,79.471,35.133,73.685,49.065,65.302,55.395,52.899,58.297,33.623,51.374,27.052,42.119,27.532,24.751,35.376,15.212,50.820,9.747,65.195,12.554,72.481,17.657,54.400,34.000
22
+ 20.0,77.459,35.294,74.228,45.128,64.241,53.864,49.805,56.369,34.999,50.067,28.075,38.797,29.160,25.937,33.771,19.096,47.777,11.905,58.403,12.136,73.420,21.682,54.500,34.000
23
+ 21.0,75.973,35.015,73.009,44.326,61.842,53.407,48.726,54.875,35.731,48.817,30.435,41.278,29.563,29.399,36.056,18.891,52.439,12.850,64.088,15.600,72.143,22.390,54.600,34.000
24
+ 22.0,74.996,34.365,71.316,45.103,61.513,52.554,49.670,54.089,38.225,49.653,31.657,41.626,30.615,29.300,40.132,17.083,50.257,13.851,61.953,15.624,71.216,22.761,54.700,34.000
25
+ 23.0,73.500,34.095,69.838,44.663,62.199,50.764,49.185,52.663,38.446,48.043,32.741,40.401,32.018,29.830,37.525,20.750,47.756,15.589,62.560,17.410,67.721,20.979,54.800,34.000
26
+ 24.0,71.496,33.635,68.967,42.531,61.240,49.183,49.649,50.906,38.814,45.862,34.237,38.717,34.211,29.367,39.369,21.641,49.602,17.100,61.632,19.005,68.152,24.306,54.900,34.000
27
+ 25.0,69.429,32.599,67.656,40.930,60.902,47.301,51.963,49.292,40.282,44.639,36.059,37.892,36.101,29.969,42.940,21.349,49.199,18.991,60.480,20.491,67.360,26.569,55.000,34.000
28
+ 26.0,67.385,32.334,66.029,39.831,59.726,45.830,49.295,47.188,41.557,43.233,38.591,39.055,38.512,29.125,42.334,24.073,49.899,20.704,58.124,21.485,64.904,26.409,55.100,34.000
29
+ 27.0,65.857,32.239,64.405,39.730,59.293,44.500,50.056,45.949,42.085,41.731,39.030,34.814,39.449,30.892,42.343,25.997,50.145,22.036,57.170,22.600,64.867,29.129,55.200,34.000
30
+ 28.0,64.987,34.514,63.392,39.520,56.828,44.554,50.192,45.057,45.608,43.386,40.667,37.625,40.441,31.038,44.329,25.487,50.852,22.848,57.382,23.644,63.117,28.062,55.300,34.000
31
+ 29.0,64.487,33.494,63.034,39.173,56.128,44.295,50.451,44.641,44.329,41.909,41.510,38.337,40.942,31.095,45.252,25.392,49.577,23.525,58.423,24.607,62.388,27.881,55.400,34.000
@@ -0,0 +1,31 @@
1
+ t,p1_x,p1_y,p2_x,p2_y,p3_x,p3_y,p4_x,p4_y,p5_x,p5_y,p6_x,p6_y,p7_x,p7_y,p8_x,p8_y,p9_x,p9_y,p10_x,p10_y,p11_x,p11_y,ball_x,ball_y
2
+ 0.0,47.880,35.244,46.863,37.700,42.273,40.903,37.687,40.893,33.874,38.631,32.008,34.330,33.338,30.013,37.468,27.170,42.706,27.224,46.832,30.253,88.000,34.000,40.000,34.000
3
+ 1.0,47.999,34.115,46.041,38.721,42.216,40.918,37.488,40.836,33.627,38.352,32.026,34.583,32.974,30.558,37.449,27.176,42.339,27.115,46.571,29.893,88.200,34.000,40.000,34.000
4
+ 2.0,47.998,34.159,46.927,37.602,42.931,40.699,38.247,41.025,33.195,37.786,32.001,34.100,33.550,29.741,36.953,27.343,41.655,26.956,46.222,29.474,88.400,34.000,40.000,34.000
5
+ 3.0,47.999,33.904,46.323,38.411,43.406,40.515,37.371,40.800,33.890,38.648,32.009,34.338,33.382,29.955,37.229,27.246,42.704,27.224,46.758,30.147,88.600,34.000,40.000,34.000
6
+ 4.0,47.998,34.153,46.692,37.946,42.262,40.906,37.346,40.792,33.248,37.861,32.001,33.888,32.961,30.578,37.035,27.313,42.186,27.074,46.773,30.168,88.800,34.000,40.000,34.000
7
+ 5.0,47.999,33.882,46.147,38.608,42.339,40.885,38.295,41.035,33.537,38.244,32.006,33.726,33.654,29.615,36.616,27.476,42.684,27.217,46.219,29.471,89.000,34.000,40.000,34.000
8
+ 6.0,47.882,32.768,46.701,37.932,43.661,40.402,38.348,41.045,33.059,37.580,32.142,32.650,33.914,29.327,37.746,27.092,43.539,27.543,46.571,29.893,89.200,34.000,40.000,34.000
9
+ 7.0,47.972,33.403,47.275,36.995,41.965,40.979,38.958,41.139,34.779,39.455,32.013,34.412,33.526,29.771,36.974,27.335,43.049,27.343,46.413,29.696,89.400,34.000,40.000,34.000
10
+ 8.0,48.000,34.080,46.329,38.404,42.862,40.723,37.443,40.822,32.793,37.125,32.025,34.572,33.565,29.722,37.807,27.076,42.104,27.054,46.430,29.717,89.600,34.000,40.000,34.000
11
+ 9.0,47.921,32.988,46.654,37.998,42.484,40.844,36.900,40.638,33.213,37.811,32.010,34.364,33.119,30.327,38.622,26.908,42.970,27.315,45.660,28.912,89.800,34.000,40.000,34.000
12
+ 10.0,47.996,33.773,46.812,37.776,42.870,40.721,38.622,41.092,33.710,38.449,32.008,34.327,33.421,29.903,38.135,26.998,42.577,27.184,45.964,29.201,90.000,34.000,40.000,34.000
13
+ 11.0,47.919,32.978,46.682,37.958,41.796,41.016,36.993,40.672,33.775,38.522,32.032,33.360,33.676,29.590,37.136,27.277,42.706,27.224,46.426,29.712,90.200,34.000,40.000,34.000
14
+ 12.0,47.966,33.335,46.646,38.008,42.399,40.869,36.765,40.585,33.955,38.716,32.001,33.885,33.521,29.776,37.475,27.168,41.695,26.964,46.901,30.358,90.400,34.000,40.000,34.000
15
+ 13.0,47.959,34.726,45.835,38.926,42.206,40.921,36.665,40.545,33.342,37.992,32.024,33.443,33.393,29.940,37.646,27.119,42.254,27.092,46.039,29.278,90.600,34.000,40.000,34.000
16
+ 14.0,47.994,33.732,46.878,37.677,43.159,40.615,37.785,40.919,33.774,38.522,32.107,32.828,34.248,28.996,38.179,26.989,42.934,27.302,46.122,29.365,90.800,34.000,40.000,34.000
17
+ 15.0,47.998,34.145,46.981,37.517,42.868,40.721,37.840,40.933,33.679,38.413,32.018,33.517,33.235,30.157,39.025,26.854,42.143,27.063,46.770,30.165,91.000,34.000,40.000,34.000
18
+ 16.0,47.934,33.074,46.121,38.636,43.224,40.589,37.420,40.815,33.538,38.245,32.017,33.536,33.758,29.496,37.755,27.089,42.368,27.123,47.124,30.723,91.200,34.000,40.000,34.000
19
+ 17.0,47.994,33.722,46.475,38.229,42.771,40.754,37.456,40.826,33.920,38.679,32.005,34.251,33.716,29.544,37.164,27.267,42.742,27.236,46.236,29.490,91.400,34.000,40.000,34.000
20
+ 18.0,47.949,34.810,46.530,38.160,41.387,41.091,37.237,40.757,33.586,38.303,32.022,33.461,33.989,29.249,38.468,26.933,43.296,27.439,46.787,30.188,91.600,34.000,40.000,34.000
21
+ 19.0,47.963,33.312,46.109,38.649,42.538,40.828,37.292,40.775,33.089,37.626,32.014,34.427,34.060,29.177,39.021,26.854,42.798,27.255,46.344,29.614,91.800,34.000,40.000,34.000
22
+ 20.0,47.952,34.784,46.410,38.308,41.831,41.009,36.784,40.593,34.155,38.916,32.002,33.824,32.995,30.523,37.089,27.294,42.829,27.265,45.982,29.219,92.000,34.000,40.000,34.000
23
+ 21.0,47.986,33.570,46.649,38.004,42.293,40.898,37.329,40.787,33.373,38.033,32.007,34.306,33.247,30.140,37.766,27.087,41.917,27.010,46.140,29.385,92.200,34.000,40.000,34.000
24
+ 22.0,47.988,34.390,46.769,37.837,43.036,40.661,36.865,40.624,33.556,38.267,32.132,35.305,33.244,30.144,38.728,26.892,44.113,27.824,46.345,29.615,92.400,34.000,40.000,34.000
25
+ 23.0,47.988,34.387,46.195,38.556,42.299,40.896,38.686,41.102,33.182,37.767,32.002,33.858,33.427,29.896,36.540,27.508,42.060,27.043,46.415,29.698,92.600,34.000,40.000,34.000
26
+ 24.0,47.998,33.833,46.399,38.321,42.728,40.768,36.947,40.655,33.313,37.952,32.037,34.688,33.294,30.074,37.763,27.087,42.108,27.054,46.947,30.429,92.800,34.000,40.000,34.000
27
+ 25.0,47.991,33.666,46.393,38.328,42.123,40.942,38.255,41.027,33.102,37.647,32.002,34.145,33.255,30.128,37.083,27.296,41.391,26.910,45.902,29.139,93.000,34.000,40.000,34.000
28
+ 26.0,47.945,34.839,46.536,38.151,42.670,40.787,37.435,40.820,33.333,37.979,32.018,34.484,33.434,29.887,37.669,27.112,43.056,27.346,46.551,29.867,93.200,34.000,40.000,34.000
29
+ 27.0,47.933,34.932,46.568,38.110,42.702,40.777,38.417,41.058,33.561,38.273,32.009,33.654,33.421,29.904,37.396,27.192,41.842,26.993,46.226,29.479,93.400,34.000,40.000,34.000
30
+ 28.0,47.994,33.717,46.522,38.169,41.836,41.008,37.246,40.760,33.215,37.815,32.033,33.347,33.342,30.008,38.329,26.959,42.473,27.153,45.921,29.158,93.600,34.000,40.000,34.000
31
+ 29.0,47.989,33.626,46.521,38.171,43.414,40.512,36.198,40.335,33.564,38.276,32.017,33.532,33.089,30.373,37.560,27.143,42.601,27.191,46.154,29.400,93.800,34.000,40.000,34.000
@@ -0,0 +1,57 @@
1
+ {
2
+ "codes": {
3
+ "boundary.imports_io": [
4
+ "module",
5
+ "imported"
6
+ ],
7
+ "boundary.no_domain_folder": [],
8
+ "boundary.no_modules": [],
9
+ "determinism.differs": [],
10
+ "determinism.not_checkable": [],
11
+ "determinism.timeout": [
12
+ "seconds"
13
+ ],
14
+ "manifest.missing_keys": [
15
+ "keys"
16
+ ],
17
+ "runner.crashed": [
18
+ "error",
19
+ "detail"
20
+ ],
21
+ "symbol.import_failed": [
22
+ "symbol",
23
+ "error",
24
+ "detail"
25
+ ],
26
+ "symbol.member_missing": [
27
+ "symbol",
28
+ "member"
29
+ ],
30
+ "symbol.not_dataclass": [
31
+ "symbol"
32
+ ],
33
+ "symbol.not_enum": [
34
+ "symbol"
35
+ ],
36
+ "symbol.not_exception": [
37
+ "symbol"
38
+ ],
39
+ "symbol.not_frozen": [
40
+ "symbol"
41
+ ],
42
+ "symbol.not_protocol_or_abc": [
43
+ "symbol"
44
+ ],
45
+ "tests.failed": [
46
+ "detail"
47
+ ],
48
+ "tests.no_files": [],
49
+ "tests.no_invariant_test": [
50
+ "errors"
51
+ ],
52
+ "tests.none_collected": [],
53
+ "tests.timeout": [
54
+ "seconds"
55
+ ]
56
+ }
57
+ }