NathanRoll commited on
Commit
23695ab
·
verified ·
1 Parent(s): 8436a06

Split benchmark into pages and clarify attribution

Browse files
Files changed (1) hide show
  1. app.py +318 -57
app.py CHANGED
@@ -44,6 +44,8 @@ CSS = """
44
  --line-soft: #d4cab8;
45
  --green: #b9e7bd;
46
  --green-strong: #7fc98a;
 
 
47
  --link: #174f8a;
48
  --radius: 6px;
49
  }
@@ -281,6 +283,25 @@ gradio-app,
281
  color: var(--ink);
282
  }
283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  .record-top {
285
  padding: 9px 10px 0;
286
  }
@@ -432,6 +453,35 @@ gradio-app,
432
  font-size: 13px;
433
  }
434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  .submit-shell,
436
  .schema-shell {
437
  width: min(1180px, calc(100% - 32px));
@@ -511,6 +561,16 @@ gradio-app,
511
  max-height: 315px;
512
  }
513
 
 
 
 
 
 
 
 
 
 
 
514
  @media (max-width: 720px) {
515
  .contain,
516
  .submit-shell,
@@ -614,6 +674,8 @@ MONTHS = (
614
  "December",
615
  )
616
 
 
 
617
 
618
  def esc(value: Any) -> str:
619
  return html.escape("" if value is None else str(value), quote=True)
@@ -709,6 +771,18 @@ def author_name(record: dict[str, Any]) -> str:
709
  return str(record.get("submitted_by") or "unknown").strip() or "unknown"
710
 
711
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  def metric_symbol(record: dict[str, Any]) -> str:
713
  return str(record.get("metric_symbol") or "s")
714
 
@@ -725,6 +799,17 @@ def metric_value(record: dict[str, Any]) -> str:
725
  return str(value)
726
 
727
 
 
 
 
 
 
 
 
 
 
 
 
728
  def compact_expression(record: dict[str, Any]) -> str:
729
  expression = str(record.get("metric_expression") or "").strip()
730
  expression = re.sub(r"\s+", " ", expression)
@@ -864,9 +949,32 @@ def visual_provenance(record: dict[str, Any], visual_record: dict[str, Any]) ->
864
  return "Coordinate rendering attached to this case."
865
 
866
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  def record_details(record: dict[str, Any], visual_record: dict[str, Any], source: str, image_name: str, expression: str, analytical: Any) -> str:
868
  rows: list[str] = []
869
  rows.append(detail_row("Rendering", esc(visual_provenance(record, visual_record))))
 
 
 
 
 
 
 
870
  if record.get("record_type") == "verified":
871
  rows.append(detail_row("Submitted", esc(display_date(record))))
872
  notes = str(record.get("notes") or "").strip()
@@ -902,19 +1010,23 @@ def record_details(record: dict[str, Any], visual_record: dict[str, Any], source
902
  def record_card(record: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
903
  verified = record.get("record_type") == "verified"
904
  visual_record = visual_record_for(record, coordinates)
 
905
  source = source_link(record)
906
  expression = compact_expression(record)
907
  image_name = source_image_name(record)
908
  analytical = friedman_reference(record).get("analytical_or_proved")
909
- visible_author = author_name(record) if verified else reference_credit(record)
 
 
 
910
  return f"""
911
- <article class="record-card {'verified' if verified else 'reference'}">
912
  <div class="record-top">
913
  <div class="record-case">{esc(record.get("case"))}</div>
914
  </div>
915
  {record_visual(record, coordinates)}
916
  <div class="record-body">
917
- <div class="metric"><strong>{esc(metric_symbol(record))} = {esc(metric_value(record))}</strong><span>n={esc(record.get("n"))}</span></div>
918
  <div class="expression">{esc(expression)}</div>
919
  <div class="record-author">by <strong>{esc(visible_author)}</strong></div>
920
  {record_details(record, visual_record, source, image_name, expression, analytical)}
@@ -944,6 +1056,40 @@ def setup_summary(setup: str) -> dict[str, Any]:
944
  }
945
 
946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  def family_preview(setup: str, coordinates: dict[str, dict[str, Any]]) -> str:
948
  record = coordinates.get(f"{setup}@10")
949
  if record is None:
