NathanRoll commited on
Commit
ef637fb
·
verified ·
1 Parent(s): 33b1c3d

Add record dates, recent markers, previous bests, and integrity stamps

Browse files
app.py CHANGED
@@ -12,6 +12,13 @@ from typing import Any
12
 
13
  import gradio as gr
14
 
 
 
 
 
 
 
 
15
  from packing_benchmark.hub_sync import maybe_hydrate_from_dataset
16
  from packing_benchmark.store import SolutionStore, is_trivial_record, metric_matches_reference
17
  from packing_benchmark.verifier import (
@@ -45,6 +52,19 @@ def cached_verified_records() -> list[dict[str, Any]]:
45
  return STORE.load_records()
46
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  @lru_cache(maxsize=64)
49
  def cached_public_records(setup: str = "", show_all: bool = False) -> list[dict[str, Any]]:
50
  return STORE.public_records(setup or None, show_all=show_all)
@@ -53,6 +73,7 @@ def cached_public_records(setup: str = "", show_all: bool = False) -> list[dict[
53
  def clear_page_caches() -> None:
54
  cached_reference_records.cache_clear()
55
  cached_verified_records.cache_clear()
 
56
  cached_public_records.cache_clear()
57
  setup_choices.cache_clear()
58
  coordinate_records_by_case.cache_clear()
@@ -451,6 +472,23 @@ footer,
451
  font-weight: 700;
452
  }
453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  .render-stage {
455
  height: 178px;
456
  margin: 8px 10px 0;
@@ -738,6 +776,32 @@ footer,
738
  color: var(--link) !important;
739
  }
740
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
  .record-modal .modal-panel .modal-case {
742
  color: var(--quiet) !important;
743
  }
@@ -1670,13 +1734,22 @@ def submission_guide_html() -> str:
1670
 
1671
  def setup_title(setup: str) -> str:
1672
  for record in cached_reference_records():
 
 
1673
  if record.get("setup") == setup and record.get("title"):
1674
  return str(record["title"])
1675
  return setup
1676
 
1677
 
1678
  def setup_updated(setup: str) -> str:
 
 
 
 
 
1679
  for record in cached_reference_records():
 
 
1680
  if record.get("setup") != setup:
1681
  continue
1682
  meta = record.get("friedman_reference")
@@ -1687,17 +1760,25 @@ def setup_updated(setup: str) -> str:
1687
  return ""
1688
 
1689
 
 
 
 
 
 
 
 
 
1690
  @lru_cache(maxsize=96)
1691
  def setup_choices() -> list[str]:
1692
  choices = {
1693
  str(record["setup"])
1694
  for record in cached_reference_records()
1695
- if record.get("setup")
1696
  }
1697
  choices.update(
1698
  str(record["setup"])
1699
  for record in cached_verified_records()
1700
- if record.get("verified") and record.get("setup")
1701
  )
1702
  return sorted(choices, key=lambda setup: (setup_title(setup).lower(), setup.lower()))
1703
 
@@ -1710,21 +1791,30 @@ def is_analytical(record: dict[str, Any]) -> bool:
1710
  return any(token in text for token in proof_tokens) or any(token in expression.lower() for token in exact_tokens)
1711
 
1712
 
 
 
 
 
 
 
 
 
 
1713
  def display_date(record: dict[str, Any]) -> str:
1714
- submitted_at = str(record.get("submitted_at") or "").strip()
1715
- if submitted_at:
1716
- try:
1717
- parsed = datetime.fromisoformat(submitted_at.replace("Z", "+00:00"))
1718
- return parsed.strftime("%b %-d, %Y")
1719
- except ValueError:
1720
- return submitted_at
 
 
 
 
1721
 
1722
- text = str(record.get("reference_text") or "")
1723
- month_pattern = "|".join(MONTHS)
1724
- match = re.search(rf"\bin\s+((?:{month_pattern})\s+\d{{4}}|\d{{4}})\b", text)
1725
- if match:
1726
- return match.group(1)
1727
- return "date not listed"
1728
 
1729
 
1730
  def friedman_reference(record: dict[str, Any]) -> dict[str, Any]:
@@ -1793,6 +1883,8 @@ def author_credit_html(author: str) -> str:
1793
  def existing_author_choices() -> list[str]:
1794
  seen: dict[str, str] = {}
1795
  for record in [*cached_reference_records(), *cached_verified_records()]:
 
 
1796
  name = display_author(record)
1797
  normalized = name.strip()
1798
  if not normalized or normalized.lower() in {"unknown", "anonymous"}:
@@ -1956,6 +2048,57 @@ def detail_row(label: str, value: str) -> str:
1956
  return f"<dt>{esc(label)}</dt><dd>{value}</dd>"
1957
 
1958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1959
  def visual_provenance(record: dict[str, Any], visual_record: dict[str, Any]) -> str:
1960
  source_case = str(visual_record.get("monotone_upper_bound_from_case") or "").strip()
1961
  if source_case:
@@ -2008,6 +2151,8 @@ def needs_recovery(record: dict[str, Any], visual_record: dict[str, Any]) -> boo
2008
  def record_detail_rows(record: dict[str, Any], visual_record: dict[str, Any], source: str, image_name: str, expression: str, analytical: Any) -> list[str]:
2009
  rows: list[str] = []
2010
  rows.append(detail_row("Rendering", esc(visual_provenance(record, visual_record))))
 
 
2011
  if record.get("record_type") == "reference" and visual_record.get("verified"):
2012
  if is_trivial_record(record):
2013
  rows.append(
@@ -2030,19 +2175,13 @@ def record_detail_rows(record: dict[str, Any], visual_record: dict[str, Any], so
2030
  )
2031
  )
2032
  if record.get("record_type") == "verified":
2033
- rows.append(detail_row("Submitted", esc(display_date(record))))
 
 
2034
  notes = str(record.get("notes") or "").strip()
2035
  if notes:
2036
  rows.append(detail_row("Notes", esc(notes)))
2037
- previous = (
2038
- f"{esc(metric_symbol(record))} = {esc(metric_value({'metric_value': record.get('reference_side'), 'side': record.get('reference_side'), 'metric_symbol': metric_symbol(record)}))}"
2039
- f" by {esc(reference_credit(record))}, {esc(reference_date(record))}"
2040
- if record.get("reference_side") is not None
2041
- else ""
2042
- )
2043
- rows.append(detail_row("Previous record", previous))
2044
- else:
2045
- rows.append(detail_row("Date", esc(reference_date(record))))
2046
 
2047
  rows.append(detail_row("Formula", esc(expression)))
2048
  if image_name:
@@ -2107,13 +2246,16 @@ def record_card(record: dict[str, Any], coordinates: dict[str, dict[str, Any]])
2107
  card_class = "verified" if verified else "reference"
2108
  if needs_recovery(record, visual_record):
2109
  card_class += " needs-recovery"
 
 
 
2110
  anchor = f"{record.get('setup')}-{record.get('n')}"
2111
  modal_id = f"modal-{anchor}"
2112
  return f"""
2113
  <article class="record-card {card_class}" id="{esc(anchor)}">
2114
  <a class="record-open" href="#{esc(modal_id)}" aria-label="Open {esc(record.get("case"))} record information">
2115
  <div class="record-top">
2116
- <div class="record-case">{esc(record.get("case"))}</div>
2117
  </div>
2118
  {record_visual(record, coordinates)}
2119
  <div class="record-body">
@@ -2144,6 +2286,7 @@ def setup_summary(setup: str) -> dict[str, Any]:
2144
  "coordinate_count": coordinate_count,
2145
  "analytic": sum(1 for r in records if is_analytical(r)),
2146
  "updated": setup_updated(setup),
 
2147
  "n_range": f"{min(n_values)}-{max(n_values)}" if n_values else "not listed",
2148
  }
2149
 
@@ -2225,13 +2368,14 @@ def family_preview(setup: str, coordinates: dict[str, dict[str, Any]]) -> str:
2225
  def family_card(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
2226
  setup = str(summary["setup"])
2227
  cls = inner_class(setup)
 
2228
  return f"""
2229
  <a class="family-card front-family-card {esc(cls)}" href="?family={esc(setup)}" aria-label="Open {esc(summary["title"])} records">
2230
  {family_preview(setup, coordinates)}
2231
  <div class="family-card-top">
2232
  <div>
2233
  <div class="family-code">{esc(setup)}</div>
2234
- <h3>{esc(summary["title"])}</h3>
2235
  </div>
2236
  </div>
2237
  </a>
@@ -2624,18 +2768,24 @@ def current_top_for_case(case: str) -> dict[str, Any] | None:
2624
  return min(candidates, key=lambda record: record_metric_float(record) or float("inf"))
2625
 
2626
 
2627
- def submission_gate(result: dict[str, Any]) -> tuple[bool, str]:
2628
- if not result.get("ok"):
2629
- return False, "Geometry verification failed."
2630
- submitted = result.get("side")
2631
  try:
2632
- submitted_metric = float(submitted)
2633
  except (TypeError, ValueError):
2634
- return False, "The evaluator could not compute a submitted metric."
2635
- if not math.isfinite(submitted_metric):
2636
- return False, "The evaluator returned a non-finite metric."
 
 
 
2637
 
 
 
 
2638
  current = current_top_for_case(str(result.get("case") or ""))
 
 
 
2639
  if current is None:
2640
  return True, "No current top record exists for this case; a valid geometry can be submitted."
2641
 
@@ -2647,11 +2797,11 @@ def submission_gate(result: dict[str, Any]) -> tuple[bool, str]:
2647
  if improvement >= SUBMISSION_MIN_IMPROVEMENT:
2648
  return (
2649
  True,
2650
- f"Improves the current top metric by {improvement:.10f}; required improvement is at least {SUBMISSION_MIN_IMPROVEMENT:.4f}.",
2651
  )
2652
  return (
2653
  False,
2654
- f"Current top metric is {current_metric:.10f}. Submitted metric is {submitted_metric:.10f}. "
2655
  f"Improvement is {improvement:.10f}; required improvement is at least {SUBMISSION_MIN_IMPROVEMENT:.4f}.",
2656
  )
2657
 
 
12
 
13
  import gradio as gr
14
 
15
+ from packing_benchmark.dates import (
16
+ date_from_friedman_record,
17
+ date_from_submission,
18
+ display_date_info,
19
+ is_recent_date_info,
20
+ parse_friedman_date_text,
21
+ )
22
  from packing_benchmark.hub_sync import maybe_hydrate_from_dataset
23
  from packing_benchmark.store import SolutionStore, is_trivial_record, metric_matches_reference
24
  from packing_benchmark.verifier import (
 
52
  return STORE.load_records()
53
 
54
 
55
+ @lru_cache(maxsize=1)
56
+ def cached_family_updates() -> dict[str, Any]:
57
+ path = DATA_DIR / "friedman_family_updates.json"
58
+ if not path.exists():
59
+ return {}
60
+ try:
61
+ payload = json.loads(path.read_text())
62
+ except Exception:
63
+ return {}
64
+ families = payload.get("families", {})
65
+ return families if isinstance(families, dict) else {}
66
+
67
+
68
  @lru_cache(maxsize=64)
69
  def cached_public_records(setup: str = "", show_all: bool = False) -> list[dict[str, Any]]:
70
  return STORE.public_records(setup or None, show_all=show_all)
 
73
  def clear_page_caches() -> None:
74
  cached_reference_records.cache_clear()
75
  cached_verified_records.cache_clear()
76
+ cached_family_updates.cache_clear()
77
  cached_public_records.cache_clear()
78
  setup_choices.cache_clear()
79
  coordinate_records_by_case.cache_clear()
 
472
  font-weight: 700;
473
  }
474
 
475
+ .recent-dot {
476
+ display: inline-block;
477
+ width: 9px;
478
+ height: 9px;
479
+ margin-left: 7px;
480
+ background: var(--green-strong);
481
+ border: 1px solid #174c1b;
482
+ border-radius: 999px;
483
+ vertical-align: 1px;
484
+ }
485
+
486
+ .family-card h3 .recent-dot {
487
+ width: 10px;
488
+ height: 10px;
489
+ vertical-align: 2px;
490
+ }
491
+
492
  .render-stage {
493
  height: 178px;
494
  margin: 8px 10px 0;
 
776
  color: var(--link) !important;
777
  }
778
 
779
+ .previous-bests-table {
780
+ width: 100%;
781
+ border-collapse: collapse;
782
+ background: var(--paper) !important;
783
+ color: var(--ink) !important;
784
+ font-size: 12px;
785
+ }
786
+
787
+ .previous-bests-table th,
788
+ .previous-bests-table td {
789
+ padding: 6px 7px;
790
+ border: 1px solid var(--line-soft);
791
+ color: var(--ink) !important;
792
+ text-align: left;
793
+ vertical-align: top;
794
+ }
795
+
796
+ .previous-bests-table th {
797
+ background: var(--paper-soft) !important;
798
+ font-weight: 700;
799
+ }
800
+
801
+ .date-source {
802
+ color: var(--muted) !important;
803
+ }
804
+
805
  .record-modal .modal-panel .modal-case {
806
  color: var(--quiet) !important;
807
  }
 
1734
 
1735
  def setup_title(setup: str) -> str:
1736
  for record in cached_reference_records():
1737
+ if record.get("_integrity_errors"):
1738
+ continue
1739
  if record.get("setup") == setup and record.get("title"):
1740
  return str(record["title"])
1741
  return setup
1742
 
1743
 
1744
  def setup_updated(setup: str) -> str:
1745
+ update = cached_family_updates().get(setup)
1746
+ if isinstance(update, dict):
1747
+ text = str(update.get("text") or "").strip()
1748
+ if text:
1749
+ return text
1750
  for record in cached_reference_records():
1751
+ if record.get("_integrity_errors"):
1752
+ continue
1753
  if record.get("setup") != setup:
1754
  continue
1755
  meta = record.get("friedman_reference")
 
1760
  return ""
1761
 
1762
 
1763
+ def setup_update_date_info(setup: str) -> dict[str, Any] | None:
1764
+ update = cached_family_updates().get(setup)
1765
+ if isinstance(update, dict):
1766
+ return update
1767
+ updated = setup_updated(setup)
1768
+ return parse_friedman_date_text(updated, "Erich Friedman Packing Center index") if updated else None
1769
+
1770
+
1771
  @lru_cache(maxsize=96)
1772
  def setup_choices() -> list[str]:
1773
  choices = {
1774
  str(record["setup"])
1775
  for record in cached_reference_records()
1776
+ if record.get("setup") and not record.get("_integrity_errors")
1777
  }
1778
  choices.update(
1779
  str(record["setup"])
1780
  for record in cached_verified_records()
1781
+ if record.get("verified") and record.get("setup") and not record.get("_integrity_errors")
1782
  )
1783
  return sorted(choices, key=lambda setup: (setup_title(setup).lower(), setup.lower()))
1784
 
 
1791
  return any(token in text for token in proof_tokens) or any(token in expression.lower() for token in exact_tokens)
1792
 
1793
 
1794
+ def effective_record_date(record: dict[str, Any]) -> dict[str, Any]:
1795
+ info = record.get("record_date")
1796
+ if isinstance(info, dict):
1797
+ return info
1798
+ if record.get("record_type") == "reference" or record.get("frontend_seed") or is_trivial_record(record):
1799
+ return date_from_friedman_record(record)
1800
+ return date_from_submission(record)
1801
+
1802
+
1803
  def display_date(record: dict[str, Any]) -> str:
1804
+ return display_date_info(effective_record_date(record))
1805
+
1806
+
1807
+ def record_date_with_source(record: dict[str, Any]) -> str:
1808
+ info = effective_record_date(record)
1809
+ text = display_date_info(info)
1810
+ source = str(info.get("source") or "").strip() if isinstance(info, dict) else ""
1811
+ if source and text != "date not listed":
1812
+ return f"{esc(text)} <span class=\"date-source\">({esc(source)})</span>"
1813
+ return esc(text)
1814
+
1815
 
1816
+ def is_recent_record(record: dict[str, Any]) -> bool:
1817
+ return is_recent_date_info(effective_record_date(record))
 
 
 
 
1818
 
1819
 
1820
  def friedman_reference(record: dict[str, Any]) -> dict[str, Any]:
 
1883
  def existing_author_choices() -> list[str]:
1884
  seen: dict[str, str] = {}
1885
  for record in [*cached_reference_records(), *cached_verified_records()]:
1886
+ if record.get("_integrity_errors"):
1887
+ continue
1888
  name = display_author(record)
1889
  normalized = name.strip()
1890
  if not normalized or normalized.lower() in {"unknown", "anonymous"}:
 
2048
  return f"<dt>{esc(label)}</dt><dd>{value}</dd>"
2049
 
2050
 
2051
+ def metric_text_from_values(symbol: Any, value: Any) -> str:
2052
+ try:
2053
+ return f"{esc(symbol or 's')} = {float(value):.5f}"
2054
+ except (TypeError, ValueError):
2055
+ return f"{esc(symbol or 's')} = {esc(value)}"
2056
+
2057
+
2058
+ def previous_bests_table(record: dict[str, Any]) -> str:
2059
+ previous = record.get("previous_bests")
2060
+ if not isinstance(previous, list) or not previous:
2061
+ return ""
2062
+ rows = []
2063
+ for item in previous:
2064
+ if not isinstance(item, dict):
2065
+ continue
2066
+ date_info = item.get("date")
2067
+ date_text = display_date_info(date_info if isinstance(date_info, dict) else None)
2068
+ source_url = str(item.get("source_url") or "").strip()
2069
+ source = str(item.get("source") or "previous record").strip()
2070
+ source_cell = f'<a href="{esc(source_url)}" target="_blank" rel="noopener noreferrer">{esc(source)}</a>' if source_url else esc(source)
2071
+ rows.append(
2072
+ "<tr>"
2073
+ f"<td>{esc(item.get('author') or 'unknown')}</td>"
2074
+ f"<td>{esc(date_text)}</td>"
2075
+ f"<td>{metric_text_from_values(item.get('metric_symbol') or metric_symbol(record), item.get('metric_value'))}</td>"
2076
+ f"<td>{source_cell}</td>"
2077
+ "</tr>"
2078
+ )
2079
+ if not rows:
2080
+ return ""
2081
+ return (
2082
+ '<table class="previous-bests-table">'
2083
+ "<thead><tr><th>Author</th><th>Date</th><th>Metric</th><th>Source</th></tr></thead>"
2084
+ f"<tbody>{''.join(rows)}</tbody></table>"
2085
+ )
2086
+
2087
+
2088
+ def integrity_status_text(record: dict[str, Any], visual_record: dict[str, Any]) -> str:
2089
+ errors = list(record.get("_integrity_errors") or [])
2090
+ if visual_record is not record:
2091
+ errors.extend(list(visual_record.get("_integrity_errors") or []))
2092
+ if errors:
2093
+ return "failed: " + "; ".join(esc(error) for error in errors)
2094
+ integrity = record.get("integrity") or visual_record.get("integrity")
2095
+ if isinstance(integrity, dict) and integrity.get("signature"):
2096
+ return "verified with signed record and solution hashes"
2097
+ if isinstance(integrity, dict):
2098
+ return "verified with record and solution hashes"
2099
+ return "legacy row; no integrity stamp"
2100
+
2101
+
2102
  def visual_provenance(record: dict[str, Any], visual_record: dict[str, Any]) -> str:
2103
  source_case = str(visual_record.get("monotone_upper_bound_from_case") or "").strip()
2104
  if source_case:
 
2151
  def record_detail_rows(record: dict[str, Any], visual_record: dict[str, Any], source: str, image_name: str, expression: str, analytical: Any) -> list[str]:
2152
  rows: list[str] = []
2153
  rows.append(detail_row("Rendering", esc(visual_provenance(record, visual_record))))
2154
+ rows.append(detail_row("Record date", record_date_with_source(record)))
2155
+ rows.append(detail_row("Database integrity", esc(integrity_status_text(record, visual_record))))
2156
  if record.get("record_type") == "reference" and visual_record.get("verified"):
2157
  if is_trivial_record(record):
2158
  rows.append(
 
2175
  )
2176
  )
2177
  if record.get("record_type") == "verified":
2178
+ database_inserted_at = str(record.get("database_inserted_at") or record.get("submitted_at") or "").strip()
2179
+ database_info = date_from_submission({"submitted_at": database_inserted_at})
2180
+ rows.append(detail_row("Database entry", esc(display_date_info(database_info))))
2181
  notes = str(record.get("notes") or "").strip()
2182
  if notes:
2183
  rows.append(detail_row("Notes", esc(notes)))
2184
+ rows.append(detail_row("Previous bests", previous_bests_table(record)))
 
 
 
 
 
 
 
 
2185
 
2186
  rows.append(detail_row("Formula", esc(expression)))
2187
  if image_name:
 
2246
  card_class = "verified" if verified else "reference"
2247
  if needs_recovery(record, visual_record):
2248
  card_class += " needs-recovery"
2249
+ if is_recent_record(record):
2250
+ card_class += " recent-record"
2251
+ recent = '<span class="recent-dot" title="New in the last 7 days"></span>' if is_recent_record(record) else ""
2252
  anchor = f"{record.get('setup')}-{record.get('n')}"
2253
  modal_id = f"modal-{anchor}"
2254
  return f"""
2255
  <article class="record-card {card_class}" id="{esc(anchor)}">
2256
  <a class="record-open" href="#{esc(modal_id)}" aria-label="Open {esc(record.get("case"))} record information">
2257
  <div class="record-top">
2258
+ <div class="record-case">{esc(record.get("case"))}{recent}</div>
2259
  </div>
2260
  {record_visual(record, coordinates)}
2261
  <div class="record-body">
 
2286
  "coordinate_count": coordinate_count,
2287
  "analytic": sum(1 for r in records if is_analytical(r)),
2288
  "updated": setup_updated(setup),
2289
+ "updated_recent": is_recent_date_info(setup_update_date_info(setup)),
2290
  "n_range": f"{min(n_values)}-{max(n_values)}" if n_values else "not listed",
2291
  }
2292
 
 
2368
  def family_card(summary: dict[str, Any], coordinates: dict[str, dict[str, Any]]) -> str:
2369
  setup = str(summary["setup"])
2370
  cls = inner_class(setup)
2371
+ recent = '<span class="recent-dot" title="Updated in the last 7 days"></span>' if summary.get("updated_recent") else ""
2372
  return f"""
2373
  <a class="family-card front-family-card {esc(cls)}" href="?family={esc(setup)}" aria-label="Open {esc(summary["title"])} records">
2374
  {family_preview(setup, coordinates)}
2375
  <div class="family-card-top">
2376
  <div>
2377
  <div class="family-code">{esc(setup)}</div>
2378
+ <h3>{esc(summary["title"])}{recent}</h3>
2379
  </div>
2380
  </div>
2381
  </a>
 
2768
  return min(candidates, key=lambda record: record_metric_float(record) or float("inf"))
2769
 
2770
 
2771
+ def submitted_metric_for_result(result: dict[str, Any], current: dict[str, Any] | None) -> tuple[str, float | None]:
 
 
 
2772
  try:
2773
+ side = float(result.get("side"))
2774
  except (TypeError, ValueError):
2775
+ return "s", None
2776
+ if not math.isfinite(side):
2777
+ return "s", None
2778
+ symbol = str((current or {}).get("metric_symbol") or "s")
2779
+ return symbol, side * 0.5 if symbol == "r" else side
2780
+
2781
 
2782
+ def submission_gate(result: dict[str, Any]) -> tuple[bool, str]:
2783
+ if not result.get("ok"):
2784
+ return False, "Geometry verification failed."
2785
  current = current_top_for_case(str(result.get("case") or ""))
2786
+ symbol, submitted_metric = submitted_metric_for_result(result, current)
2787
+ if submitted_metric is None:
2788
+ return False, "The evaluator could not compute a submitted metric."
2789
  if current is None:
2790
  return True, "No current top record exists for this case; a valid geometry can be submitted."
2791
 
 
2797
  if improvement >= SUBMISSION_MIN_IMPROVEMENT:
2798
  return (
2799
  True,
2800
+ f"Improves the current top {symbol} metric by {improvement:.10f}; required improvement is at least {SUBMISSION_MIN_IMPROVEMENT:.4f}.",
2801
  )
2802
  return (
2803
  False,
2804
+ f"Current top {symbol} metric is {current_metric:.10f}. Submitted {symbol} metric is {submitted_metric:.10f}. "
2805
  f"Improvement is {improvement:.10f}; required improvement is at least {SUBMISSION_MIN_IMPROVEMENT:.4f}.",
2806
  )
2807
 
data/friedman_family_updates.json ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "families": {
3
+ "cirincir": {
4
+ "precision": "day",
5
+ "raw": "6/30/11",
6
+ "setup": "cirincir",
7
+ "sort_date": "2011-06-30",
8
+ "source": "Erich Friedman Packing Center index",
9
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
10
+ "text": "Jun 30, 2011",
11
+ "title": "Circles in Circles"
12
+ },
13
+ "cirinhex": {
14
+ "precision": "day",
15
+ "raw": "7/19/12",
16
+ "setup": "cirinhex",
17
+ "sort_date": "2012-07-19",
18
+ "source": "Erich Friedman Packing Center index",
19
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
20
+ "text": "Jul 19, 2012",
21
+ "title": "Circles in Hexagons"
22
+ },
23
+ "cirinpen": {
24
+ "precision": "day",
25
+ "raw": "7/10/12",
26
+ "setup": "cirinpen",
27
+ "sort_date": "2012-07-10",
28
+ "source": "Erich Friedman Packing Center index",
29
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
30
+ "text": "Jul 10, 2012",
31
+ "title": "Circles in Pentagons"
32
+ },
33
+ "cirinsqu": {
34
+ "precision": "day",
35
+ "raw": "2/11/26",
36
+ "setup": "cirinsqu",
37
+ "sort_date": "2026-02-11",
38
+ "source": "Erich Friedman Packing Center index",
39
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
40
+ "text": "Feb 11, 2026",
41
+ "title": "Circles in Squares"
42
+ },
43
+ "cirintri": {
44
+ "precision": "day",
45
+ "raw": "7/4/11",
46
+ "setup": "cirintri",
47
+ "sort_date": "2011-07-04",
48
+ "source": "Erich Friedman Packing Center index",
49
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
50
+ "text": "Jul 4, 2011",
51
+ "title": "Circles in Triangles"
52
+ },
53
+ "domindom": {
54
+ "precision": "day",
55
+ "raw": "4/3/26",
56
+ "setup": "domindom",
57
+ "sort_date": "2026-04-03",
58
+ "source": "Erich Friedman Packing Center index",
59
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
60
+ "text": "Apr 3, 2026",
61
+ "title": "Dominoes in Dominoes"
62
+ },
63
+ "domintri": {
64
+ "precision": "day",
65
+ "raw": "4/23/26",
66
+ "setup": "domintri",
67
+ "sort_date": "2026-04-23",
68
+ "source": "Erich Friedman Packing Center index",
69
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
70
+ "text": "Apr 23, 2026",
71
+ "title": "Dominoes in Triangles"
72
+ },
73
+ "hexincir": {
74
+ "precision": "day",
75
+ "raw": "4/28/26",
76
+ "setup": "hexincir",
77
+ "sort_date": "2026-04-28",
78
+ "source": "Erich Friedman Packing Center index",
79
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
80
+ "text": "Apr 28, 2026",
81
+ "title": "Hexagons in Circles"
82
+ },
83
+ "hexindom": {
84
+ "precision": "day",
85
+ "raw": "4/6/26",
86
+ "setup": "hexindom",
87
+ "sort_date": "2026-04-06",
88
+ "source": "Erich Friedman Packing Center index",
89
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
90
+ "text": "Apr 6, 2026",
91
+ "title": "Hexagons in Dominoes"
92
+ },
93
+ "hexinhex": {
94
+ "precision": "day",
95
+ "raw": "4/15/26",
96
+ "setup": "hexinhex",
97
+ "sort_date": "2026-04-15",
98
+ "source": "Erich Friedman Packing Center index",
99
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
100
+ "text": "Apr 15, 2026",
101
+ "title": "Hexagons in Hexagons"
102
+ },
103
+ "hexinsqu": {
104
+ "precision": "day",
105
+ "raw": "4/28/26",
106
+ "setup": "hexinsqu",
107
+ "sort_date": "2026-04-28",
108
+ "source": "Erich Friedman Packing Center index",
109
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
110
+ "text": "Apr 28, 2026",
111
+ "title": "Hexagons in Squares"
112
+ },
113
+ "hexintri": {
114
+ "precision": "day",
115
+ "raw": "5/1/26",
116
+ "setup": "hexintri",
117
+ "sort_date": "2026-05-01",
118
+ "source": "Erich Friedman Packing Center index",
119
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
120
+ "text": "May 1, 2026",
121
+ "title": "Hexagons in Triangles"
122
+ },
123
+ "octinoct": {
124
+ "precision": "day",
125
+ "raw": "3/27/26",
126
+ "setup": "octinoct",
127
+ "sort_date": "2026-03-27",
128
+ "source": "Erich Friedman Packing Center index",
129
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
130
+ "text": "Mar 27, 2026",
131
+ "title": "Octagons in Octagons"
132
+ },
133
+ "penincir": {
134
+ "precision": "day",
135
+ "raw": "4/13/26",
136
+ "setup": "penincir",
137
+ "sort_date": "2026-04-13",
138
+ "source": "Erich Friedman Packing Center index",
139
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
140
+ "text": "Apr 13, 2026",
141
+ "title": "Pentagons in Circles"
142
+ },
143
+ "peninpen": {
144
+ "precision": "day",
145
+ "raw": "4/29/26",
146
+ "setup": "peninpen",
147
+ "sort_date": "2026-04-29",
148
+ "source": "Erich Friedman Packing Center index",
149
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
150
+ "text": "Apr 29, 2026",
151
+ "title": "Pentagons in Pentagons"
152
+ },
153
+ "peninsqu": {
154
+ "precision": "day",
155
+ "raw": "4/30/26",
156
+ "setup": "peninsqu",
157
+ "sort_date": "2026-04-30",
158
+ "source": "Erich Friedman Packing Center index",
159
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
160
+ "text": "Apr 30, 2026",
161
+ "title": "Pentagons in Squares"
162
+ },
163
+ "squincir": {
164
+ "precision": "day",
165
+ "raw": "8/9/11",
166
+ "setup": "squincir",
167
+ "sort_date": "2011-08-09",
168
+ "source": "Erich Friedman Packing Center index",
169
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
170
+ "text": "Aug 9, 2011",
171
+ "title": "Squares in Circles"
172
+ },
173
+ "squindom": {
174
+ "precision": "day",
175
+ "raw": "8/11/12",
176
+ "setup": "squindom",
177
+ "sort_date": "2012-08-11",
178
+ "source": "Erich Friedman Packing Center index",
179
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
180
+ "text": "Aug 11, 2012",
181
+ "title": "Squares in Dominoes"
182
+ },
183
+ "squinhex": {
184
+ "precision": "day",
185
+ "raw": "5/1/26",
186
+ "setup": "squinhex",
187
+ "sort_date": "2026-05-01",
188
+ "source": "Erich Friedman Packing Center index",
189
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
190
+ "text": "May 1, 2026",
191
+ "title": "Squares in Hexagons"
192
+ },
193
+ "squinoct": {
194
+ "precision": "day",
195
+ "raw": "3/17/26",
196
+ "setup": "squinoct",
197
+ "sort_date": "2026-03-17",
198
+ "source": "Erich Friedman Packing Center index",
199
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
200
+ "text": "Mar 17, 2026",
201
+ "title": "Squares in Octagons"
202
+ },
203
+ "squinpen": {
204
+ "precision": "day",
205
+ "raw": "4/30/26",
206
+ "setup": "squinpen",
207
+ "sort_date": "2026-04-30",
208
+ "source": "Erich Friedman Packing Center index",
209
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
210
+ "text": "Apr 30, 2026",
211
+ "title": "Squares in Pentagons"
212
+ },
213
+ "squintri": {
214
+ "precision": "day",
215
+ "raw": "4/30/26",
216
+ "setup": "squintri",
217
+ "sort_date": "2026-04-30",
218
+ "source": "Erich Friedman Packing Center index",
219
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
220
+ "text": "Apr 30, 2026",
221
+ "title": "Squares in Triangles"
222
+ },
223
+ "triincir": {
224
+ "precision": "day",
225
+ "raw": "8/31/19",
226
+ "setup": "triincir",
227
+ "sort_date": "2019-08-31",
228
+ "source": "Erich Friedman Packing Center index",
229
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
230
+ "text": "Aug 31, 2019",
231
+ "title": "Triangles in Circles"
232
+ },
233
+ "triindom": {
234
+ "precision": "day",
235
+ "raw": "4/4/26",
236
+ "setup": "triindom",
237
+ "sort_date": "2026-04-04",
238
+ "source": "Erich Friedman Packing Center index",
239
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
240
+ "text": "Apr 4, 2026",
241
+ "title": "Triangles in Dominoes"
242
+ },
243
+ "triinhex": {
244
+ "precision": "day",
245
+ "raw": "4/30/26",
246
+ "setup": "triinhex",
247
+ "sort_date": "2026-04-30",
248
+ "source": "Erich Friedman Packing Center index",
249
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
250
+ "text": "Apr 30, 2026",
251
+ "title": "Triangles in Hexagons"
252
+ },
253
+ "triinoct": {
254
+ "precision": "day",
255
+ "raw": "3/22/26",
256
+ "setup": "triinoct",
257
+ "sort_date": "2026-03-22",
258
+ "source": "Erich Friedman Packing Center index",
259
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
260
+ "text": "Mar 22, 2026",
261
+ "title": "Triangles in Octagons"
262
+ },
263
+ "triinpen": {
264
+ "precision": "day",
265
+ "raw": "4/30/26",
266
+ "setup": "triinpen",
267
+ "sort_date": "2026-04-30",
268
+ "source": "Erich Friedman Packing Center index",
269
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
270
+ "text": "Apr 30, 2026",
271
+ "title": "Triangles in Pentagons"
272
+ },
273
+ "triinsqu": {
274
+ "precision": "day",
275
+ "raw": "5/1/26",
276
+ "setup": "triinsqu",
277
+ "sort_date": "2026-05-01",
278
+ "source": "Erich Friedman Packing Center index",
279
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
280
+ "text": "May 1, 2026",
281
+ "title": "Triangles in Squares"
282
+ },
283
+ "triintri": {
284
+ "precision": "day",
285
+ "raw": "4/30/26",
286
+ "setup": "triintri",
287
+ "sort_date": "2026-04-30",
288
+ "source": "Erich Friedman Packing Center index",
289
+ "source_url": "https://erich-friedman.github.io/packing/index.html",
290
+ "text": "Apr 30, 2026",
291
+ "title": "Triangles in Triangles"
292
+ }
293
+ },
294
+ "retrieved_at": "2026-05-01T21:37:50+00:00",
295
+ "source_url": "https://erich-friedman.github.io/packing/index.html"
296
+ }
data/friedman_references.jsonl CHANGED
The diff for this file is too large to render. See raw diff
 
