Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """Build a field-by-field Devpost form kit from the current submission packet.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| from proofframe.submission_gate import build_submission_gate | |
| ROOT = Path(__file__).resolve().parents[1] | |
| DEFAULT_JSON = ROOT / "docs" / "assets" / "devpost-form-kit.json" | |
| DEFAULT_MD = ROOT / "docs" / "assets" / "devpost-form-kit.md" | |
| DEFAULT_PACKET = ROOT / "docs" / "assets" / "devpost-submission-packet.json" | |
| BUILT_WITH = [ | |
| "Python", | |
| "FastAPI", | |
| "Backblaze B2 S3-compatible API", | |
| "Genblaze", | |
| "Hugging Face Spaces", | |
| "GitHub Actions", | |
| ] | |
| TAGS = [ | |
| "AI", | |
| "Generative media", | |
| "Backblaze B2", | |
| "Genblaze", | |
| "Provenance", | |
| "Media operations", | |
| ] | |
| FINAL_FORM_TASKS = ["T020", "T021", "T040", "T041A"] | |
| FORM_FIELD_SPECS = [ | |
| { | |
| "id": "project_name", | |
| "label": "Project name", | |
| "source": "packet.project_name", | |
| "max_chars": 80, | |
| "required": True, | |
| }, | |
| { | |
| "id": "tagline", | |
| "label": "Tagline", | |
| "source": "packet.tagline", | |
| "max_chars": 140, | |
| "required": True, | |
| }, | |
| { | |
| "id": "one_liner", | |
| "label": "One-liner", | |
| "source": "packet.one_liner", | |
| "max_chars": 280, | |
| "required": True, | |
| }, | |
| { | |
| "id": "short_description", | |
| "label": "Short description", | |
| "source": "packet.short_description", | |
| "max_chars": 2000, | |
| "required": True, | |
| }, | |
| { | |
| "id": "repository_url", | |
| "label": "Repository URL", | |
| "source": "packet.repository_url", | |
| "max_chars": 300, | |
| "required": True, | |
| "kind": "url", | |
| }, | |
| { | |
| "id": "demo_url", | |
| "label": "Demo URL", | |
| "source": "packet.demo_url", | |
| "max_chars": 300, | |
| "required": True, | |
| "kind": "url", | |
| }, | |
| { | |
| "id": "video_url", | |
| "label": "Demo video URL", | |
| "source": "packet.video_url", | |
| "max_chars": 300, | |
| "required": True, | |
| "kind": "url", | |
| "allow_placeholder_before_final": True, | |
| }, | |
| { | |
| "id": "built_with", | |
| "label": "Built with", | |
| "source": "form.built_with", | |
| "max_chars": 800, | |
| "required": True, | |
| }, | |
| { | |
| "id": "tags", | |
| "label": "Suggested tags", | |
| "source": "form.tags", | |
| "max_chars": 500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "inspiration", | |
| "label": "Inspiration", | |
| "source": "packet.inspiration", | |
| "max_chars": 2000, | |
| "required": True, | |
| }, | |
| { | |
| "id": "what_it_does", | |
| "label": "What it does", | |
| "source": "packet.what_it_does", | |
| "max_chars": 2500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "how_we_built_it", | |
| "label": "How we built it", | |
| "source": "packet.how_we_built_it", | |
| "max_chars": 2500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "backblaze_b2_usage", | |
| "label": "Backblaze B2 usage", | |
| "source": "packet.b2_usage", | |
| "max_chars": 1500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "genblaze_usage", | |
| "label": "Genblaze usage", | |
| "source": "packet.genblaze_usage", | |
| "max_chars": 1500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "challenges", | |
| "label": "Challenges", | |
| "source": "packet.challenges", | |
| "max_chars": 2000, | |
| "required": True, | |
| }, | |
| { | |
| "id": "accomplishments", | |
| "label": "Accomplishments", | |
| "source": "packet.accomplishments", | |
| "max_chars": 2500, | |
| "required": True, | |
| }, | |
| { | |
| "id": "what_we_learned", | |
| "label": "What we learned", | |
| "source": "packet.what_we_learned", | |
| "max_chars": 2000, | |
| "required": True, | |
| }, | |
| { | |
| "id": "whats_next", | |
| "label": "What's next", | |
| "source": "packet.whats_next", | |
| "max_chars": 1800, | |
| "required": True, | |
| }, | |
| { | |
| "id": "judging_note", | |
| "label": "Judging note", | |
| "source": "form.judging_note", | |
| "max_chars": 1000, | |
| "required": True, | |
| }, | |
| ] | |
| def load_json(path: Path) -> dict[str, Any] | None: | |
| try: | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| except (FileNotFoundError, json.JSONDecodeError): | |
| return None | |
| def stringify(value: Any) -> str: | |
| if isinstance(value, list): | |
| return "\n".join(f"- {item}" for item in value) | |
| if value is None: | |
| return "" | |
| return str(value).strip() | |
| def packet_value(packet: dict[str, Any], source: str) -> str: | |
| if source == "form.built_with": | |
| return ", ".join(BUILT_WITH) | |
| if source == "form.tags": | |
| return ", ".join(TAGS) | |
| if source == "form.judging_note": | |
| return ( | |
| f"Current packet mode: {packet.get('mode', 'unknown')}. " | |
| f"{packet.get('claim_warning', '')}" | |
| ).strip() | |
| if source.startswith("packet."): | |
| return stringify(packet.get(source.removeprefix("packet."))) | |
| return "" | |
| def usable_url(value: str) -> bool: | |
| value = value.strip() | |
| return value.startswith(("https://", "http://")) and not has_placeholder(value) | |
| def has_placeholder(value: str) -> bool: | |
| lowered = value.strip().lower() | |
| return not lowered or lowered.startswith("tbd") or "tbd after" in lowered | |
| def task_statuses(root: Path) -> dict[str, str]: | |
| tasks = load_json(root / "tasks.json") or {} | |
| return { | |
| str(task.get("id", "")).upper(): str(task.get("status", "missing")) | |
| for task in tasks.get("tasks", []) | |
| } | |
| def build_form_fields(packet: dict[str, Any]) -> list[dict[str, Any]]: | |
| fields: list[dict[str, Any]] = [] | |
| for spec in FORM_FIELD_SPECS: | |
| value = packet_value(packet, spec["source"]) | |
| too_long = len(value) > int(spec["max_chars"]) | |
| missing = bool(spec["required"]) and not value | |
| placeholder = has_placeholder(value) | |
| url_missing = spec.get("kind") == "url" and not usable_url(value) | |
| if spec.get("allow_placeholder_before_final"): | |
| url_missing = False | |
| fields.append( | |
| { | |
| "id": spec["id"], | |
| "label": spec["label"], | |
| "source": spec["source"], | |
| "required": spec["required"], | |
| "value": value, | |
| "chars": len(value), | |
| "max_chars": spec["max_chars"], | |
| "too_long": too_long, | |
| "missing": missing, | |
| "placeholder": placeholder, | |
| "url_missing": url_missing, | |
| "ok_for_mock": not too_long and not missing and not url_missing, | |
| "ok_for_final": ( | |
| not too_long | |
| and not missing | |
| and not placeholder | |
| and (spec.get("kind") != "url" or usable_url(value)) | |
| ), | |
| } | |
| ) | |
| return fields | |
| def final_task_gate(statuses: dict[str, str]) -> dict[str, Any]: | |
| tasks = [ | |
| {"id": task_id, "status": statuses.get(task_id, "missing")} | |
| for task_id in FINAL_FORM_TASKS | |
| ] | |
| return { | |
| "ok": all(task["status"] == "done" for task in tasks), | |
| "tasks": tasks, | |
| } | |
| def next_actions( | |
| *, | |
| fields: list[dict[str, Any]], | |
| packet: dict[str, Any], | |
| evidence_ready: bool, | |
| task_gate: dict[str, Any], | |
| ) -> list[str]: | |
| actions: list[str] = [] | |
| if packet.get("mode") != "post_live_verified": | |
| actions.append("Regenerate the Devpost packet in post-live mode after B2 and Genblaze proof.") | |
| if not evidence_ready: | |
| actions.append("Capture sanitized final B2 plus Genblaze live proof evidence.") | |
| if not task_gate["ok"]: | |
| missing = ", ".join( | |
| task["id"] for task in task_gate["tasks"] if task["status"] != "done" | |
| ) | |
| actions.append(f"Finish final form prerequisite tasks: {missing}.") | |
| for field in fields: | |
| if not field["ok_for_final"]: | |
| actions.append(f"Finalize Devpost field: {field['label']}.") | |
| actions.append("Run final secret scan, final audit, then submit Devpost.") | |
| return list(dict.fromkeys(actions))[:8] | |
| def build_form_kit(root: Path = ROOT, packet_path: Path | None = None) -> dict[str, Any]: | |
| root = root.resolve() | |
| packet = load_json(packet_path or root / "docs" / "assets" / "devpost-submission-packet.json") | |
| if packet is None: | |
| packet = {} | |
| gate = build_submission_gate(root) | |
| fields = build_form_fields(packet) | |
| statuses = task_statuses(root) | |
| task_gate = final_task_gate(statuses) | |
| mock_form_ready = bool(packet) and all(field["ok_for_mock"] for field in fields) | |
| public_video_ready = usable_url(str(packet.get("video_url", ""))) | |
| final_form_ready = ( | |
| mock_form_ready | |
| and all(field["ok_for_final"] for field in fields) | |
| and packet.get("mode") == "post_live_verified" | |
| and gate["evidence_gate"]["ok"] | |
| and task_gate["ok"] | |
| and public_video_ready | |
| ) | |
| return { | |
| "schema": "proofframe.devpost_form_kit.v1", | |
| "mode": "final_form_ready" if final_form_ready else "pre_live_form_ready", | |
| "packet_mode": packet.get("mode"), | |
| "mock_form_ready": mock_form_ready, | |
| "final_form_ready": final_form_ready, | |
| "public_video_ready": public_video_ready, | |
| "field_count": len(fields), | |
| "fields": fields, | |
| "task_gate": task_gate, | |
| "evidence_gate": { | |
| "ok": gate["evidence_gate"]["ok"], | |
| "status": gate["evidence_gate"]["status"], | |
| }, | |
| "next_actions": next_actions( | |
| fields=fields, | |
| packet=packet, | |
| evidence_ready=gate["evidence_gate"]["ok"], | |
| task_gate=task_gate, | |
| ), | |
| } | |
| def render_markdown(kit: dict[str, Any]) -> str: | |
| lines = [ | |
| "# ProofFrame Devpost Form Kit", | |
| "", | |
| f"Mode: `{kit['mode']}`", | |
| f"Packet mode: `{kit['packet_mode']}`", | |
| f"Mock form ready: `{str(kit['mock_form_ready']).lower()}`", | |
| f"Final form ready: `{str(kit['final_form_ready']).lower()}`", | |
| f"Public video ready: `{str(kit['public_video_ready']).lower()}`", | |
| "", | |
| "## Fields", | |
| "", | |
| ] | |
| for field in kit["fields"]: | |
| status = "OK" if field["ok_for_mock"] else "CHECK" | |
| final_status = "FINAL OK" if field["ok_for_final"] else "FINAL PENDING" | |
| lines.extend( | |
| [ | |
| f"### {field['label']}", | |
| "", | |
| f"- Status: `{status}` / `{final_status}`", | |
| f"- Source: `{field['source']}`", | |
| f"- Length: `{field['chars']} / {field['max_chars']}`", | |
| "", | |
| "```text", | |
| field["value"], | |
| "```", | |
| "", | |
| ] | |
| ) | |
| lines.extend(["## Final Prerequisite Tasks", ""]) | |
| for task in kit["task_gate"]["tasks"]: | |
| lines.append(f"- `{task['id']}`: `{task['status']}`") | |
| lines.extend(["", "## Next Actions", ""]) | |
| lines.extend(f"- {action}" for action in kit["next_actions"]) | |
| return "\n".join(lines).rstrip() + "\n" | |
| def write_outputs(kit: dict[str, Any], json_path: Path, markdown_path: Path) -> None: | |
| json_path.parent.mkdir(parents=True, exist_ok=True) | |
| markdown_path.parent.mkdir(parents=True, exist_ok=True) | |
| json_path.write_text(json.dumps(kit, indent=2) + "\n", encoding="utf-8") | |
| markdown_path.write_text(render_markdown(kit), encoding="utf-8") | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Build a field-by-field Devpost form kit.") | |
| parser.add_argument("--root", type=Path, default=ROOT) | |
| parser.add_argument("--packet", type=Path, default=DEFAULT_PACKET) | |
| parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON) | |
| parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD) | |
| parser.add_argument( | |
| "--strict-final", | |
| action="store_true", | |
| help="Fail unless final live proof, public video URL, and final form fields are ready.", | |
| ) | |
| return parser | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| kit = build_form_kit(args.root, args.packet) | |
| write_outputs(kit, args.json_out, args.markdown_out) | |
| ok = kit["final_form_ready"] if args.strict_final else kit["mock_form_ready"] | |
| print( | |
| json.dumps( | |
| { | |
| "ok": ok, | |
| "mode": kit["mode"], | |
| "json": str(args.json_out), | |
| "markdown": str(args.markdown_out), | |
| "next_actions": kit["next_actions"], | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| raise SystemExit(0 if ok else 2) | |
| if __name__ == "__main__": | |
| main() | |