@@ -965,7 +1111,7 @@ def family_preview(setup: str, coordinates: dict[str, dict[str, Any]]) -> str:
965
  def family_card(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
966
  setup = str(summary["setup"])
967
  return f"""
968
- <a class="family-card" href="#setup-{esc(setup)}">
969
  {family_preview(setup, coordinates)}
970
  <div class="family-card-top">
971
  <div>
@@ -973,32 +1119,54 @@ def family_card(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]])
973
  <h3>{esc(summary["title"])}</h3>
974
  </div>
975
  </div>
976
- </a>
977
  """
978
 
979
 
980
- def family_section(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
 
 
 
 
981
  setup = str(summary["setup"])
982
  records = summary["records"]
983
  cards = "".join(record_card(record, coordinates) for record in records)
984
  return f"""
985
- <section class="family-section" id="setup-{esc(setup)}">
 
 
 
986
  <div class="family-title-card">
987
- <div class="family-code">{esc(setup)}</div>
988
- <h2>{esc(summary["title"])}</h2>
989
- <p class="family-subtitle">Entries ordered by n.</p>
 
 
990
  </div>
991
  <div class="record-grid">{cards}</div>
992
- <a class="back-link" href="#families">Back to family directory</a>
993
  </section>
 
994
  """
995
 
996
 
997
- def browse_html() -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  summaries = [setup_summary(setup) for setup in setup_choices()]
999
  coordinates = coordinate_records_by_case()
1000
  family_cards = "".join(family_card(summary, coordinates) for summary in summaries)
1001
- sections = "".join(family_section(summary, coordinates) for summary in summaries)
1002
  return f"""
1003
  <main class="packing-shell" id="top">
1004
  <section class="hero-stage">
@@ -1006,39 +1174,114 @@ def browse_html() -> str:
1006
  <div>
1007
  <h1>Packing Benchmark</h1>
1008
  <p class="hero-copy">
1009
- Equal-copy packing records with canonical coordinate JSON. Friedman rows keep their reported metric
1010
- and credited finder; when Friedman coordinates are unavailable, the drawing is a generated feasible
1011
- coordinate rendering and the details panel says so.
 
1012
  </p>
1013
  </div>
1014
  </div>
1015
  </section>
1016
- <nav class="top-nav">
1017
- <div class="contain top-nav-inner">
1018
- <a href="#top" aria-label="Packing Benchmark home">Packing Benchmark</a>
1019
- <div class="nav-links">
1020
- <a href="#families">Families</a>
1021
- <a href="#records">Records</a>
1022
- <a href="#submit-panel">Submit</a>
1023
- <a href="#schema-panel">Schema</a>
1024
- </div>
1025
- </div>
1026
- </nav>
1027
  <section class="section" id="families">
1028
  <div class="contain">
1029
  <div class="section-head">
1030
  <div>
1031
  <h2>Choose a family</h2>
1032
  <p class="section-note">
1033
- Select a family to view records ordered by item count.
1034
  </p>
1035
  </div>
1036
  </div>
1037
  <div class="family-grid">{family_cards}</div>
1038
  </div>
1039
  </section>
1040
- <section class="record-book" id="records">
1041
- <div class="contain">{sections}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
  </section>
1043
  </main>
1044
  """
@@ -1107,44 +1350,62 @@ def submit_solution(
1107
  pretty = json.dumps(stored, indent=2, sort_keys=True)
1108
  sync_status = record.get("sync_status", "dataset sync disabled")
1109
  message = result_markdown(result) + f"\n\nSaved as record `{record['id']}`.\n\nDataset sync: `{sync_status}`."
1110
- return message, preview_html(stored), pretty, browse_html()
1111
  except Exception as exc:
1112
- return f"### Submission rejected\n\n- {exc}", "", json_text, browse_html()
 
 
 
 
1113
 
1114
 
1115
  THEME = gr.themes.Base()
1116
 
1117
 
1118
  with gr.Blocks(title="Packing Benchmark") as demo:
1119
- browse_page = gr.HTML(browse_html())
1120
-
1121
- with gr.Group(elem_classes=["submit-shell"]):
1122
- with gr.Accordion("Submit a canonical coordinate JSON", open=False, elem_id="submit-panel"):
1123
- with gr.Row():
1124
- with gr.Column(scale=7):
1125
- json_code = gr.Code(value=SAMPLE_JSON, language="json", label="Canonical coordinate JSON", lines=20)
1126
- json_file = gr.File(label="or upload .json", file_types=[".json"], type="filepath")
1127
- with gr.Column(scale=5):
1128
- submitter = gr.Textbox(label="Submitted by", placeholder="Name, handle, or lab")
1129
- source_url = gr.Textbox(label="Source URL", placeholder="Optional paper, repo, or run link")
1130
- notes = gr.Textbox(label="Notes", lines=3, placeholder="Optional method or provenance note")
1131
- tolerance = gr.Number(value=DEFAULT_TOLERANCE, label="Verification tolerance")
1132
- with gr.Row():
1133
- verify_btn = gr.Button("Verify", variant="secondary")
1134
- submit_btn = gr.Button("Submit Verified Record", variant="primary")
1135
- report = gr.Markdown()
1136
- preview = gr.HTML(label="Browser render")
1137
- normalized_json = gr.Code(label="Normalized JSON", language="json", lines=12)
1138
-
1139
- with gr.Group(elem_classes=["schema-shell"]):
1140
- with gr.Accordion("Canonical JSON schema", open=False, elem_id="schema-panel"):
1141
- gr.Markdown(SCHEMA_TEXT)
1142
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1143
  verify_btn.click(verify_only, inputs=[json_code, json_file, tolerance], outputs=[report, preview, normalized_json])
1144
  submit_btn.click(
1145
  submit_solution,
1146
  inputs=[json_code, json_file, submitter, notes, source_url, tolerance],
1147
- outputs=[report, preview, normalized_json, browse_page],
1148
  )
1149
 
1150
 
 
44
  --line-soft: #d4cab8;
45
  --green: #b9e7bd;
46
  --green-strong: #7fc98a;
47
+ --orange: #f4b36a;
48
+ --orange-soft: #ffe1ba;
49
  --link: #174f8a;
50
  --radius: 6px;
51
  }
 
283
  color: var(--ink);
284
  }
285
 
286
+ .record-card.needs-recovery {
287
+ background: var(--orange-soft);
288
+ border-color: #a65b0b;
289
+ }
290
+
291
+ .record-card.needs-recovery .record-case::after {
292
+ content: "needs recovery";
293
+ display: inline-block;
294
+ margin-left: 7px;
295
+ padding: 1px 5px;
296
+ border: 1px solid #a65b0b;
297
+ border-radius: 3px;
298
+ color: #5c3100;
299
+ font-family: Helvetica, Arial, sans-serif !important;
300
+ font-size: 10px;
301
+ font-weight: 700;
302
+ text-transform: uppercase;
303
+ }
304
+
305
  .record-top {
306
  padding: 9px 10px 0;
307
  }
 
453
  font-size: 13px;
454
  }