data/records.jsonl CHANGED
The diff for this file is too large to render. See raw diff
 
packing_benchmark/dates.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from datetime import UTC, date, datetime, timedelta
5
+ from typing import Any
6
+
7
+
8
+ MONTHS = (
9
+ "January",
10
+ "February",
11
+ "March",
12
+ "April",
13
+ "May",
14
+ "June",
15
+ "July",
16
+ "August",
17
+ "September",
18
+ "October",
19
+ "November",
20
+ "December",
21
+ )
22
+
23
+ MONTH_BY_NAME = {name: index + 1 for index, name in enumerate(MONTHS)}
24
+
25
+
26
+ def unknown_date(source: str = "") -> dict[str, str]:
27
+ return {
28
+ "text": "date not listed",
29
+ "sort_date": "",
30
+ "precision": "unknown",
31
+ "source": source,
32
+ }
33
+
34
+
35
+ def date_info(text: str, sort_date: str, precision: str, source: str) -> dict[str, str]:
36
+ return {
37
+ "text": text,
38
+ "sort_date": sort_date,
39
+ "precision": precision,
40
+ "source": source,
41
+ }
42
+
43
+
44
+ def parse_iso_datetime(value: str, source: str = "server submission") -> dict[str, str] | None:
45
+ value = str(value or "").strip()
46
+ if not value:
47
+ return None
48
+ try:
49
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
50
+ except ValueError:
51
+ return None
52
+ if parsed.tzinfo is None:
53
+ parsed = parsed.replace(tzinfo=UTC)
54
+ parsed = parsed.astimezone(UTC)
55
+ return date_info(
56
+ parsed.strftime("%b %-d, %Y"),
57
+ parsed.date().isoformat(),
58
+ "second",
59
+ source,
60
+ )
61
+
62
+
63
+ def parse_friedman_date_text(value: str, source: str = "Erich Friedman Packing Center") -> dict[str, str] | None:
64
+ value = str(value or "").strip()
65
+ if not value:
66
+ return None
67
+
68
+ slash = re.fullmatch(r"(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})", value)
69
+ if slash:
70
+ month = int(slash.group(1))
71
+ day = int(slash.group(2))
72
+ year = int(slash.group(3))
73
+ if year < 100:
74
+ year += 2000 if year <= 69 else 1900
75
+ try:
76
+ parsed = date(year, month, day)
77
+ except ValueError:
78
+ return None
79
+ return date_info(parsed.strftime("%b %-d, %Y"), parsed.isoformat(), "day", source)
80
+
81
+ month_pattern = "|".join(MONTHS)
82
+ match = re.search(rf"\b((?:{month_pattern})\s+\d{{4}}|\d{{4}})\b", value)
83
+ if not match:
84
+ return None
85
+ text = match.group(1)
86
+ parts = text.split()
87
+ if len(parts) == 2:
88
+ month = MONTH_BY_NAME[parts[0]]
89
+ year = int(parts[1])
90
+ return date_info(text, f"{year:04d}-{month:02d}-01", "month", source)
91
+ year = int(parts[0])
92
+ return date_info(text, f"{year:04d}-01-01", "year", source)
93
+
94
+
95
+ def date_from_reference_text(reference_text: str, source: str = "Erich Friedman Packing Center") -> dict[str, str]:
96
+ reference_text = str(reference_text or "")
97
+ month_pattern = "|".join(MONTHS)
98
+ match = re.search(rf"\bin\s+((?:{month_pattern})\s+\d{{4}}|\d{{4}})\b", reference_text)
99
+ if match:
100
+ parsed = parse_friedman_date_text(match.group(1), source)
101
+ if parsed:
102
+ return parsed
103
+ return unknown_date(source)
104
+
105
+
106
+ def date_from_friedman_record(record: dict[str, Any], source: str = "Erich Friedman Packing Center") -> dict[str, str]:
107
+ meta = record.get("friedman_reference")
108
+ if isinstance(meta, dict):
109
+ parsed = parse_friedman_date_text(str(meta.get("date") or ""), source)
110
+ if parsed:
111
+ return parsed
112
+ reference_text = str(meta.get("reference_text") or "")
113
+ if reference_text:
114
+ return date_from_reference_text(reference_text, source)
115
+ parsed = parse_friedman_date_text(str(record.get("friedman_date") or record.get("date") or ""), source)
116
+ if parsed:
117
+ return parsed
118
+ return date_from_reference_text(str(record.get("reference_text") or record.get("friedman_reference_text") or ""), source)
119
+
120
+
121
+ def date_from_submission(record: dict[str, Any]) -> dict[str, str]:
122
+ return parse_iso_datetime(str(record.get("submitted_at") or ""), "server submission") or unknown_date("server submission")
123
+
124
+
125
+ def display_date_info(info: dict[str, Any] | None) -> str:
126
+ if not isinstance(info, dict):
127
+ return "date not listed"
128
+ return str(info.get("text") or "date not listed")
129
+
130
+
131
+ def is_recent_date_info(info: dict[str, Any] | None, *, now: date | None = None, days: int = 7) -> bool:
132
+ if not isinstance(info, dict):
133
+ return False
134
+ if info.get("precision") not in {"day", "second"}:
135
+ return False
136
+ sort_date = str(info.get("sort_date") or "")
137
+ if not sort_date:
138
+ return False
139
+ try:
140
+ parsed = date.fromisoformat(sort_date)
141
+ except ValueError:
142
+ return False
143
+ today = now or datetime.now(UTC).date()
144
+ return today - timedelta(days=days) <= parsed <= today
packing_benchmark/integrity.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import hashlib
5
+ import hmac
6
+ import json
7
+ import os
8
+ from datetime import UTC, datetime
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ INTEGRITY_VERSION = 1
14
+ INTEGRITY_KEY_ENV = "PACKING_INTEGRITY_SECRET"
15
+ INTEGRITY_FIELD = "integrity"
16
+ PRIVATE_PREFIX = "_"
17
+
18
+
19
+ def canonical_json(payload: Any) -> str:
20
+ return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
21
+
22
+
23
+ def sha256_text(text: str) -> str:
24
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
25
+
26
+
27
+ def sha256_file(path: Path) -> str:
28
+ digest = hashlib.sha256()
29
+ with path.open("rb") as handle:
30
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
31
+ digest.update(chunk)
32
+ return digest.hexdigest()
33
+
34
+
35
+ def integrity_secret() -> str:
36
+ return os.environ.get(INTEGRITY_KEY_ENV, "")
37
+
38
+
39
+ def record_payload(record: dict[str, Any]) -> dict[str, Any]:
40
+ payload = copy.deepcopy(record)
41
+ payload.pop(INTEGRITY_FIELD, None)
42
+ for key in list(payload):
43
+ if key.startswith(PRIVATE_PREFIX) or key == "sync_status":
44
+ payload.pop(key, None)
45
+ return payload
46
+
47
+
48
+ def record_sha256(record: dict[str, Any]) -> str:
49
+ return sha256_text(canonical_json(record_payload(record)))
50
+
51
+
52
+ def _abs_data_path(data_root: Path, path: str | Path) -> Path:
53
+ p = Path(path)
54
+ return p if p.is_absolute() else data_root.parent / p
55
+
56
+
57
+ def solution_sha256(record: dict[str, Any], data_root: Path) -> str:
58
+ path = record.get("solution_path")
59
+ if not path:
60
+ return ""
61
+ target = _abs_data_path(data_root, str(path))
62
+ if not target.exists():
63
+ return ""
64
+ return sha256_file(target)
65
+
66
+
67
+ def sign_record_digest(record_digest: str, solution_digest: str) -> str:
68
+ secret = integrity_secret()
69
+ if not secret:
70
+ return ""
71
+ message = f"{record_digest}:{solution_digest}".encode("utf-8")
72
+ return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
73
+
74
+
75
+ def seal_record(record: dict[str, Any], data_root: Path, *, signed_at: str | None = None) -> dict[str, Any]:
76
+ out = copy.deepcopy(record)
77
+ previous = out.get(INTEGRITY_FIELD)
78
+ record_digest = record_sha256(out)
79
+ solution_digest = solution_sha256(out, data_root)
80
+ signature = sign_record_digest(record_digest, solution_digest)
81
+ if not signature and isinstance(previous, dict) and previous.get("record_sha256") == record_digest:
82
+ signature = str(previous.get("signature") or "")
83
+ out[INTEGRITY_FIELD] = {
84
+ "version": INTEGRITY_VERSION,
85
+ "record_sha256": record_digest,
86
+ "solution_sha256": solution_digest,
87
+ "signature": signature,
88
+ "signed_at": signed_at or datetime.now(UTC).isoformat(timespec="seconds"),
89
+ }
90
+ return out
91
+
92
+
93
+ def verify_record_integrity(record: dict[str, Any], data_root: Path) -> list[str]:
94
+ integrity = record.get(INTEGRITY_FIELD)
95
+ if not isinstance(integrity, dict):
96
+ return ["missing integrity stamp"]
97
+
98
+ errors: list[str] = []
99
+ expected_record = record_sha256(record)
100
+ stored_record = str(integrity.get("record_sha256") or "")
101
+ if stored_record != expected_record:
102
+ errors.append("record hash mismatch")
103
+
104
+ expected_solution = solution_sha256(record, data_root)
105
+ stored_solution = str(integrity.get("solution_sha256") or "")
106
+ if expected_solution != stored_solution:
107
+ errors.append("solution file hash mismatch")
108
+
109
+ secret = integrity_secret()
110
+ stored_signature = str(integrity.get("signature") or "")
111
+ if secret:
112
+ expected_signature = sign_record_digest(stored_record, stored_solution)
113
+ if not stored_signature or not hmac.compare_digest(stored_signature, expected_signature):
114
+ errors.append("record signature mismatch")
115
+ return errors
116
+
117
+
118
+ def attach_integrity_status(record: dict[str, Any], data_root: Path) -> dict[str, Any]:
119
+ out = dict(record)
120
+ errors = verify_record_integrity(out, data_root)
121
+ out["_integrity_errors"] = errors
122
+ out["_integrity_ok"] = not errors
123
+ return out
packing_benchmark/store.py CHANGED
@@ -7,7 +7,9 @@ from datetime import UTC, datetime
7
  from pathlib import Path
