designaround 0.1.3__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.
- designaround-0.1.3.dist-info/METADATA +27 -0
- designaround-0.1.3.dist-info/RECORD +56 -0
- designaround-0.1.3.dist-info/WHEEL +4 -0
- designaround-0.1.3.dist-info/entry_points.txt +3 -0
- designaround_cli/__init__.py +1 -0
- designaround_cli/__main__.py +3 -0
- designaround_cli/batch.py +146 -0
- designaround_cli/bundle/agents/package-lock.json +1555 -0
- designaround_cli/bundle/agents/package.json +25 -0
- designaround_cli/bundle/agents/src/assistant.ts +514 -0
- designaround_cli/bundle/agents/src/chat-cli.ts +117 -0
- designaround_cli/bundle/agents/src/design.ts +791 -0
- designaround_cli/bundle/agents/src/engine.ts +79 -0
- designaround_cli/bundle/agents/src/env.ts +21 -0
- designaround_cli/bundle/agents/src/main.ts +131 -0
- designaround_cli/bundle/agents/src/prompts.ts +59 -0
- designaround_cli/bundle/agents/src/runtime.ts +205 -0
- designaround_cli/bundle/agents/src/team.ts +306 -0
- designaround_cli/bundle/agents/src/tools.ts +141 -0
- designaround_cli/bundle/agents/src/ui-server.ts +576 -0
- designaround_cli/bundle/agents/ui/app.css +373 -0
- designaround_cli/bundle/agents/ui/app.js +1208 -0
- designaround_cli/bundle/agents/ui/art/welcome.jpg +0 -0
- designaround_cli/bundle/agents/ui/favicon.png +0 -0
- designaround_cli/bundle/agents/ui/fonts/JETBRAINS-MONO-LICENSE.txt +93 -0
- designaround_cli/bundle/agents/ui/fonts/JetBrainsMono.woff2 +0 -0
- designaround_cli/bundle/agents/ui/fonts/MONA-SANS-LICENSE.txt +93 -0
- designaround_cli/bundle/agents/ui/fonts/MonaSans.woff2 +0 -0
- designaround_cli/bundle/agents/ui/i18n.js +355 -0
- designaround_cli/bundle/agents/ui/icons.js +2 -0
- designaround_cli/bundle/agents/ui/index.html +178 -0
- designaround_cli/bundle/agents/ui/mark.svg +1 -0
- designaround_cli/bundle/agents/ui/markdown.js +64 -0
- designaround_cli/bundle/skill/SKILL.md +136 -0
- designaround_cli/bundle/skill/assets/constraints.template.yaml +25 -0
- designaround_cli/bundle/skill/references/cadquery.md +67 -0
- designaround_cli/bundle/skill/references/constraints.md +17 -0
- designaround_cli/bundle/skill/references/jurisdictions.md +41 -0
- designaround_cli/bundle/skill/references/output-format.md +35 -0
- designaround_cli/bundle/skill/references/safety.md +30 -0
- designaround_cli/bundle/skill/references/workflow.md +137 -0
- designaround_cli/bundle/skill/scripts/da.py +51 -0
- designaround_cli/bundles.py +114 -0
- designaround_cli/client.py +76 -0
- designaround_cli/config.py +176 -0
- designaround_cli/detached.py +73 -0
- designaround_cli/figures.py +173 -0
- designaround_cli/guarantee.py +164 -0
- designaround_cli/main.py +2050 -0
- designaround_cli/mcp.py +347 -0
- designaround_cli/monitor.py +238 -0
- designaround_cli/notify.py +104 -0
- designaround_cli/output.py +83 -0
- designaround_cli/runner.py +419 -0
- designaround_cli/screening.py +247 -0
- designaround_cli/share.py +194 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: designaround
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: DesignAround CLI + local engine: design-patent screening, design-around, patent views and filing packages. Works with Claude Code, Codex, Cursor, Gemini CLI, OpenCode, OpenClaw and Kiro via the bundled Skill.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: <3.13,>=3.11
|
|
7
|
+
Requires-Dist: designaround-core
|
|
8
|
+
Requires-Dist: designaround-engine
|
|
9
|
+
Requires-Dist: httpx==0.28.1
|
|
10
|
+
Requires-Dist: keyring==25.7.0
|
|
11
|
+
Requires-Dist: pyyaml==6.0.3
|
|
12
|
+
Requires-Dist: rich==15.0.0
|
|
13
|
+
Requires-Dist: typer==0.27.3
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# designaround (CLI + local engine)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install designaround # or: pipx install designaround
|
|
20
|
+
designaround onboard # trial key, stored in the OS keychain
|
|
21
|
+
designaround skill install claude-code # or: cursor, codex, gemini-cli, opencode, openclaw, kiro, all
|
|
22
|
+
designaround config mode host # inside an agent host; or platform / byok
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Commands print one JSON envelope with `--json` (default when stdout is not a terminal). Exit codes: 0 ok, 2 needs a person, 3 budget or plan, 4 auth, 5 invalid input, 6 error.
|
|
26
|
+
|
|
27
|
+
See the repository README for the full workflow.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
designaround_cli/__init__.py,sha256=XEqb2aiIn8fzGE68Mph4ck1FtQqsR_am0wRWvrYPffQ,22
|
|
2
|
+
designaround_cli/__main__.py,sha256=CnXNTEvTGzcH7oL7CozD_C3vDVuBfMWH6V93GuaEaDQ,29
|
|
3
|
+
designaround_cli/batch.py,sha256=qjTx7PVRxeSgj-4VpJ9pd3lm6HOIH0CWzeRs07dLe0s,7895
|
|
4
|
+
designaround_cli/bundles.py,sha256=TPEgdwiWbakc0C8MpLQEOFiIrktWfvI9ZB7SUie5c9M,4488
|
|
5
|
+
designaround_cli/client.py,sha256=KnDdcnV3Hy9yiC9kLqefJWJb4KqSg7DA-alBpaquq-E,4609
|
|
6
|
+
designaround_cli/config.py,sha256=WqEl4engoARK3kD0OF8aToud5aQYKduX7tZ3fKYVYNI,4821
|
|
7
|
+
designaround_cli/detached.py,sha256=7OU3A6m-8hrEdl32NoLpvvcJ-isHO3tvN6kzyvaSBmI,3166
|
|
8
|
+
designaround_cli/figures.py,sha256=_G7CDRbiRT2KVtEdK78ZmybNOKdxslMZDk0u_4ceZRI,7251
|
|
9
|
+
designaround_cli/guarantee.py,sha256=6onpoOhzbxcgv4XnONODmGUZ5Ii5mXaqINboo_fnQN8,9191
|
|
10
|
+
designaround_cli/main.py,sha256=DvrLxPlngjPJdJIi9PuGz1BU5Yc9-rumBFQd2vtTkNY,122474
|
|
11
|
+
designaround_cli/mcp.py,sha256=5IUtvUf5J8xtxtJ48oZAWoQOm7bmNNpe24IIys6aKcQ,18242
|
|
12
|
+
designaround_cli/monitor.py,sha256=2VImG_a_08cFBb_U71Red62VCr_VCYqYWJEWmUR3ZB8,12112
|
|
13
|
+
designaround_cli/notify.py,sha256=gEpJHSwv6f057Pc017xEc-iFlDqZTXVuR7jQaMsF964,5147
|
|
14
|
+
designaround_cli/output.py,sha256=IOnymgWO7BsRmSYML31EVPk5G_ixalJFYN_DSuumPlU,2884
|
|
15
|
+
designaround_cli/runner.py,sha256=HbqsartVOmLNWKLpde_tVhsXsIjO6zPeIXQliouLJ9Q,21008
|
|
16
|
+
designaround_cli/screening.py,sha256=TiF8SByj7J6N4O9oKlyY2GaERgOhesnGqum4qzWA3BQ,11080
|
|
17
|
+
designaround_cli/share.py,sha256=SXZFB5dQJKS4MGy6d9eB9FubKmlbmSPcS2NOX3u3OEI,9301
|
|
18
|
+
designaround_cli/bundle/skill/SKILL.md,sha256=cH4Hcb5szpuLmmfjlOeqNG3GkIs9nkLVPNSj1Fn0WNY,12140
|
|
19
|
+
designaround_cli/bundle/skill/assets/constraints.template.yaml,sha256=sk7k4wlblCxdW7qUffsOi16fz5rOlv2Na1IO9JKNicU,651
|
|
20
|
+
designaround_cli/bundle/skill/references/cadquery.md,sha256=zEmTuaZdjCQxxCKqXDztgv9EMQ0MOm8mDQcEc_XQmYM,3317
|
|
21
|
+
designaround_cli/bundle/skill/references/constraints.md,sha256=zCerA69YwEy9LSdW97jDVvuCz_NkDW5M33DTN_E61XY,1271
|
|
22
|
+
designaround_cli/bundle/skill/references/jurisdictions.md,sha256=itRue27QtBlkb2BvMV1E8PQd7twYiM3QzNYpJvxRzJA,3778
|
|
23
|
+
designaround_cli/bundle/skill/references/output-format.md,sha256=_zVUKaBzTZH2HKA9ndNE_jlc2Eskf2SLuxnVVcxwCvY,1635
|
|
24
|
+
designaround_cli/bundle/skill/references/safety.md,sha256=he0HB68Jd-vRGvxH88tI2RgiOrmPlcl5qoP45hc8iIM,2046
|
|
25
|
+
designaround_cli/bundle/skill/references/workflow.md,sha256=V9oHD1txVK3hMq-UH1S1eP8szI641I62R38LSdvIU6c,6850
|
|
26
|
+
designaround_cli/bundle/skill/scripts/da.py,sha256=K6AseZlZxAHuNIpjVx0Sh2K843960x3vxJaMyXFr_n0,1919
|
|
27
|
+
designaround_cli/bundle/agents/package-lock.json,sha256=fzXugjLfSgZZT7ldzYORRHOFdHrAL66PO4u-el4hVBc,58236
|
|
28
|
+
designaround_cli/bundle/agents/package.json,sha256=mY8knQ7ZRmnlodxfGnSoSzb7GGopnN25oKqYeH4kq9E,607
|
|
29
|
+
designaround_cli/bundle/agents/src/assistant.ts,sha256=GDoxZjXhcdpnp9VT6D1k35gczimND-oZM_qOk3FibeQ,52476
|
|
30
|
+
designaround_cli/bundle/agents/src/chat-cli.ts,sha256=l3arbRnG79_1EaHfrftqH2FFlGy6yZITKVSYRWmpwZI,5043
|
|
31
|
+
designaround_cli/bundle/agents/src/design.ts,sha256=4cIQXpU-iRGX7dfti3UcXVxgF6mq1FpdsO61oj77rNE,54443
|
|
32
|
+
designaround_cli/bundle/agents/src/engine.ts,sha256=5KSUIRf5icpJ4bJJQ7065Uh0JlFVQjgcE6b7XNRF5a8,2713
|
|
33
|
+
designaround_cli/bundle/agents/src/env.ts,sha256=vIxtmcl2_YdWuevPz4qCIa4MZlEUXSb39_NjlzNU-Ag,933
|
|
34
|
+
designaround_cli/bundle/agents/src/main.ts,sha256=gcrYt-NwezbA-qmt4YtCiYo9Y7wb8o5mDFZLSLw8fa8,5855
|
|
35
|
+
designaround_cli/bundle/agents/src/prompts.ts,sha256=CapanU9UP6mOAfRlGj60jsaaYAHUA8w1qA1ORTYob0k,4382
|
|
36
|
+
designaround_cli/bundle/agents/src/runtime.ts,sha256=10qs15UAcYNt_9g4x5JZI6jo8LUK62SvkDdw-SQU9o8,9362
|
|
37
|
+
designaround_cli/bundle/agents/src/team.ts,sha256=X2AMRng93qeBkCUjdxOCMXPSbdrfHgp-8JpiBU19rSA,16258
|
|
38
|
+
designaround_cli/bundle/agents/src/tools.ts,sha256=oiY5l92bcpzO8FU39lug0tSOpEyM52SBLNGS9CQEBb4,6750
|
|
39
|
+
designaround_cli/bundle/agents/src/ui-server.ts,sha256=8CDCOEy-mTsF-rACZzTX8k-zEUaTbGP0kMSP66sTfjE,34668
|
|
40
|
+
designaround_cli/bundle/agents/ui/app.css,sha256=V4Ctl1l5QcFppMQbBQB6TGb7rhORirhTI2X0SUNnQbw,28388
|
|
41
|
+
designaround_cli/bundle/agents/ui/app.js,sha256=g6GXCoJapg9gW07JyM2NC-lTgY7XobYKLcE_CTXl1kw,56387
|
|
42
|
+
designaround_cli/bundle/agents/ui/favicon.png,sha256=ngnx3M6_QnvqvhZsVA9D6bdhxM0xy-kzrZINVBKrka4,980
|
|
43
|
+
designaround_cli/bundle/agents/ui/i18n.js,sha256=zSCUem3VzMIu5fFS9j_0K8RyDD0gOLREbv40FarUREg,21796
|
|
44
|
+
designaround_cli/bundle/agents/ui/icons.js,sha256=JWei5nsv0Gz8UMBC8CkSIfN2UTYbXSEryLw61FFtKqE,24550
|
|
45
|
+
designaround_cli/bundle/agents/ui/index.html,sha256=VnbfzQiFmhtLO-7HsoQIcwvmD4-du1q4WmL03K1gyJg,10832
|
|
46
|
+
designaround_cli/bundle/agents/ui/mark.svg,sha256=M7m4GPEpRTU7Yu4bC-ni1gxww3XOqQqpa8t0XmX43lI,447
|
|
47
|
+
designaround_cli/bundle/agents/ui/markdown.js,sha256=Fgj9_bXBIGSWpjvJ8F6sGI5nC6zUc8ZnEkJR2kitSKM,2764
|
|
48
|
+
designaround_cli/bundle/agents/ui/art/welcome.jpg,sha256=i0VUvgDcL0Ydy2CUvCtgxMlyTJs04gWWL7ti-nt1njQ,174534
|
|
49
|
+
designaround_cli/bundle/agents/ui/fonts/JETBRAINS-MONO-LICENSE.txt,sha256=QDWBtp2sXP9AeSBeAca0Z-Vq9EnsvXJHaT3bG6r6AFs,4524
|
|
50
|
+
designaround_cli/bundle/agents/ui/fonts/JetBrainsMono.woff2,sha256=GL5FJyS_3CNsB0ypSiSaf0GoZ1LH0EqyWM6e1WUfan4,40404
|
|
51
|
+
designaround_cli/bundle/agents/ui/fonts/MONA-SANS-LICENSE.txt,sha256=QemRFJPeWJmiAza68Ttupkg7Xuo04jfy_-0iw5vcSo0,4564
|
|
52
|
+
designaround_cli/bundle/agents/ui/fonts/MonaSans.woff2,sha256=XqN0BjPlrHr1bBPX6OwyvK4S53I-U9NChEaaiUfMsvM,98124
|
|
53
|
+
designaround-0.1.3.dist-info/METADATA,sha256=1B0uO7_urXrzo7kVBHXaOKSMNDNpwlaJcK-5_AS_2Tg,1181
|
|
54
|
+
designaround-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
55
|
+
designaround-0.1.3.dist-info/entry_points.txt,sha256=roVKAb5qM4RTr-F-D5iH04fY2Q_4rDRkkVnnsK_FIeo,90
|
|
56
|
+
designaround-0.1.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.3"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Batch screening from a CSV (FR-RISK-06, FR-CASE-08, FR-CLI-07).
|
|
2
|
+
|
|
3
|
+
Columns (header row, UTF-8; only ``name`` and ``article`` are required):
|
|
4
|
+
|
|
5
|
+
name, article, article_zh, jurisdictions ("US;CN"), image (path), cad (STEP/IGES path), case
|
|
6
|
+
|
|
7
|
+
Each row becomes a case (or reuses ``case``), is screened on the server, and, when a
|
|
8
|
+
3D version exists (``cad`` column or an existing case), ranked locally. Progress is
|
|
9
|
+
kept in ``<csv>.state.json`` next to the CSV, so a second run continues where the
|
|
10
|
+
first stopped and retries only failed rows. The whole run has one credit cap,
|
|
11
|
+
checked against the estimate before anything is charged.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import csv
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import threading
|
|
20
|
+
import time
|
|
21
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from designaround_core import catalog
|
|
25
|
+
from designaround_engine import api
|
|
26
|
+
from designaround_engine.errors import EngineError
|
|
27
|
+
|
|
28
|
+
from .output import EXIT_BUDGET, EXIT_INVALID, CliExit
|
|
29
|
+
|
|
30
|
+
MAX_ROWS = 1000
|
|
31
|
+
OFFICES = {"US", "CN", "EU"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _key(row: dict) -> str:
|
|
35
|
+
raw = json.dumps({k: (row.get(k) or "").strip() for k in ("name", "article", "image", "cad", "case")}, sort_keys=True, ensure_ascii=False)
|
|
36
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def read_rows(path: Path) -> list[dict]:
|
|
40
|
+
if not path.exists():
|
|
41
|
+
raise CliExit(EXIT_INVALID, "not_found", f"CSV not found: {path}")
|
|
42
|
+
with path.open(encoding="utf-8-sig", newline="") as fh:
|
|
43
|
+
rows = [{(k or "").strip().lower(): (v or "").strip() for k, v in r.items()} for r in csv.DictReader(fh)]
|
|
44
|
+
rows = [r for r in rows if any(r.values())]
|
|
45
|
+
if not rows:
|
|
46
|
+
raise CliExit(EXIT_INVALID, "empty_csv", "The CSV has no rows.", next_actions=["Header: name,article,article_zh,jurisdictions,image,cad"])
|
|
47
|
+
if len(rows) > MAX_ROWS:
|
|
48
|
+
raise CliExit(EXIT_INVALID, "too_many_rows", f"{len(rows)} rows; one batch handles at most {MAX_ROWS}.")
|
|
49
|
+
bad = [i + 2 for i, r in enumerate(rows) if not (r.get("name") and (r.get("article") or r.get("case")))]
|
|
50
|
+
if bad:
|
|
51
|
+
raise CliExit(EXIT_INVALID, "missing_columns", f"Rows {bad[:10]} need name and article (or an existing case id).")
|
|
52
|
+
for r in rows:
|
|
53
|
+
r["_key"] = _key(r)
|
|
54
|
+
r["_offices"] = [o.strip().upper() for o in (r.get("jurisdictions") or "US").replace(",", ";").split(";") if o.strip().upper() in OFFICES] or ["US"]
|
|
55
|
+
for col in ("image", "cad"):
|
|
56
|
+
if r.get(col) and not Path(r[col]).expanduser().is_absolute():
|
|
57
|
+
r[col] = str((path.parent / r[col]).resolve())
|
|
58
|
+
return rows
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def estimate(rows: list[dict], deep: bool, done: set[str]) -> int:
|
|
62
|
+
per = catalog.ACTION_CREDITS["screen.deep.batch" if deep and len(rows) >= 100 else ("screen.deep" if deep else "screen.quick")]
|
|
63
|
+
return per * sum(1 for r in rows if r["_key"] not in done)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_batch(csv_path: Path, *, deep: bool, max_credits: int, concurrency: int = 2, fetch: int = 8, figures_per: int = 3, out: Path | None = None) -> dict:
|
|
67
|
+
from .screening import STATE as SCREEN_STATE
|
|
68
|
+
from .screening import run_screen
|
|
69
|
+
|
|
70
|
+
rows = read_rows(csv_path)
|
|
71
|
+
state_path = csv_path.with_suffix(csv_path.suffix + ".state.json")
|
|
72
|
+
state = json.loads(state_path.read_text(encoding="utf-8")) if state_path.exists() else {"rows": {}}
|
|
73
|
+
done = {k for k, v in state["rows"].items() if v.get("status") == "done"}
|
|
74
|
+
est = estimate(rows, deep, done)
|
|
75
|
+
if est > max_credits:
|
|
76
|
+
raise CliExit(EXIT_BUDGET, "budget_exceeded", f"Estimated {est} credits for {len(rows) - len(done)} rows exceeds --max-credits {max_credits}.", data={"estimate": est, "rows": len(rows), "done": len(done)}, next_actions=[f"Re-run with --max-credits {est}", "Use quick screening (drop --deep) for a first pass"])
|
|
77
|
+
lock = threading.Lock()
|
|
78
|
+
SCREEN_STATE["quiet"] = True
|
|
79
|
+
|
|
80
|
+
def save() -> None:
|
|
81
|
+
tmp = state_path.with_suffix(".tmp")
|
|
82
|
+
tmp.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
83
|
+
tmp.replace(state_path)
|
|
84
|
+
|
|
85
|
+
def one(row: dict) -> dict:
|
|
86
|
+
record = {"name": row["name"], "status": "running", "started": time.time(), "error": None}
|
|
87
|
+
try:
|
|
88
|
+
case_id = row.get("case") or (state["rows"].get(row["_key"]) or {}).get("case")
|
|
89
|
+
if not case_id:
|
|
90
|
+
case_id = api.case_create(row["name"], article=row.get("article") or None, article_zh=row.get("article_zh") or None, jurisdictions=row["_offices"])["id"]
|
|
91
|
+
api.case_update(case_id, status="batch")
|
|
92
|
+
record["case"] = case_id
|
|
93
|
+
with lock:
|
|
94
|
+
state["rows"][row["_key"]] = {**(state["rows"].get(row["_key"]) or {}), **record}
|
|
95
|
+
save()
|
|
96
|
+
if row.get("cad") and not api.case_show(case_id).get("original"):
|
|
97
|
+
api.import_model_file(case_id, row["cad"])
|
|
98
|
+
image = Path(row["image"]).expanduser() if row.get("image") else None
|
|
99
|
+
if image is not None and not image.exists():
|
|
100
|
+
raise CliExit(EXIT_INVALID, "not_found", f"Image not found: {image}")
|
|
101
|
+
data = run_screen(case_id, image=image, query=None, deep=deep, jurisdictions=row["_offices"], fetch=fetch, figures_per=figures_per, version=None, hires=2 if deep else 0)
|
|
102
|
+
ranked = data.get("ranked") or []
|
|
103
|
+
record.update({
|
|
104
|
+
"status": "done",
|
|
105
|
+
"candidates": data.get("candidates", 0),
|
|
106
|
+
"prior_art_added": len(data.get("added") or []),
|
|
107
|
+
"top": [{"number": r.get("number"), "shape": round((r.get("shape") or {}).get("score", r.get("shape_score", 0)) or 0, 3)} for r in ranked[:3]],
|
|
108
|
+
"version": data.get("version"),
|
|
109
|
+
"charged": data.get("charged", 0),
|
|
110
|
+
"source_failures": len(data.get("source_failures") or []),
|
|
111
|
+
})
|
|
112
|
+
except CliExit as exc:
|
|
113
|
+
record.update({"status": "failed", "error": f"{exc.kind}: {exc.message}"[:300]})
|
|
114
|
+
if exc.code == EXIT_BUDGET:
|
|
115
|
+
record["budget"] = True
|
|
116
|
+
except EngineError as exc:
|
|
117
|
+
record.update({"status": "failed", "error": f"{exc.code}: {exc}"[:300]})
|
|
118
|
+
except Exception as exc: # noqa: BLE001 - one bad row must not stop the batch
|
|
119
|
+
record.update({"status": "failed", "error": f"{type(exc).__name__}: {exc}"[:300]})
|
|
120
|
+
record["finished"] = time.time()
|
|
121
|
+
with lock:
|
|
122
|
+
state["rows"][row["_key"]] = {**(state["rows"].get(row["_key"]) or {}), **record}
|
|
123
|
+
save()
|
|
124
|
+
return record
|
|
125
|
+
|
|
126
|
+
todo = [r for r in rows if r["_key"] not in done]
|
|
127
|
+
spent = 0
|
|
128
|
+
stopped = False
|
|
129
|
+
with ThreadPoolExecutor(max_workers=max(1, min(concurrency, 6))) as pool:
|
|
130
|
+
futures = {pool.submit(one, r): r for r in todo}
|
|
131
|
+
for future in as_completed(futures):
|
|
132
|
+
rec = future.result()
|
|
133
|
+
spent += int(rec.get("charged") or 0)
|
|
134
|
+
if rec.get("budget"):
|
|
135
|
+
stopped = True
|
|
136
|
+
results = []
|
|
137
|
+
for r in rows:
|
|
138
|
+
rec = state["rows"].get(r["_key"]) or {"status": "pending"}
|
|
139
|
+
results.append({"name": r["name"], "case": rec.get("case"), "status": rec.get("status"), "candidates": rec.get("candidates"), "top": "; ".join(f"{t['number']} ({t['shape']})" for t in rec.get("top") or []), "error": rec.get("error")})
|
|
140
|
+
target = out or csv_path.with_name(csv_path.stem + "-results.csv")
|
|
141
|
+
with target.open("w", encoding="utf-8-sig", newline="") as fh:
|
|
142
|
+
writer = csv.DictWriter(fh, fieldnames=["name", "case", "status", "candidates", "top", "error"])
|
|
143
|
+
writer.writeheader()
|
|
144
|
+
writer.writerows(results)
|
|
145
|
+
counts = {s: sum(1 for x in results if x["status"] == s) for s in ("done", "failed", "pending")}
|
|
146
|
+
return {"rows": len(rows), "counts": counts, "estimate": est, "charged_this_run": spent, "stopped_on_budget": stopped, "results_csv": str(target), "state": str(state_path), "results": results}
|