455
 
456
+ .page-card {
457
+ background: var(--paper);
458
+ border: 1px solid var(--line);
459
+ border-radius: var(--radius);
460
+ padding: 16px;
461
+ }
462
+
463
+ .leaderboard-table {
464
+ width: 100%;
465
+ border-collapse: collapse;
466
+ background: var(--paper);
467
+ border: 1px solid var(--line);
468
+ }
469
+
470
+ .leaderboard-table th,
471
+ .leaderboard-table td {
472
+ padding: 10px 12px;
473
+ border-bottom: 1px solid var(--line-soft);
474
+ color: var(--ink);
475
+ text-align: left;
476
+ vertical-align: top;
477
+ }
478
+
479
+ .leaderboard-table th {
480
+ background: var(--paper-soft);
481
+ font-size: 12px;
482
+ text-transform: uppercase;
483
+ }
484
+
485
  .submit-shell,
486
  .schema-shell {
487
  width: min(1180px, calc(100% - 32px));
 
561
  max-height: 315px;
562
  }
563
 
564
+ .gradio-container .tab-nav {
565
+ background: var(--page) !important;
566
+ border-bottom: 1px solid var(--line) !important;
567
+ }
568
+
569
+ .gradio-container .tab-nav button {
570
+ background: transparent !important;
571
+ border: 0 !important;
572
+ }
573
+
574
  @media (max-width: 720px) {
575
  .contain,
576
  .submit-shell,
 
674
  "December",
675
  )
676
 