8
  from typing import Any
9
 
 
10
  from .hub_sync import maybe_sync_dataset
 
11
  from .renderer import svg_markup
12
  from .verifier import (
13
  DEFAULT_TOLERANCE,
@@ -61,6 +63,14 @@ def is_trivial_record(record: dict[str, Any]) -> bool:
61
  return credit == "trivial" or "trivial" in text
62
 
63
 
 
 
 
 
 
 
 
 
64
  class SolutionStore:
65
  def __init__(self, root: Path):
66
  self.root = root
@@ -85,7 +95,7 @@ class SolutionStore:
85
  with self.records_path.open() as f:
86
  for line in f:
87
  if line.strip():
88
- records.append(json.loads(line))
89
  return records
90
 
91
  def load_reference_records(self) -> list[dict[str, Any]]:
@@ -95,7 +105,7 @@ class SolutionStore:
95
  with self.references_path.open() as f:
96
  for line in f:
97
  if line.strip():
98
- records.append(json.loads(line))
99
  return records
100
 
101
  def normalized_public_record(self, record: dict[str, Any]) -> dict[str, Any]:
@@ -113,11 +123,15 @@ class SolutionStore:
113
  return out
114
 
115
  def public_records(self, setup: str | None = None, show_all: bool = False) -> list[dict[str, Any]]:
116
- references = [self.normalized_public_record(r) for r in self.load_reference_records()]
 
 
 
 
117
  verified = [
118
  self.normalized_public_record(r)
119
  for r in self.load_records()
120
- if r.get("verified")
121
  ]
122
  if setup and setup != "All":
123
  references = [r for r in references if r.get("setup") == setup]
@@ -170,9 +184,11 @@ class SolutionStore:
170
 
171
  def write_records(self, records: list[dict[str, Any]]) -> None:
172
  tmp = self.records_path.with_suffix(".tmp")
 
173
  with tmp.open("w") as f:
174
  for record in records:
175
- f.write(json.dumps(record, sort_keys=True) + "\n")
 
176
  tmp.replace(self.records_path)
177
 
178
  def solution_for_record(self, record: dict[str, Any]) -> dict[str, Any]:
@@ -215,6 +231,75 @@ class SolutionStore:
215
  setups = sorted(setups)
216
  return ["All", *setups]
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  def append_verified_submission(
219
  self,
220
  solution: dict[str, Any],
@@ -258,23 +343,34 @@ class SolutionStore:
258
  reference_side = comparison.get("friedman_displayed_side")
259
  if reference_side is None:
260
  reference_side = comparison.get("reference_side")
261
- reference_side = float(reference_side) if reference_side not in (None, "") else None
262
  side = float(result.side or 0.0)
 
 
 
 
 
 
 
263
  record = {
264
  "id": rid,
265
  "case": result.case,
266
  "setup": result.setup,
267
  "n": result.n,
268
  "side": side,
 
 
269
  "density": result.density,
270
  "submitted_by": submitter,
271
  "submitted_at": now,
 
 
272
  "verified": True,
273
  "max_boundary_excess": result.max_boundary_excess,
274
  "max_pair_overlap_depth": result.max_pair_overlap_depth,
275
  "tolerance": tolerance,
276
  "reference_side": reference_side,
277
- "improvement": (reference_side - side) if reference_side is not None else None,
 
278
  "source_url": source_url.strip(),
279
  "notes": notes.strip(),
280
  "solution_path": str(solution_path.relative_to(self.root.parent)),
 
7
  from pathlib import Path
8
  from typing import Any
9
 
10
+ from .dates import date_from_friedman_record, date_from_submission
11
  from .hub_sync import maybe_sync_dataset
12
+ from .integrity import attach_integrity_status, seal_record
13
  from .renderer import svg_markup
14
  from .verifier import (
15
  DEFAULT_TOLERANCE,
 
63
  return credit == "trivial" or "trivial" in text
64
 
65
 
66
+ def clean_record_for_storage(record: dict[str, Any]) -> dict[str, Any]:
67
+ return {
68
+ key: value
69
+ for key, value in record.items()
70
+ if not key.startswith("_") and key != "sync_status"
71
+ }
72
+
73
+
74
  class SolutionStore:
75
  def __init__(self, root: Path):
76
  self.root = root
 
95
  with self.records_path.open() as f:
96
  for line in f:
97
  if line.strip():
98
+ records.append(attach_integrity_status(json.loads(line), self.root))
99
  return records
100
 
101
  def load_reference_records(self) -> list[dict[str, Any]]:
 
105
  with self.references_path.open() as f:
106
  for line in f:
107
  if line.strip():
108
+ records.append(attach_integrity_status(json.loads(line), self.root))
109
  return records
110
 
111
  def normalized_public_record(self, record: dict[str, Any]) -> dict[str, Any]:
 
123
  return out
124
 
125
  def public_records(self, setup: str | None = None, show_all: bool = False) -> list[dict[str, Any]]:
126
+ references = [
127
+ self.normalized_public_record(r)
128
+ for r in self.load_reference_records()
129
+ if not r.get("_integrity_errors")
130
+ ]
131
  verified = [
132
  self.normalized_public_record(r)
133
  for r in self.load_records()
134
+ if r.get("verified") and not r.get("_integrity_errors")
135
  ]
136
  if setup and setup != "All":
137
  references = [r for r in references if r.get("setup") == setup]
 
184
 
185
  def write_records(self, records: list[dict[str, Any]]) -> None:
186
  tmp = self.records_path.with_suffix(".tmp")
187
+ signed_at = datetime.now(UTC).isoformat(timespec="seconds")
188
  with tmp.open("w") as f:
189
  for record in records:
190
+ clean = clean_record_for_storage(record)
191
+ f.write(json.dumps(seal_record(clean, self.root, signed_at=signed_at), sort_keys=True) + "\n")
192
  tmp.replace(self.records_path)
193
 
194
  def solution_for_record(self, record: dict[str, Any]) -> dict[str, Any]:
 
231
  setups = sorted(setups)
232
  return ["All", *setups]
233
 
234
+ def reference_record_for_case(self, case: str, metric_symbol: str | None = None) -> dict[str, Any] | None:
235
+ candidates = [
236
+ self.normalized_public_record(record)
237
+ for record in self.load_reference_records()
238
+ if not record.get("_integrity_errors") and str(record.get("case")) == case
239
+ ]
240
+ if metric_symbol:
241
+ symbol_candidates = [record for record in candidates if str(record.get("metric_symbol") or "s") == metric_symbol]
242
+ if symbol_candidates:
243
+ candidates = symbol_candidates
244
+ if not candidates:
245
+ return None
246
+ return min(candidates, key=metric_float)
247
+
248
+ def submission_metric_symbol(self, case: str) -> str:
249
+ reference = self.reference_record_for_case(case)
250
+ if reference is not None:
251
+ return str(reference.get("metric_symbol") or "s")
252
+ return "s"
253
+
254
+ @staticmethod
255
+ def metric_value_for_side(side: float, metric_symbol: str) -> float:
256
+ return side * 0.5 if metric_symbol == "r" else side
257
+
258
+ def previous_best_entry(self, record: dict[str, Any]) -> dict[str, Any]:
259
+ date_info = record.get("record_date")
260
+ if not isinstance(date_info, dict):
261
+ if record.get("record_type") == "reference":
262
+ date_info = date_from_friedman_record(record)
263
+ else:
264
+ date_info = date_from_submission(record)
265
+ source_url = str(record.get("source_url") or "")
266
+ meta = record.get("friedman_reference")
267
+ if isinstance(meta, dict):
268
+ source_url = str(meta.get("source_page") or source_url)
269
+ return {
270
+ "case": record.get("case"),
271
+ "record_type": record.get("record_type") or ("verified" if record.get("verified") else "reference"),
272
+ "author": record.get("submitted_by") or "unknown",
273
+ "date": date_info,
274
+ "metric_symbol": record.get("metric_symbol") or "s",
275
+ "metric_value": metric_float(record),
276
+ "metric_expression": record.get("metric_expression") or "",
277
+ "source": record.get("source") or ("Verified submission" if record.get("verified") else "Erich Friedman Packing Center"),
278
+ "source_url": source_url,
279
+ "reference_text": record.get("reference_text") or "",
280
+ }
281
+
282
+ def current_best_for_case(self, case: str, metric_symbol: str) -> dict[str, Any] | None:
283
+ candidates: list[dict[str, Any]] = []
284
+ candidates.extend(
285
+ self.normalized_public_record(record)
286
+ for record in self.load_reference_records()
287
+ if not record.get("_integrity_errors")
288
+ and str(record.get("case")) == case
289
+ and str(record.get("metric_symbol") or "s") == metric_symbol
290
+ )
291
+ candidates.extend(
292
+ self.normalized_public_record(record)
293
+ for record in self.load_records()
294
+ if record.get("verified")
295
+ and not record.get("_integrity_errors")
296
+ and str(record.get("case")) == case
297
+ and str(record.get("metric_symbol") or "s") == metric_symbol
298
+ )
299
+ if not candidates:
300
+ return None
301
+ return min(candidates, key=metric_float)
302
+
303
  def append_verified_submission(
304
  self,
305
  solution: dict[str, Any],
 
343
  reference_side = comparison.get("friedman_displayed_side")
344
  if reference_side is None:
345
  reference_side = comparison.get("reference_side")
 
346
  side = float(result.side or 0.0)
347
+ metric_symbol = self.submission_metric_symbol(result.case)
348
+ metric_value = self.metric_value_for_side(side, metric_symbol)
349
+ previous = self.current_best_for_case(result.case, metric_symbol)
350
+ previous_bests = [self.previous_best_entry(previous)] if previous is not None else []
351
+ if reference_side is None and previous is not None:
352
+ reference_side = metric_float(previous)
353
+ reference_side = float(reference_side) if reference_side not in (None, "") else None
354
  record = {
355
  "id": rid,
356
  "case": result.case,
357
  "setup": result.setup,
358
  "n": result.n,
359
  "side": side,
360
+ "metric_symbol": metric_symbol,
361
+ "metric_value": metric_value,
362
  "density": result.density,
363
  "submitted_by": submitter,
364
  "submitted_at": now,
365
+ "database_inserted_at": now,
366
+ "record_date": date_from_submission({"submitted_at": now}),
367
  "verified": True,
368
  "max_boundary_excess": result.max_boundary_excess,
369
  "max_pair_overlap_depth": result.max_pair_overlap_depth,
370
  "tolerance": tolerance,
371
  "reference_side": reference_side,
372
+ "improvement": (reference_side - metric_value) if reference_side is not None else None,
373
+ "previous_bests": previous_bests,
374
  "source_url": source_url.strip(),
375
  "notes": notes.strip(),
376
  "solution_path": str(solution_path.relative_to(self.root.parent)),