Spaces:
Running
Running
File size: 8,608 Bytes
717fd79 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | # SPDX-License-Identifier: Apache-2.0
"""Reviewing from the Space: the rules and wording behind the validator screens.
Plain data in, plain data out, like contribute.py. webapi.py does the HTTP, and account.py the
calls to DataBounty's validator tools (list_audits, claim_audit, get_audit, submit_decisions,
list_my_audits). DataBounty enforces every rule; the checks here only save a round trip.
"""
from __future__ import annotations
import json
from typing import Any
import contribute
# Why a reviewer rejects an item: DataBounty's FlagReason values, in the console's order.
FLAG_REASONS = {
"duplicate": "Duplicate or near-duplicate",
"contaminated": "Copied from a public benchmark",
"tests_invalid": "Tests are wrong",
"solution_incorrect": "Solution is wrong",
"too_trivial": "Too easy",
"low_quality": "Low quality",
"off_spec": "Doesn't follow the pool's spec",
"other": "Something else",
}
# DataBounty refuses a rejection without a note at least this long (MIN_FLAG_NOTE_LENGTH):
# the note is all the contributor is told about why their item came back.
MIN_NOTE_LENGTH = 10
METHOD_LABELS = {"human": "Written by a person", "ai_assisted": "Written with AI help", "ai_generated": "AI generated"}
# An audit's status as a reviewer reads it: (label, tone).
AUDIT_STATUS = {
"available": ("Open", "info"),
"in_progress": ("Open", "info"),
"claimed": ("Yours", "review"),
"overdue_review": ("Past due", "warn"),
"completed": ("Completed", "ok"),
}
def _int(value: Any) -> int:
return int(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else 0
def rank_view(who: dict[str, Any]) -> dict[str, Any]:
"""The reviewer's standing from whoami: rank, progress to the next one, and how many audits
they may hold at once against how many they hold now."""
validator = (who.get("ranks") or {}).get("validator") or {}
upcoming = validator.get("nextRank")
rate = validator.get("falseFlagRate")
return {
"rank": validator.get("rank") or "",
"auditsCompleted": _int(validator.get("auditsCompleted")),
"nextRank": {"name": upcoming["name"], "toGo": _int(upcoming.get("itemsToGo"))}
if isinstance(upcoming, dict) and upcoming.get("name")
else None,
"maxConcurrent": max(1, _int(validator.get("maxConcurrentAudits"))),
"active": _int((who.get("activity") or {}).get("activeAudits")),
"falseFlagRate": rate if isinstance(rate, (int, float)) else None,
"missedDeadlines": _int(validator.get("missedDeadlines")),
}
def audit_row(audit: dict[str, Any]) -> dict[str, Any]:
"""One audit in a list: open ones from list_audits, the reviewer's own from list_my_audits."""
label, tone = AUDIT_STATUS.get(str(audit.get("status") or ""), ("Open", "info"))
return {
"id": audit.get("id"),
"poolId": audit.get("bountyId"),
"pool": audit.get("bountyTitle") or "",
"items": _int(audit.get("itemCount")),
"decided": _int(audit.get("decidedCount")),
"status": audit.get("status"),
"statusLabel": label,
"statusTone": tone,
"deadline": audit.get("deadline"),
"category": str(audit.get("category") or "").replace("_", " "),
"language": audit.get("language") or "",
"karma": audit.get("karmaReward"),
"closed": bool(audit.get("supersededAt")),
}
def _text(value: Any) -> str:
"""A field's value as the reviewer reads it. A list puts one entry per line, which keeps
lists of test cases readable."""
if isinstance(value, str):
return value
if value is None:
return ""
if isinstance(value, list) and value:
return "[\n" + ",\n".join(f" {json.dumps(entry, ensure_ascii=False)}" for entry in value) + "\n]"
return json.dumps(value, indent=2, ensure_ascii=False)
def fields_view(payload: Any, specs: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""An item's fields for reading: the contract's fields in its order, then anything else it carries."""
payload = payload if isinstance(payload, dict) else {}
known = [spec["key"] for spec in specs if spec.get("key")]
labels = {spec["key"]: spec.get("label") or spec["key"] for spec in specs if spec.get("key")}
kinds = {spec["key"]: contribute.field_kind(spec) for spec in specs if spec.get("key")}
keys = [k for k in known if k in payload] + [k for k in payload if k not in known]
return [{"key": key, "label": labels.get(key, key), "value": _text(payload.get(key)), "code": kinds.get(key) == "code"} for key in keys]
def _item_view(item: dict[str, Any], specs: list[dict[str, Any]]) -> dict[str, Any]:
sub = item.get("submission") or {}
verdict = item.get("verdict")
# The console's rule (validator/audit/[id]/view.tsx itemDecision): when DataBounty says where
# a verdict came from, the verdict is real. An audit without an audit batch has no decision
# time until it closes, so requiring one would offer controls DataBounty then refuses.
decided = verdict in ("ok", "flagged") and ("verdictSource" in item or "decidedAt" not in item or bool(item.get("decidedAt")))
return {
"id": item.get("id"),
"submissionId": item.get("submissionId") or sub.get("id"),
"title": sub.get("title") or "",
"method": METHOD_LABELS.get(str(sub.get("generationMethod") or ""), ""),
"fields": fields_view(sub.get("payloadJson"), specs),
"checks": [c for c in contribute.checks_for(sub.get("validationResults")) if c["stage"] != "human_audit"],
"attachments": [
{"name": a.get("filename") or "file", "size": a.get("sizeBytes")}
for a in item.get("attachments") or []
if isinstance(a, dict)
],
"decided": decided,
"verdict": verdict if decided else None,
"flagReason": FLAG_REASONS.get(str(item.get("flagReason") or ""), "") if decided and verdict == "flagged" else "",
"note": (item.get("note") or "") if decided and verdict == "flagged" else "",
}
def audit_view(detail: dict[str, Any], app_url: str) -> dict[str, Any]:
"""A claimed audit for its review page, from get_audit: the pool's spec and each item with
its content, its automated checks and any decision already made."""
bounty = detail.get("bounty") or {}
dataset_type = bounty.get("datasetType") or {}
specs = [f for f in dataset_type.get("fields") or [] if isinstance(f, dict) and f.get("key")]
items = [_item_view(item, specs) for item in detail.get("items") or [] if isinstance(item, dict)]
return {
"id": detail.get("id"),
"poolId": detail.get("bountyId") or bounty.get("id"),
"pool": bounty.get("title") or "",
"typeName": dataset_type.get("name") or "",
"language": bounty.get("language") or "",
"deadline": detail.get("claimExpiresAt") or detail.get("deadline"),
"status": detail.get("status"),
"karma": detail.get("karmaReward"),
"spec": [
{"key": spec["key"], "label": spec.get("label") or spec["key"], "required": bool(spec.get("required")), "hint": contribute.short_hint(spec)}
for spec in specs
],
"items": items,
"decided": sum(1 for item in items if item["decided"]),
"total": len(items),
"consoleUrl": f"{app_url}/validator/audit/{detail.get('id')}/",
}
def decision(body: dict[str, Any]) -> tuple[dict[str, Any] | None, str]:
"""One decision from the page, in submit_decisions' shape, or why DataBounty would refuse it."""
item_id = str(body.get("itemId") or "").strip()
verdict = body.get("verdict")
if not item_id:
return None, "Pick the item you're deciding."
if verdict == "ok":
return {"auditItemId": item_id, "verdict": "ok"}, ""
if verdict != "flagged":
return None, "Approve the item or reject it."
reason = str(body.get("flagReason") or "")
note = str(body.get("note") or "").strip() # DataBounty counts the trimmed note
if reason not in FLAG_REASONS:
return None, "Choose why you're rejecting it."
if len(note) < MIN_NOTE_LENGTH:
return None, f"Tell the contributor what's wrong, in at least {MIN_NOTE_LENGTH} characters. It's all they'll see."
return {"auditItemId": item_id, "verdict": "flagged", "flagReason": reason, "note": note}, ""
|