677
+ RECOVERY_TOLERANCE_REL = 1.0e-3
678
+
679
 
680
  def esc(value: Any) -> str:
681
  return html.escape("" if value is None else str(value), quote=True)
 
771
  return str(record.get("submitted_by") or "unknown").strip() or "unknown"
772
 
773
 
774
+ def display_author(record: dict[str, Any]) -> str:
775
+ if record.get("record_type") == "reference":
776
+ return reference_credit(record)
777
+ author = author_name(record)
778
+ notes = str(record.get("notes") or "").lower()
779
+ seeded = "seeded from" in notes or record.get("frontend_seed")
780
+ prior = reference_credit(record)
781
+ if author.lower().startswith("nathan roll") and seeded and prior not in {"unknown", "Erich Friedman Packing Center"}:
782
+ return prior
783
+ return author
784
+
785
+
786
  def metric_symbol(record: dict[str, Any]) -> str:
787
  return str(record.get("metric_symbol") or "s")
788
 
 
799
  return str(value)
800
 
801
 
802
+ def numeric_metric(record: dict[str, Any]) -> float | None:
803
+ value = record.get("metric_value")
804
+ if value is None:
805
+ value = record.get("side")
806
+ try:
807
+ parsed = float(value)
808
+ except (TypeError, ValueError):
809
+ return None
810
+ return parsed if math.isfinite(parsed) else None
811
+
812
+
813
  def compact_expression(record: dict[str, Any]) -> str:
814
  expression = str(record.get("metric_expression") or "").strip()
815
  expression = re.sub(r"\s+", " ", expression)
 
949
  return "Coordinate rendering attached to this case."
950
 
951
 
952
+ def display_metric_record(record: dict[str, Any], visual_record: dict[str, Any]) -> dict[str, Any]:
953
+ if visual_record.get("verified") and visual_record.get("solution_path"):
954
+ return visual_record
955
+ return record
956
+
957
+
958
+ def needs_recovery(record: dict[str, Any], visual_record: dict[str, Any]) -> bool:
959
+ if record.get("record_type") != "reference":
960
+ return False
961
+ reference = numeric_metric(record)
962
+ visual = numeric_metric(visual_record)
963
+ if reference is None or visual is None:
964
+ return True
965
+ return visual > reference * (1.0 + RECOVERY_TOLERANCE_REL)
966
+
967
+
968
  def record_details(record: dict[str, Any], visual_record: dict[str, Any], source: str, image_name: str, expression: str, analytical: Any) -> str:
969
  rows: list[str] = []
970
  rows.append(detail_row("Rendering", esc(visual_provenance(record, visual_record))))
971
+ if record.get("record_type") == "reference" and visual_record.get("verified"):
972
+ rows.append(
973
+ detail_row(
974
+ "Friedman reference",
975
+ f"{esc(metric_symbol(record))} = {esc(metric_value(record))} by {esc(reference_credit(record))}, {esc(reference_date(record))}",
976
+ )
977
+ )
978
  if record.get("record_type") == "verified":
979
  rows.append(detail_row("Submitted", esc(display_date(record))))
980
  notes = str(record.get("notes") or "").strip()
 
