roadmodel 0.2.63__tar.gz → 0.2.64__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.
- {roadmodel-0.2.63 → roadmodel-0.2.64}/PKG-INFO +1 -1
- {roadmodel-0.2.63 → roadmodel-0.2.64}/pyproject.toml +1 -1
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/__init__.py +1 -1
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/recommend.py +31 -4
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/scoring.py +97 -22
- {roadmodel-0.2.63 → roadmodel-0.2.64}/.gitignore +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/LICENSE +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/NOTICE +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/README.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/benchmarks.json +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/catalog.json +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/model-selector.txt +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/settings-display.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/templates/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/templates/planning-kit-how-to-use.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/templates/project-roadmap-template.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/templates/prompt-phase-roadmap.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/templates/prompt-project-roadmap.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/docs/user-context.example.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/hatch_build.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/infra/README.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/infra/gcp-killswitch/README.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/infra/supabase/README.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/benchmarks.json +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/catalog.json +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/model-selector.txt +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/planning-kit-how-to-use.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/project-roadmap-template.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/prompt-phase-roadmap.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/prompt-project-roadmap.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/settings-display.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/roadmodel/data/user-context.example.md +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/scripts/update_projects.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/__main__.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/cli.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/config.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/cost.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/errors.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/mcp_server.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/__init__.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/anthropic.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/google.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/openai.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/openai_compatible.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/providers/registry.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/usage.py +0 -0
- {roadmodel-0.2.63 → roadmodel-0.2.64}/src/roadmodel/user_context.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: roadmodel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.64
|
|
4
4
|
Summary: BYO-key CLI that recommends the right AI model, platform, and settings for a prompt.
|
|
5
5
|
Project-URL: Homepage, https://roadmodel.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/nathanramoscfa/roadmodel
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# src/roadmodel/__init__.py
|
|
2
|
-
__version__ = "0.2.
|
|
2
|
+
__version__ = "0.2.64"
|
|
@@ -1295,7 +1295,14 @@ def _table_row_key(
|
|
|
1295
1295
|
def _rung_pick_sentence(row: scoring.Ladder, tier: str) -> str:
|
|
1296
1296
|
r = row.rungs[tier]
|
|
1297
1297
|
c = r.candidate
|
|
1298
|
-
|
|
1298
|
+
aa = "not measured" if r.point.aa_index is None else f"{r.point.aa_index:g}"
|
|
1299
|
+
facts = f"AA Index {aa}, ${c.blended_price_usd:.2f} per 1M tokens at list price"
|
|
1300
|
+
if r.specialist:
|
|
1301
|
+
return (
|
|
1302
|
+
f"{c.model_name} is the strongest model you can run for {row.task.category} work: "
|
|
1303
|
+
f"its {row.task.category} rating ({c.letter}) is above every model on your "
|
|
1304
|
+
f"cost/quality frontier ({facts})."
|
|
1305
|
+
)
|
|
1299
1306
|
if tier == "quality":
|
|
1300
1307
|
return f"{c.model_name} is the strongest model on your cost/quality frontier for {row.task.category} work ({facts})."
|
|
1301
1308
|
if tier == "cost":
|
|
@@ -1375,6 +1382,18 @@ def _attach_backup_plans(picks: dict[str, dict[str, Any]], row: scoring.Ladder)
|
|
|
1375
1382
|
}
|
|
1376
1383
|
|
|
1377
1384
|
|
|
1385
|
+
def _mark_specialist(picks: dict[str, dict[str, Any]], row: scoring.Ladder) -> None:
|
|
1386
|
+
"""Flag the QUALITY pick ``specialist`` with its category when the row
|
|
1387
|
+
puts a category specialist there and the pick still names it."""
|
|
1388
|
+
rung = row.rungs["quality"]
|
|
1389
|
+
if not rung.specialist:
|
|
1390
|
+
return
|
|
1391
|
+
if picks["quality"].get("model") != cost.canonical_model_name(rung.candidate.model_name):
|
|
1392
|
+
return
|
|
1393
|
+
picks["quality"]["specialist"] = True
|
|
1394
|
+
picks["quality"]["specialist_category"] = row.task.category
|
|
1395
|
+
|
|
1396
|
+
|
|
1378
1397
|
# Claude Code's Ultracode is a session setting above its Max level: it stands
|
|
1379
1398
|
# where the row says max.
|
|
1380
1399
|
_ULTRACODE_PLATFORM: Final = "claude-code"
|
|
@@ -1457,9 +1476,11 @@ def recommend_structured_ladder(
|
|
|
1457
1476
|
whose dial the catalog documents (:func:`_enforce_table_effort`), and
|
|
1458
1477
|
BACKUP included where the row names one (:func:`_enforce_table_backup`;
|
|
1459
1478
|
such a pick also carries a ``backup_plan`` with the backup's platform and
|
|
1460
|
-
effort).
|
|
1461
|
-
|
|
1462
|
-
|
|
1479
|
+
effort). A QUALITY pick the row gives a category specialist carries
|
|
1480
|
+
``specialist: True`` and ``specialist_category``. ``guard`` then reports
|
|
1481
|
+
``mode: "frontier"``, the row, how it was found, the tiers rewritten, the
|
|
1482
|
+
tiers whose effort was set, the tiers whose backup was set and the
|
|
1483
|
+
specialist (or None), and is healthy: two rungs on one model are the
|
|
1463
1484
|
table's answer, not a collapse.
|
|
1464
1485
|
Without a table, or a response the table cannot place, the engine's own
|
|
1465
1486
|
picks stand under the tier-distinctness guard as before.
|
|
@@ -1509,6 +1530,11 @@ def recommend_structured_ladder(
|
|
|
1509
1530
|
"effort_set": _enforce_table_effort(ladder, row),
|
|
1510
1531
|
"backup_set": _enforce_table_backup(ladder, row),
|
|
1511
1532
|
"frontier": [p.candidate.model_name for p in row.frontier],
|
|
1533
|
+
"specialist": (
|
|
1534
|
+
{"tier": "quality", "category": row.task.category}
|
|
1535
|
+
if row.rungs["quality"].specialist
|
|
1536
|
+
else None
|
|
1537
|
+
),
|
|
1512
1538
|
}
|
|
1513
1539
|
else:
|
|
1514
1540
|
frontier_guard = {"mode": "frontier", "classification": None, "found": found}
|
|
@@ -1524,6 +1550,7 @@ def recommend_structured_ladder(
|
|
|
1524
1550
|
}
|
|
1525
1551
|
if frontier_guard is not None and frontier_guard["classification"] is not None:
|
|
1526
1552
|
_attach_backup_plans(picks, table[frontier_guard["classification"]])
|
|
1553
|
+
_mark_specialist(picks, table[frontier_guard["classification"]])
|
|
1527
1554
|
guard = _ladder_tier_guard(picks)
|
|
1528
1555
|
if frontier_guard is not None:
|
|
1529
1556
|
guard.update(frontier_guard)
|
|
@@ -1016,7 +1016,9 @@ FRONTIER_INDEX: Final[str] = "artificial_analysis_intelligence_index"
|
|
|
1016
1016
|
@dataclass(frozen=True)
|
|
1017
1017
|
class FrontierPoint:
|
|
1018
1018
|
candidate: Candidate
|
|
1019
|
-
|
|
1019
|
+
# The AA Intelligence Index; every frontier point has one, and only a
|
|
1020
|
+
# category specialist (off the frontier) may be unmeasured.
|
|
1021
|
+
aa_index: float | None
|
|
1020
1022
|
# Blended list $/1M, the price /models plots. List price, not what the
|
|
1021
1023
|
# operator pays: on a subscription every covered model costs them $0, which
|
|
1022
1024
|
# would collapse the frontier to its top model; list price is what a pick
|
|
@@ -1054,6 +1056,8 @@ class Rung:
|
|
|
1054
1056
|
# The best cross-maker substitute at no higher list price; None when the
|
|
1055
1057
|
# pool holds no other maker's model.
|
|
1056
1058
|
backup: Backup | None = None
|
|
1059
|
+
# True for a QUALITY rung held by a category specialist off the frontier.
|
|
1060
|
+
specialist: bool = False
|
|
1057
1061
|
|
|
1058
1062
|
@property
|
|
1059
1063
|
def candidate(self) -> Candidate:
|
|
@@ -1099,6 +1103,7 @@ class Ladder:
|
|
|
1099
1103
|
**point(r.point),
|
|
1100
1104
|
"effort": r.effort,
|
|
1101
1105
|
"native_effort": r.native,
|
|
1106
|
+
"specialist": r.specialist,
|
|
1102
1107
|
"backup": (
|
|
1103
1108
|
{
|
|
1104
1109
|
**point(r.backup.point),
|
|
@@ -1121,20 +1126,70 @@ def frontier(candidates: list[Candidate], bench: dict[str, Any]) -> list[Frontie
|
|
|
1121
1126
|
at its price or less. The rule web/lib/benchmark-grid.ts paretoFrontier
|
|
1122
1127
|
draws on /models: a tie on price goes to the higher index, a tie on index to
|
|
1123
1128
|
the cheaper price. Unmeasured models have no place on it."""
|
|
1124
|
-
|
|
1125
|
-
|
|
1129
|
+
measured = [
|
|
1130
|
+
(c, aa)
|
|
1126
1131
|
for c in candidates
|
|
1127
1132
|
for aa in [_evidence(bench, c.model_id, FRONTIER_INDEX)]
|
|
1128
1133
|
if aa is not None
|
|
1129
1134
|
]
|
|
1130
|
-
|
|
1135
|
+
measured.sort(key=lambda m: (m[0].blended_price_usd, -m[1], m[0].model_id))
|
|
1131
1136
|
out: list[FrontierPoint] = []
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1137
|
+
best = -math.inf
|
|
1138
|
+
for c, aa in measured:
|
|
1139
|
+
if aa > best:
|
|
1140
|
+
out.append(FrontierPoint(c, aa, c.blended_price_usd))
|
|
1141
|
+
best = aa
|
|
1135
1142
|
return out
|
|
1136
1143
|
|
|
1137
1144
|
|
|
1145
|
+
def _strength(p: FrontierPoint) -> tuple[float, float, float]:
|
|
1146
|
+
"""A point's standing for the task's category: its category quality, then
|
|
1147
|
+
the AA Index, then the cheaper price."""
|
|
1148
|
+
return (p.candidate.quality, -1.0 if p.aa_index is None else p.aa_index, -p.price_usd)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
# Letters from weakest to strongest.
|
|
1152
|
+
LETTER_ORDER: Final[str] = "DCBAS"
|
|
1153
|
+
# Categories whose evidence is something other than the AA Intelligence Index,
|
|
1154
|
+
# the frontier's own axis: there a model off the frontier can be clearly
|
|
1155
|
+
# stronger for the category (every category but planning).
|
|
1156
|
+
SPECIALIST_CATEGORIES: Final[frozenset[str]] = frozenset(
|
|
1157
|
+
cat for cat, key in CATEGORY_EVIDENCE.items() if key != FRONTIER_INDEX
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def _specialist(
|
|
1162
|
+
task: Task,
|
|
1163
|
+
pool: list[Candidate],
|
|
1164
|
+
front: list[FrontierPoint],
|
|
1165
|
+
top: FrontierPoint,
|
|
1166
|
+
bench: dict[str, Any],
|
|
1167
|
+
) -> FrontierPoint | None:
|
|
1168
|
+
"""A category specialist for QUALITY: the pool model off the frontier
|
|
1169
|
+
whose letter in the task's category is strictly above that of ``top``
|
|
1170
|
+
(the strongest adequate frontier point) and whose category quality is
|
|
1171
|
+
strictly above it too; the strongest such model, else None. Only for
|
|
1172
|
+
SPECIALIST_CATEGORIES."""
|
|
1173
|
+
if task.category not in SPECIALIST_CATEGORIES:
|
|
1174
|
+
return None
|
|
1175
|
+
on_front = {p.candidate.model_id for p in front}
|
|
1176
|
+
rank = LETTER_ORDER.find
|
|
1177
|
+
t = top.candidate
|
|
1178
|
+
stronger = [
|
|
1179
|
+
c
|
|
1180
|
+
for c in pool
|
|
1181
|
+
if c.model_id not in on_front and rank(c.letter) > rank(t.letter) and c.quality > t.quality
|
|
1182
|
+
]
|
|
1183
|
+
if not stronger:
|
|
1184
|
+
return None
|
|
1185
|
+
best = max(
|
|
1186
|
+
stronger, key=lambda c: (c.quality, rank(c.letter), -c.blended_price_usd, c.model_id)
|
|
1187
|
+
)
|
|
1188
|
+
return FrontierPoint(
|
|
1189
|
+
best, _evidence(bench, best.model_id, FRONTIER_INDEX), best.blended_price_usd
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
|
|
1138
1193
|
def ladder(
|
|
1139
1194
|
task: Task,
|
|
1140
1195
|
user_context_text: str,
|
|
@@ -1166,6 +1221,12 @@ def ladder(
|
|
|
1166
1221
|
level they may converge. On an `uncapped` pool every free rung runs at
|
|
1167
1222
|
the top effort and two rungs on one model converge: a lower effort saves
|
|
1168
1223
|
that operator nothing.
|
|
1224
|
+
- A category specialist (:func:`_specialist`) may take QUALITY in a
|
|
1225
|
+
category whose evidence is not the AA Index: a pool model off the
|
|
1226
|
+
frontier whose letter and category quality are both strictly above
|
|
1227
|
+
the strongest adequate frontier point's. It runs at the best posture's
|
|
1228
|
+
effort; COST and BALANCED stay on the frontier, BALANCED then spanning
|
|
1229
|
+
every adequate point above COST.
|
|
1169
1230
|
- Each rung carries a BACKUP from another maker (:func:`_backup_for`):
|
|
1170
1231
|
the best adequate point on the other makers' frontier at no higher list
|
|
1171
1232
|
price. ``backup_warning`` says why a rung has none.
|
|
@@ -1186,15 +1247,14 @@ def ladder(
|
|
|
1186
1247
|
if not front:
|
|
1187
1248
|
return None
|
|
1188
1249
|
|
|
1189
|
-
def strength(p: FrontierPoint) -> tuple[float, float, float]:
|
|
1190
|
-
return (p.candidate.quality, p.aa_index, -p.price_usd)
|
|
1191
|
-
|
|
1192
1250
|
adequate = [p for p in front if p.candidate.requirement_penalty == 0]
|
|
1193
1251
|
if not adequate:
|
|
1194
|
-
adequate = [max(front, key=
|
|
1195
|
-
top = max(adequate, key=
|
|
1252
|
+
adequate = [max(front, key=_strength)]
|
|
1253
|
+
top = max(adequate, key=_strength)
|
|
1196
1254
|
span = adequate[: adequate.index(top) + 1]
|
|
1197
1255
|
headroom = consumption_headroom(user_context_text or "")
|
|
1256
|
+
pool = funded or ranking.candidates
|
|
1257
|
+
specialist = _specialist(task, pool, front, top, bench)
|
|
1198
1258
|
|
|
1199
1259
|
def rung(tier: str, p: FrontierPoint) -> Rung:
|
|
1200
1260
|
t = Task(task.category, task.complexity, task.novel, TIER_BUDGET[tier])
|
|
@@ -1206,8 +1266,14 @@ def ladder(
|
|
|
1206
1266
|
return a.candidate.model_id == b.candidate.model_id and a.level == b.level
|
|
1207
1267
|
|
|
1208
1268
|
cost_rung = rung("cost", span[0])
|
|
1209
|
-
quality_rung =
|
|
1210
|
-
|
|
1269
|
+
quality_rung = (
|
|
1270
|
+
replace(rung("quality", specialist), specialist=True)
|
|
1271
|
+
if specialist is not None
|
|
1272
|
+
else rung("quality", top)
|
|
1273
|
+
)
|
|
1274
|
+
# With a specialist on QUALITY, BALANCED spans every adequate frontier point
|
|
1275
|
+
# above COST, the frontier's strongest included.
|
|
1276
|
+
between = span[1:] if specialist is not None else span[1:-1]
|
|
1211
1277
|
if between:
|
|
1212
1278
|
balanced_rung = rung(
|
|
1213
1279
|
"balanced", max(between, key=lambda p: (p.candidate.score, -p.price_usd))
|
|
@@ -1215,15 +1281,16 @@ def ladder(
|
|
|
1215
1281
|
else:
|
|
1216
1282
|
# Two adequate points or one: BALANCED runs one of them at the balanced
|
|
1217
1283
|
# posture, whichever differs from both other rungs; it converges only
|
|
1218
|
-
# when neither does.
|
|
1219
|
-
options = [rung("balanced", span[0])
|
|
1284
|
+
# when neither does. Beside a specialist it stays on the frontier.
|
|
1285
|
+
options = [rung("balanced", span[0])]
|
|
1286
|
+
if specialist is None:
|
|
1287
|
+
options.append(rung("balanced", top))
|
|
1220
1288
|
balanced_rung = next(
|
|
1221
1289
|
(r for r in options if not same(r, cost_rung) and not same(r, quality_rung)),
|
|
1222
1290
|
options[0],
|
|
1223
1291
|
)
|
|
1224
1292
|
balanced_rung = _below(balanced_rung, quality_rung, cat)
|
|
1225
1293
|
cost_rung = _below(cost_rung, balanced_rung, cat)
|
|
1226
|
-
pool = funded or ranking.candidates
|
|
1227
1294
|
rungs = {
|
|
1228
1295
|
r.tier: replace(r, backup=_backup_for(r, pool, task, headroom, bench, cat))
|
|
1229
1296
|
for r in (cost_rung, balanced_rung, quality_rung)
|
|
@@ -1264,7 +1331,7 @@ def _backup_for(
|
|
|
1264
1331
|
within = [p for p in adequate if p.price_usd <= rung.point.price_usd]
|
|
1265
1332
|
chosen = within[-1] if within else adequate[0]
|
|
1266
1333
|
else:
|
|
1267
|
-
chosen = max(front, key=
|
|
1334
|
+
chosen = max(front, key=_strength)
|
|
1268
1335
|
t = Task(task.category, task.complexity, task.novel, TIER_BUDGET[rung.tier])
|
|
1269
1336
|
c = chosen.candidate
|
|
1270
1337
|
effort = effort_for(t, headroom, c.scarcity)
|
|
@@ -1339,6 +1406,10 @@ def ladder_table(
|
|
|
1339
1406
|
return out
|
|
1340
1407
|
|
|
1341
1408
|
|
|
1409
|
+
def _aa_text(p: FrontierPoint) -> str:
|
|
1410
|
+
return "not measured" if p.aa_index is None else f"{p.aa_index:g}"
|
|
1411
|
+
|
|
1412
|
+
|
|
1342
1413
|
def render_ladder_table(table: dict[str, Ladder]) -> str:
|
|
1343
1414
|
"""The table as the engine reads it: one row per classification, each rung
|
|
1344
1415
|
as model, platform and effort, under the frontier it was read from."""
|
|
@@ -1351,23 +1422,27 @@ def render_ladder_table(table: dict[str, Ladder]) -> str:
|
|
|
1351
1422
|
"the AA Intelligence Index than any they can run for less), cheapest first:",
|
|
1352
1423
|
" "
|
|
1353
1424
|
+ " < ".join(
|
|
1354
|
-
f"{p.candidate.model_name} (AA {p
|
|
1425
|
+
f"{p.candidate.model_name} (AA {_aa_text(p)}, ${p.candidate.blended_price_usd:.2f}/1M list)"
|
|
1355
1426
|
for p in any_ladder.frontier
|
|
1356
1427
|
),
|
|
1357
1428
|
"Rows: <category>/<complexity>[/novel]: COST | BALANCED | QUALITY, each "
|
|
1358
1429
|
"<model> @ <platform> · <effort>, the effort in the platform's own level words, "
|
|
1359
|
-
"then `backup <model> @ <platform>`: that pick's BACKUP."
|
|
1430
|
+
"then `backup <model> @ <platform>`: that pick's BACKUP. A QUALITY cell marked "
|
|
1431
|
+
"`(top for <category> work)` names a model off the frontier whose rating for that "
|
|
1432
|
+
"category is above every frontier model's.",
|
|
1360
1433
|
]
|
|
1361
1434
|
|
|
1362
|
-
def cell(t: str, r: Rung) -> str:
|
|
1435
|
+
def cell(t: str, r: Rung, category: str) -> str:
|
|
1363
1436
|
text = f"{t.upper()} = {r.candidate.model_name} @ {r.candidate.platform_name} · {r.level}"
|
|
1437
|
+
if r.specialist:
|
|
1438
|
+
text += f" (top for {category} work)"
|
|
1364
1439
|
if r.backup is not None:
|
|
1365
1440
|
b = r.backup.candidate
|
|
1366
1441
|
text += f", backup {b.model_name} @ {b.platform_name}"
|
|
1367
1442
|
return text
|
|
1368
1443
|
|
|
1369
1444
|
for key, lad in table.items():
|
|
1370
|
-
cells = " | ".join(cell(t, lad.rungs[t]) for t in LADDER_TIERS)
|
|
1445
|
+
cells = " | ".join(cell(t, lad.rungs[t], lad.task.category) for t in LADDER_TIERS)
|
|
1371
1446
|
lines.append(f"{key}: {cells}")
|
|
1372
1447
|
lines.append("</ladder-table>")
|
|
1373
1448
|
return "\n".join(lines)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|