atsfill 0.0.5.dev139__tar.gz → 0.0.5.dev141__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.
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/PKG-INFO +4 -5
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/README.md +3 -4
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/pyproject.toml +1 -1
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/pyproject.toml.orig +1 -1
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/commands.py +1 -26
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/registry.py +33 -100
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/results.py +1 -5
- atsfill-0.0.5.dev141/src/atsfill/registry.toml +385 -0
- atsfill-0.0.5.dev139/src/atsfill/registry.toml +0 -442
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/LICENSE +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/__init__.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/__init__.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/ashby.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/bamboohr.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/breezy.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/dayforce.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/eightfold.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/greenhouse.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/gupy.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hireology.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hiringthing.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hrmdirect.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/isolvedhire.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/jazzhr.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/join.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/lever.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/manatal.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/oracle.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/paycor.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/paylocity.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/personio.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/phenom.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/pinpoint.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/recruitee.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/rippling.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/smartrecruiters.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/successfactors.js +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/successfactors.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/taleo.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/teamtailor.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/ukg.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/workable.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/workday.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/wttj.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/zohorecruit.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/cli.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/__init__.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/accounts.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/adapter.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/attempts.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/browser.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/conditions.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/html.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/matching.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/otp.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/passwords.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/paths.py +0 -0
- {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atsfill
|
|
3
|
-
Version: 0.0.5.
|
|
3
|
+
Version: 0.0.5.dev141
|
|
4
4
|
Summary: Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle.
|
|
5
5
|
Keywords: job-applications,ats,ashby,greenhouse,teamtailor,ai-agents,cli
|
|
6
6
|
Author: shbernal
|
|
@@ -57,10 +57,10 @@ It is built for agents with a shell: every command prints one JSON document and
|
|
|
57
57
|
## Supported hiring systems
|
|
58
58
|
|
|
59
59
|
<!-- badges -->
|
|
60
|
-
                             
|
|
61
61
|
<!-- /badges -->
|
|
62
62
|
|
|
63
|
-
Each badge is a hiring system atsfill has an adapter for
|
|
63
|
+
Each badge is a hiring system atsfill has an adapter for: `supported` once a dry run passed on five employers, with its date.
|
|
64
64
|
[The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
|
|
65
65
|
Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
|
|
66
66
|
|
|
@@ -127,8 +127,7 @@ Every push to `main` that passes CI is also published as a dev release; `uvx --p
|
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
A dry run is not invisible: some systems save answers or the resume as they are typed.
|
|
130
|
-
`atsfill status` lists each system's side effects, whether it is allowed to submit, and
|
|
131
|
-
A dry run or a `send` through an adapter no send was verified on carries a `warning`; the send goes ahead anyway.
|
|
130
|
+
`atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
|
|
132
131
|
|
|
133
132
|
One home can send for more than one user: `--user <name>` gives each their own sessions, double-send guard and `[users.<name>]` section in the config file ([ADR 0015](docs/adr/0015-users-and-accounts.md)).
|
|
134
133
|
Given an account's email (`atsfill login <adapter> --email`) and a password source (a `[password-commands]` entry such as `rbw get example.com {email}`, or `--password -` on stdin), atsfill signs in by itself, and again whenever a form command finds the session signed out.
|
|
@@ -29,10 +29,10 @@ It is built for agents with a shell: every command prints one JSON document and
|
|
|
29
29
|
## Supported hiring systems
|
|
30
30
|
|
|
31
31
|
<!-- badges -->
|
|
32
|
-
                             
|
|
33
33
|
<!-- /badges -->
|
|
34
34
|
|
|
35
|
-
Each badge is a hiring system atsfill has an adapter for
|
|
35
|
+
Each badge is a hiring system atsfill has an adapter for: `supported` once a dry run passed on five employers, with its date.
|
|
36
36
|
[The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
|
|
37
37
|
Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
|
|
38
38
|
|
|
@@ -99,8 +99,7 @@ Every push to `main` that passes CI is also published as a dev release; `uvx --p
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
A dry run is not invisible: some systems save answers or the resume as they are typed.
|
|
102
|
-
`atsfill status` lists each system's side effects, whether it is allowed to submit, and
|
|
103
|
-
A dry run or a `send` through an adapter no send was verified on carries a `warning`; the send goes ahead anyway.
|
|
102
|
+
`atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
|
|
104
103
|
|
|
105
104
|
One home can send for more than one user: `--user <name>` gives each their own sessions, double-send guard and `[users.<name>]` section in the config file ([ADR 0015](docs/adr/0015-users-and-accounts.md)).
|
|
106
105
|
Given an account's email (`atsfill login <adapter> --email`) and a password source (a `[password-commands]` entry such as `rbw get example.com {email}`, or `--password -` on stdin), atsfill signs in by itself, and again whenever a form command finds the session signed out.
|
|
@@ -324,7 +324,6 @@ def _form_command(
|
|
|
324
324
|
result = _fill(page, adapter, url, answers, user)
|
|
325
325
|
if result.status is not Status.READY:
|
|
326
326
|
return result
|
|
327
|
-
result.warning = _unverified(adapter.name)
|
|
328
327
|
if not send:
|
|
329
328
|
return result
|
|
330
329
|
attempts.record(mine, posting, attempts.PENDING, url=url)
|
|
@@ -398,18 +397,6 @@ def send(
|
|
|
398
397
|
)
|
|
399
398
|
|
|
400
399
|
|
|
401
|
-
def _unverified(adapter: str) -> str | None:
|
|
402
|
-
"""The warning a ready dry run or a send carries when no send through the adapter was
|
|
403
|
-
verified, or the last verified one fails now (ADR 0012)."""
|
|
404
|
-
source = registry.for_adapter(adapter)
|
|
405
|
-
if source and source.send_broken:
|
|
406
|
-
return f"send_broken: {_broken(source)}"
|
|
407
|
-
if source and source.send_verified:
|
|
408
|
-
return None
|
|
409
|
-
verification = source.verification if source else None
|
|
410
|
-
return f"not_send_verified: {adapter} is {verification or 'not in the registry'}"
|
|
411
|
-
|
|
412
|
-
|
|
413
400
|
def _sessions(data: Path, users: list[str], candidates: list[Adapter]) -> list[SessionReport]:
|
|
414
401
|
"""Every session a user stored, each asked of its system."""
|
|
415
402
|
return [
|
|
@@ -439,26 +426,14 @@ def status(*, data_dir: Path | None = None) -> StatusReport:
|
|
|
439
426
|
)
|
|
440
427
|
|
|
441
428
|
|
|
442
|
-
def _broken(source: registry.Source) -> str:
|
|
443
|
-
broken, last = source.send_broken, source.send_verified
|
|
444
|
-
assert broken
|
|
445
|
-
since = f"send broken since {broken.date} ({broken.version}), {broken.reason}"
|
|
446
|
-
return since + (f"; last verified {last.date} ({last.version})" if last else "")
|
|
447
|
-
|
|
448
|
-
|
|
449
429
|
def _adapter_status(adapter: Adapter, users: list[str]) -> AdapterStatus:
|
|
450
430
|
source = registry.for_adapter(adapter.name)
|
|
451
|
-
verified = source.verification if source else None
|
|
452
|
-
v = getattr(source, verified) if source and verified else None
|
|
453
431
|
return AdapterStatus(
|
|
454
432
|
adapter.name,
|
|
455
433
|
submit_allowed=[u for u in users if paths.submit_enabled(adapter.name, u)],
|
|
456
434
|
side_effects=adapter.side_effects,
|
|
457
435
|
side_effects_muted=[u for u in users if paths.muted(adapter.name, u)],
|
|
458
|
-
|
|
459
|
-
verified_version=v.version if v else None,
|
|
460
|
-
verified_release=registry.release(v.version, version("atsfill")) if v else None,
|
|
461
|
-
send_broken=_broken(source) if source and source.send_broken else None,
|
|
436
|
+
scope=source.scope.value if source else None,
|
|
462
437
|
)
|
|
463
438
|
|
|
464
439
|
|
|
@@ -1,61 +1,31 @@
|
|
|
1
1
|
"""The committed registry of freehire sources (ADR 0012)."""
|
|
2
2
|
|
|
3
|
+
import datetime
|
|
3
4
|
import tomllib
|
|
4
5
|
from dataclasses import dataclass, fields
|
|
5
6
|
from datetime import date
|
|
6
7
|
from functools import cache
|
|
7
8
|
from importlib.resources import files
|
|
8
9
|
|
|
9
|
-
from packaging.version import Version
|
|
10
|
-
|
|
11
10
|
KINDS = ("ats", "board", "aggregator", "custom")
|
|
12
11
|
ACCOUNTS = ("none", "to_send", "to_read") # also the backlog order (ADR 0013)
|
|
13
12
|
HOSTING = ("hosted", "embedded")
|
|
14
13
|
VENDORS = ("DataDome", "Cloudflare", "Akamai", "PerimeterX", "hCaptcha", "unknown")
|
|
15
14
|
STEPS = ("posting", "form", "upload")
|
|
16
|
-
|
|
17
|
-
CAUSES = ("system", "code", "unknown")
|
|
18
|
-
RELEASES = ("this_release", "older", "newer")
|
|
15
|
+
SCOPES = ("out_of_scope", "in_scope", "supported")
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
@dataclass(frozen=True)
|
|
22
|
-
class
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
class Scope:
|
|
20
|
+
"""Where a source stands (ADR 0016). `supported`: a dev dry run passed at `commit`, never
|
|
21
|
+
demoted. `in_scope`: wanted, its `reason` the known gap a round stopped at, if one did.
|
|
22
|
+
`out_of_scope`: never filled, it needs the user live (ADR 0014). A `reason` names fields
|
|
23
|
+
and steps, never postings."""
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
date: date
|
|
34
|
-
version: str
|
|
35
|
-
commit: str
|
|
36
|
-
by: str
|
|
37
|
-
reason: str
|
|
38
|
-
cause: str = "unknown"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass(frozen=True)
|
|
42
|
-
class Shortfall:
|
|
43
|
-
"""A round that ended without an adapter passing the adapter gate (ADR 0013): still wanted.
|
|
44
|
-
`reason` names the known gap that stopped it, never postings."""
|
|
45
|
-
|
|
46
|
-
date: date
|
|
47
|
-
version: str
|
|
48
|
-
commit: str
|
|
49
|
-
by: str
|
|
50
|
-
reason: str
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass(frozen=True)
|
|
54
|
-
class OutOfScope:
|
|
55
|
-
"""A source atsfill will never fill: it needs the user live (ADR 0014)."""
|
|
56
|
-
|
|
57
|
-
reason: str
|
|
58
|
-
date: date
|
|
25
|
+
value: str = "in_scope"
|
|
26
|
+
date: datetime.date | None = None
|
|
27
|
+
commit: str | None = None
|
|
28
|
+
reason: str | None = None
|
|
59
29
|
|
|
60
30
|
|
|
61
31
|
@dataclass(frozen=True)
|
|
@@ -79,17 +49,7 @@ class Source:
|
|
|
79
49
|
share: float | None = None
|
|
80
50
|
links_to: tuple[str, ...] = () # an aggregator's: the sources its postings link to
|
|
81
51
|
block: Block | None = None
|
|
82
|
-
|
|
83
|
-
dry_run_verified: Verification | None = None
|
|
84
|
-
send_verified: Verification | None = None # the last known good, kept through a break
|
|
85
|
-
send_broken: Broken | None = None
|
|
86
|
-
unsupported: Shortfall | None = None
|
|
87
|
-
out_of_scope: OutOfScope | None = None
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
def verification(self) -> str | None:
|
|
91
|
-
"""The furthest verification reached; each one implies the ones before it."""
|
|
92
|
-
return next((s for s in reversed(VERIFICATIONS) if getattr(self, s)), None)
|
|
52
|
+
scope: Scope = Scope()
|
|
93
53
|
|
|
94
54
|
|
|
95
55
|
def _one_of(name: str, key: str, value: object, allowed: tuple[str, ...]) -> None:
|
|
@@ -112,13 +72,8 @@ def _source(name: str, entry: dict) -> Source:
|
|
|
112
72
|
_one_of(name, "block.vendor", block.get("vendor"), VENDORS)
|
|
113
73
|
_one_of(name, "block.step", block.get("step"), STEPS)
|
|
114
74
|
block = Block(**block)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if broken is not None:
|
|
118
|
-
_one_of(name, "send_broken.cause", broken.get("cause", "unknown"), CAUSES)
|
|
119
|
-
broken = Broken(**broken)
|
|
120
|
-
unsupported = entry.get("unsupported")
|
|
121
|
-
out_of_scope = entry.get("out_of_scope")
|
|
75
|
+
scope = dict(entry.get("scope", {"is": "in_scope"}))
|
|
76
|
+
_one_of(name, "scope.is", scope.get("is"), SCOPES)
|
|
122
77
|
return Source(
|
|
123
78
|
name,
|
|
124
79
|
entry["kind"],
|
|
@@ -128,10 +83,7 @@ def _source(name: str, entry: dict) -> Source:
|
|
|
128
83
|
entry.get("share"),
|
|
129
84
|
tuple(entry.get("links_to", [])),
|
|
130
85
|
block,
|
|
131
|
-
**
|
|
132
|
-
send_broken=broken,
|
|
133
|
-
unsupported=Shortfall(**unsupported) if unsupported is not None else None,
|
|
134
|
-
out_of_scope=OutOfScope(**out_of_scope) if out_of_scope is not None else None,
|
|
86
|
+
Scope(scope.pop("is"), **scope),
|
|
135
87
|
)
|
|
136
88
|
|
|
137
89
|
|
|
@@ -148,52 +100,40 @@ def _toml(value: object) -> str:
|
|
|
148
100
|
return str(value) if isinstance(value, date) else f'"{value}"'
|
|
149
101
|
|
|
150
102
|
|
|
151
|
-
def
|
|
152
|
-
text
|
|
153
|
-
) -> str:
|
|
154
|
-
"""The registry file's text with `key` of `source` set to `value`, the rest untouched.
|
|
155
|
-
Writing `send_verified` ends a `send_broken`."""
|
|
103
|
+
def with_scope(text: str, source: str, scope: Scope) -> str:
|
|
104
|
+
"""The registry file's text with `source`'s scope set, the rest untouched."""
|
|
156
105
|
lines = text.splitlines()
|
|
157
106
|
start = lines.index(f"[{source}]")
|
|
158
107
|
end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("[")), len(lines))
|
|
159
108
|
while lines[end - 1] == "":
|
|
160
109
|
end -= 1
|
|
161
|
-
|
|
162
|
-
entry = lines[start:end]
|
|
163
|
-
at = next((i for i, x in enumerate(entry) if x.split(" =")[0] in gone), len(entry))
|
|
164
|
-
entry = [x for x in entry if x.split(" =")[0] not in gone]
|
|
110
|
+
entry = [x for x in lines[start:end] if not x.startswith("scope =")]
|
|
165
111
|
pairs = ", ".join(
|
|
166
|
-
f"{f.name} = {_toml(getattr(
|
|
167
|
-
for f in fields(
|
|
168
|
-
if getattr(
|
|
112
|
+
f"{'is' if f.name == 'value' else f.name} = {_toml(getattr(scope, f.name))}"
|
|
113
|
+
for f in fields(scope)
|
|
114
|
+
if getattr(scope, f.name) is not None
|
|
169
115
|
)
|
|
170
|
-
entry.
|
|
116
|
+
entry.append(f"scope = {{ {pairs} }}")
|
|
171
117
|
return "\n".join(lines[:start] + entry + lines[end:]) + "\n"
|
|
172
118
|
|
|
173
119
|
|
|
174
|
-
def release(verified: str, installed: str) -> str:
|
|
175
|
-
"""Whether a verified release is the installed one, `older` or `newer`."""
|
|
176
|
-
a, b = (Version(Version(v).public) for v in (verified, installed))
|
|
177
|
-
return "this_release" if a == b else "older" if a < b else "newer"
|
|
178
|
-
|
|
179
|
-
|
|
180
120
|
def for_adapter(name: str) -> Source | None:
|
|
181
121
|
return next((s for s in sources().values() if s.adapter == name), None)
|
|
182
122
|
|
|
183
123
|
|
|
184
124
|
def _backlog_key(s: Source) -> tuple:
|
|
185
|
-
stopped = s.block is not None or s.
|
|
125
|
+
stopped = s.block is not None or s.scope.reason is not None
|
|
186
126
|
return (stopped, ACCOUNTS.index(s.account), -(s.share or 0), s.name)
|
|
187
127
|
|
|
188
128
|
|
|
189
129
|
def _row(s: Source) -> str:
|
|
190
130
|
share = f"{s.share:.1%}" if s.share is not None else ""
|
|
191
131
|
block = f"{s.block.vendor} at {s.block.step}" if s.block else ""
|
|
192
|
-
v =
|
|
193
|
-
if s.
|
|
194
|
-
v
|
|
132
|
+
v = s.scope.value.replace("_", " ")
|
|
133
|
+
if s.scope.reason:
|
|
134
|
+
v += f": {s.scope.reason}"
|
|
195
135
|
if s.adapter:
|
|
196
|
-
v = f"[{v}]({s.adapter}.md)"
|
|
136
|
+
v = f"[{v}]({s.adapter}.md)"
|
|
197
137
|
kind = f"{s.kind} → {', '.join(s.links_to)}" if s.links_to else s.kind
|
|
198
138
|
cells = [s.name, kind, share, s.account, ", ".join(s.hosting), block, v]
|
|
199
139
|
return "| " + " | ".join(cells) + " |"
|
|
@@ -202,9 +142,9 @@ def _row(s: Source) -> str:
|
|
|
202
142
|
def backlog(registry: dict[str, Source] | None = None) -> str:
|
|
203
143
|
"""`docs/adapters/README.md`: the systems in backlog order (ADR 0013)."""
|
|
204
144
|
registry = sources() if registry is None else registry
|
|
205
|
-
columns = ("Source", "Kind", "Share", "Account", "Hosting", "Block", "
|
|
145
|
+
columns = ("Source", "Kind", "Share", "Account", "Hosting", "Block", "Scope")
|
|
206
146
|
head = f"| {' | '.join(columns)} |\n" + "|---" * len(columns) + "|"
|
|
207
|
-
wanted = lambda s: s.kind in ("ats", "board") and
|
|
147
|
+
wanted = lambda s: s.kind in ("ats", "board") and s.scope.value != "out_of_scope" # noqa: E731
|
|
208
148
|
adapters = sorted((s for s in registry.values() if wanted(s)), key=_backlog_key)
|
|
209
149
|
others = sorted((s for s in registry.values() if not wanted(s)), key=_backlog_key)
|
|
210
150
|
text = [
|
|
@@ -214,7 +154,7 @@ def backlog(registry: dict[str, Source] | None = None) -> str:
|
|
|
214
154
|
" `uv run python -m scripts.backlog`. The registry is described in"
|
|
215
155
|
" [ADR 0012](../adr/0012-systems-registry.md), the order in"
|
|
216
156
|
" [ADR 0013](../adr/0013-survey-and-adapter-rounds.md): no account first, blocked and"
|
|
217
|
-
"
|
|
157
|
+
" stopped by a known gap last, then by share of postings.",
|
|
218
158
|
"",
|
|
219
159
|
f"The registry covers {sum(s.share or 0 for s in registry.values()):.1%} of postings.",
|
|
220
160
|
"",
|
|
@@ -234,19 +174,12 @@ def backlog(registry: dict[str, Source] | None = None) -> str:
|
|
|
234
174
|
|
|
235
175
|
|
|
236
176
|
BADGES = ("<!-- badges -->", "<!-- /badges -->")
|
|
237
|
-
COLORS = {"
|
|
177
|
+
COLORS = {"supported": "9ece6a", "in_scope": "e0af68", "out_of_scope": "565f89"}
|
|
238
178
|
|
|
239
179
|
|
|
240
180
|
def _badge(s: Source) -> str:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
elif s.unsupported:
|
|
244
|
-
message, color = f"unsupported {s.unsupported.date}", "e0af68"
|
|
245
|
-
elif s.verification:
|
|
246
|
-
v: Verification = getattr(s, s.verification)
|
|
247
|
-
message, color = f"{s.verification.replace('_', ' ')} {v.date}", COLORS[s.verification]
|
|
248
|
-
else:
|
|
249
|
-
message, color = "unverified", "565f89"
|
|
181
|
+
message = " ".join(str(x) for x in (s.scope.value.replace("_", " "), s.scope.date) if x)
|
|
182
|
+
color = COLORS[s.scope.value]
|
|
250
183
|
escape = lambda t: t.replace("-", "--").replace("_", "__").replace(" ", "%20") # noqa: E731
|
|
251
184
|
url = f"https://img.shields.io/badge/{escape(s.adapter or s.name)}-{escape(message)}-{color}"
|
|
252
185
|
return f""
|
|
@@ -101,7 +101,6 @@ class Result:
|
|
|
101
101
|
checks: list[Check] = field(default_factory=list)
|
|
102
102
|
side_effects: list[SideEffect] = field(default_factory=list)
|
|
103
103
|
evidence: str | None = None
|
|
104
|
-
warning: str | None = None # `code: detail`, on a ready dry run or a send that went ahead
|
|
105
104
|
|
|
106
105
|
|
|
107
106
|
@dataclass
|
|
@@ -122,10 +121,7 @@ class AdapterStatus:
|
|
|
122
121
|
submit_allowed: list[str] # the users whose config lets it submit
|
|
123
122
|
side_effects: list[SideEffect]
|
|
124
123
|
side_effects_muted: list[str] # the users who muted them
|
|
125
|
-
|
|
126
|
-
verified_version: str | None = None
|
|
127
|
-
verified_release: str | None = None # this_release | older | newer than the installed one
|
|
128
|
-
send_broken: str | None = None # since when and why, with the last verified send
|
|
124
|
+
scope: str | None = None # supported | in_scope | out_of_scope, None when not in the registry
|
|
129
125
|
|
|
130
126
|
|
|
131
127
|
@dataclass
|