1010
  def record_card(record: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
1011
  verified = record.get("record_type") == "verified"
1012
  visual_record = visual_record_for(record, coordinates)
1013
+ display_record = display_metric_record(record, visual_record)
1014
  source = source_link(record)
1015
  expression = compact_expression(record)
1016
  image_name = source_image_name(record)
1017
  analytical = friedman_reference(record).get("analytical_or_proved")
1018
+ visible_author = display_author(record)
1019
+ card_class = "verified" if verified else "reference"
1020
+ if needs_recovery(record, visual_record):
1021
+ card_class += " needs-recovery"
1022
  return f"""
1023
+ <article class="record-card {card_class}">
1024
  <div class="record-top">
1025
  <div class="record-case">{esc(record.get("case"))}</div>
1026
  </div>
1027
  {record_visual(record, coordinates)}
1028
  <div class="record-body">
1029
+ <div class="metric"><strong>{esc(metric_symbol(display_record))} = {esc(metric_value(display_record))}</strong><span>n={esc(record.get("n"))}</span></div>
1030
  <div class="expression">{esc(expression)}</div>
1031
  <div class="record-author">by <strong>{esc(visible_author)}</strong></div>
1032
  {record_details(record, visual_record, source, image_name, expression, analytical)}
 
1056
  }
1057
 
1058
 
1059
+ def shape_phrase(code: str, plural: bool = False) -> str:
1060
+ names = {
1061
+ "cir": ("circle", "circles"),
1062
+ "dom": ("domino", "dominoes"),
1063
+ "tri": ("triangle", "triangles"),
1064
+ "squ": ("square", "squares"),
1065
+ "pen": ("regular pentagon", "regular pentagons"),
1066
+ "hex": ("regular hexagon", "regular hexagons"),
1067
+ "oct": ("regular octagon", "regular octagons"),
1068
+ }
1069
+ singular, many = names.get(code, (code, code + "s"))
1070
+ return many if plural else singular
1071
+
1072
+
1073
+ def parse_setup_codes(setup: str) -> tuple[str, str]:
1074
+ if "in" not in setup:
1075
+ return setup, ""
1076
+ return tuple(setup.split("in", 1)) # type: ignore[return-value]
1077
+
1078
+
1079
+ def family_description(summary: dict[str, Any]) -> str:
1080
+ item_code, container_code = parse_setup_codes(str(summary["setup"]))
1081
+ item = shape_phrase(item_code, plural=True)
1082
+ container = shape_phrase(container_code)
1083
+ updated = f" Friedman lists this family as updated {esc(summary['updated'])}." if summary.get("updated") else ""
1084
+ return (
1085
+ f"This page tracks the smallest known container metric for packing n equal {esc(item)} "
1086
+ f"inside a {esc(container)}. The number shown on each card is the verified coordinate metric "
1087
+ f"for the rendering; the Friedman reference value and source information are in Details. "
1088
+ f"Orange cards need more recovery work because the verified coordinate JSON does not yet match "
1089
+ f"the listed reference within {RECOVERY_TOLERANCE_REL:.2%}.{updated}"
1090
+ )
1091
+
1092
+
1093
  def family_preview(setup: str, coordinates: dict[str, dict[str, Any]]) -> str:
1094
  record = coordinates.get(f"{setup}@10")
1095
  if record is None:
 
1111
  def family_card(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
1112
  setup = str(summary["setup"])
1113
  return f"""
1114
+ <div class="family-card">
1115
  {family_preview(setup, coordinates)}
1116
  <div class="family-card-top">
1117
  <div>
 
1119
  <h3>{esc(summary["title"])}</h3>
1120
  </div>
1121
  </div>
1122
+ </div>
1123
  """
1124
 
1125
 
1126
+ def family_page_html(setup: str | None = None) -> str:
1127
+ choices = setup_choices()
1128
+ setup = setup if setup in choices else (choices[0] if choices else "")
1129
+ summary = setup_summary(setup) if setup else {"records": [], "setup": "", "title": ""}
1130
+ coordinates = coordinate_records_by_case()
1131
  setup = str(summary["setup"])
1132
  records = summary["records"]
1133
  cards = "".join(record_card(record, coordinates) for record in records)
1134
  return f"""
1135
+ <main class="packing-shell">
1136
+ {top_nav()}
1137
+ <section class="section">
1138
+ <div class="contain">
1139
  <div class="family-title-card">
1140
+ <div>
1141
+ <div class="family-code">{esc(setup)}</div>
1142
+ <h2>{esc(summary["title"])}</h2>
1143
+ <p class="family-subtitle">{family_description(summary)}</p>
1144
+ </div>
1145
  </div>
1146
  <div class="record-grid">{cards}</div>
1147
+ </div>
1148
  </section>
1149
+ </main>
1150
  """
1151
 
1152
 
1153
+ def top_nav() -> str:
1154
+ return """
1155
+ <nav class="top-nav">
1156
+ <div class="contain top-nav-inner">
1157
+ <a href="#top" aria-label="Packing Benchmark home">Packing Benchmark</a>
1158
+ <div class="nav-links">
1159
+ <a href="https://erich-friedman.github.io/packing/" target="_blank" rel="noopener noreferrer">Friedman</a>
1160
+ </div>
1161
+ </div>
1162
+ </nav>
1163
+ """
1164
+
1165
+
1166
+ def directory_html() -> str:
1167
  summaries = [setup_summary(setup) for setup in setup_choices()]
1168
  coordinates = coordinate_records_by_case()
1169
  family_cards = "".join(family_card(summary, coordinates) for summary in summaries)
 
1170
  return f"""
1171
  <main class="packing-shell" id="top">
1172
  <section class="hero-stage">
 
1174
  <div>
1175
  <h1>Packing Benchmark</h1>
1176
  <p class="hero-copy">
1177
+ Equal-copy packing records with canonical coordinate JSON, initialized from the public reference
1178
+ families on <a href="https://erich-friedman.github.io/packing/" target="_blank" rel="noopener noreferrer">Erich Friedman's Packing Center</a>.
1179
+ Friedman rows keep their credited finder; rendered cards report our verified coordinate metric and
1180
+ mark unrecovered references in orange.
1181
  </p>
1182
  </div>
1183
  </div>
1184
  </section>
1185
+ {top_nav()}
 
 
 
 
 
 
 
 
 
 
1186
  <section class="section" id="families">
1187
  <div class="contain">
1188
  <div class="section-head">
1189
  <div>
1190
  <h2>Choose a family</h2>
1191
  <p class="section-note">
1192
+ Use the Family Records page to open a family. Each tile previews the n=10 rendering for that setup.
1193
  </p>
1194
  </div>
1195
  </div>
1196
  <div class="family-grid">{family_cards}</div>
1197
  </div>
1198
  </section>
1199
+ </main>
1200
+ """
1201
+
1202
+
1203
+ def browse_html() -> str:
1204
+ return directory_html()
1205
+
1206
+
1207
+ def leaderboard_html() -> str:
1208
+ rows: dict[str, dict[str, Any]] = {}
1209
+ coordinates = coordinate_records_by_case()
1210
+ for record in STORE.public_records(show_all=False):
1211
+ author = display_author(record)
1212
+ entry = rows.setdefault(author, {"wins": 0, "verified": 0, "orange": 0, "examples": []})
1213
+ entry["wins"] += 1
1214
+ if record.get("record_type") == "verified":
1215
+ entry["verified"] += 1
1216
+ visual = visual_record_for(record, coordinates)
1217
+ if needs_recovery(record, visual):
1218
+ entry["orange"] += 1
1219
+ if len(entry["examples"]) < 5:
1220
+ entry["examples"].append(str(record.get("case")))
1221
+
1222
+ sorted_rows = sorted(rows.items(), key=lambda item: (-int(item[1]["wins"]), item[0].lower()))
1223
+ body = "\n".join(
1224
+ f"""
1225
+ <tr>
1226
+ <td>{rank}</td>
1227
+ <td><strong>{esc(author)}</strong></td>
1228
+ <td>{stats['wins']}</td>
1229
+ <td>{stats['verified']}</td>
1230
+ <td>{stats['orange']}</td>
1231
+ <td>{esc(', '.join(stats['examples']))}</td>
1232
+ </tr>
1233
+ """
1234
+ for rank, (author, stats) in enumerate(sorted_rows, start=1)
1235
+ )
1236
+ return f"""
1237
+ <main class="packing-shell">
1238
+ {top_nav()}
1239
+ <section class="section">
1240
+ <div class="contain">
1241
+ <div class="section-head">
1242
+ <div>
1243
+ <h2>Leaderboard</h2>
1244
+ <p class="section-note">
1245
+ Counts current winning records by credited author. If an improved coordinate layout was seeded
1246
+ from an existing Friedman reference, the credit stays with the prior finder rather than Nathan.
1247
+ </p>
1248
+ </div>
1249
+ </div>
1250
+ <table class="leaderboard-table">
1251
+ <thead>
1252
+ <tr><th>#</th><th>Author</th><th>Wins</th><th>Verified submissions</th><th>Needs recovery</th><th>Examples</th></tr>
1253
+ </thead>
1254
+ <tbody>{body}</tbody>
1255
+ </table>
1256
+ </div>
1257
+ </section>
1258
+ </main>
1259
+ """
1260
+
1261
+
1262
+ def submit_schema_intro_html() -> str:
1263
+ return f"""
1264
+ <main class="packing-shell">
1265
+ {top_nav()}
1266
+ <section class="section">
1267
+ <div class="contain">
1268
+ <div class="section-head">
1269
+ <div>
1270
+ <h2>Submit JSON</h2>
1271
+ <p class="section-note">
1272
+ Paste canonical coordinates, verify them locally in the browser session, then submit the verified
1273
+ record. The schema is intentionally small: regular polygons, circles, and rectangles only.
1274
+ </p>
1275
+ </div>
1276
+ </div>
1277
+ <div class="page-card">
1278
+ <p><strong>Coordinate convention:</strong> origin at the container center; item placements are item
1279
+ centers; rotations are radians counterclockwise. Touching is legal. Overlap and protrusion are rejected.</p>
1280
+ <p><strong>Supported shapes:</strong> <code>regular_polygon</code>, <code>circle</code>, and
1281
+ <code>rectangle</code>. Include <code>case</code>, <code>item</code>, <code>container</code>, and
1282
+ <code>placements</code>.</p>
1283
+ </div>
1284
+ </div>
1285
  </section>
1286
  </main>
1287
  """
 
1350
  pretty = json.dumps(stored, indent=2, sort_keys=True)
1351
  sync_status = record.get("sync_status", "dataset sync disabled")
1352
  message = result_markdown(result) + f"\n\nSaved as record `{record['id']}`.\n\nDataset sync: `{sync_status}`."
1353
+ return message, preview_html(stored), pretty, directory_html(), leaderboard_html()
1354
  except Exception as exc:
1355
+ return f"### Submission rejected\n\n- {exc}", "", json_text, directory_html(), leaderboard_html()
1356
+
1357
+
1358
+ def update_family_page(setup: str):
1359
+ return family_page_html(setup)
1360
 
1361
 
1362
  THEME = gr.themes.Base()
1363
 
1364
 
1365
  with gr.Blocks(title="Packing Benchmark") as demo:
1366
+ with gr.Tabs():
1367
+ with gr.Tab("Families"):
1368
+ browse_page = gr.HTML(directory_html())
1369
+
1370
+ with gr.Tab("Family Records"):
1371
+ choices = setup_choices()
1372
+ initial_setup = choices[0] if choices else ""
1373
+ family_select = gr.Dropdown(
1374
+ choices=choices,
1375
+ value=initial_setup,
1376
+ label="Family",
1377
+ elem_id="family-selector",
1378
+ )
1379
+ family_page = gr.HTML(family_page_html(initial_setup))
1380
+
1381
+ with gr.Tab("Leaderboard"):
1382
+ leaderboard_page = gr.HTML(leaderboard_html())
1383
+
1384
+ with gr.Tab("Submit JSON"):
1385
+ gr.HTML(submit_schema_intro_html())
1386
+ with gr.Group(elem_classes=["submit-shell"]):
1387
+ with gr.Row():
1388
+ with gr.Column(scale=7):
1389
+ json_code = gr.Code(value=SAMPLE_JSON, language="json", label="Canonical coordinate JSON", lines=18)
1390
+ json_file = gr.File(label="or upload .json", file_types=[".json"], type="filepath")
1391
+ with gr.Column(scale=5):
1392
+ submitter = gr.Textbox(label="Submitted by", placeholder="Name, handle, or lab")
1393
+ source_url = gr.Textbox(label="Source URL", placeholder="Optional paper, repo, or run link")
1394
+ notes = gr.Textbox(label="Notes", lines=3, placeholder="Optional method or provenance note")
1395
+ tolerance = gr.Number(value=DEFAULT_TOLERANCE, label="Verification tolerance")
1396
+ with gr.Row():
1397
+ verify_btn = gr.Button("Verify", variant="secondary")
1398
+ submit_btn = gr.Button("Submit Verified Record", variant="primary")
1399
+ report = gr.Markdown()
1400
+ preview = gr.HTML(label="Browser render")
1401
+ normalized_json = gr.Code(label="Normalized JSON", language="json", lines=10)
1402
+
1403
+ family_select.change(update_family_page, inputs=[family_select], outputs=[family_page])
1404
  verify_btn.click(verify_only, inputs=[json_code, json_file, tolerance], outputs=[report, preview, normalized_json])
1405
  submit_btn.click(
1406
  submit_solution,
1407
  inputs=[json_code, json_file, submitter, notes, source_url, tolerance],
1408
+ outputs=[report, preview, normalized_json, browse_page, leaderboard_page],
1409
  )
1410
 
1411