zhiyi commited on
Commit
eef685d
·
1 Parent(s): 73fc26e

Align dynamic comparison tables

Browse files
Files changed (3) hide show
  1. app.py +40 -13
  2. static/dynamic-content.css +14 -0
  3. tests/test_app.py +17 -1
app.py CHANGED
@@ -715,6 +715,42 @@ def render_value(value: Any, *, depth: int = 0) -> str:
715
  return f"<p>{esc(value)}</p>"
716
 
717
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
  def render_section_content(section: dict[str, Any]) -> str:
719
  content, layout = section.get("content", {}), section.get("layout", "text")
720
  if layout == "timeline":
@@ -725,22 +761,13 @@ def render_section_content(section: dict[str, Any]) -> str:
725
  for n in content.get("nodes", [])
726
  ) + "</div>"
727
  if layout == "comparison":
728
- columns, rows = content.get("columns", []), content.get("rows", [])
729
  head = "".join(f"<th>{esc(x)}</th>" for x in columns)
730
- def cells(row: dict[str, Any]) -> list[Any]:
731
- values = row.get("values")
732
- if isinstance(values, dict):
733
- return [values.get(column, "") for column in columns]
734
- if isinstance(values, list):
735
- return values
736
- reserved = {"name", "method", "title"}
737
- return [row.get(column, "") for column in columns] or [v for k, v in row.items() if k not in reserved]
738
  body = "".join(
739
- f'<tr><th>{esc(row.get("name") or row.get("method") or row.get("title"))}</th>'
740
- f'{"".join(f"<td>{render_value(v)}</td>" for v in cells(row))}</tr>'
741
- for row in rows
742
  )
743
- return f'<div class="table-wrap"><table><thead><tr><th>方法</th>{head}</tr></thead><tbody>{body}</tbody></table></div>'
744
  if layout == "process":
745
  return '<div class="process">' + "".join(
746
  f'<article class="process-step"><span>{i}</span>{render_value(step)}</article>'
 
715
  return f"<p>{esc(value)}</p>"
716
 
717
 
718
+ def normalize_comparison(content: dict[str, Any]) -> tuple[str, list[str], list[tuple[str, list[Any]]]]:
719
+ columns = [str(column) for column in content.get("columns", [])]
720
+ raw_rows = content.get("rows", [])
721
+ row_keys = ("name", "dimension", "criterion", "metric", "title", "method")
722
+ row_label = "对比维度"
723
+
724
+ max_values = max(
725
+ (len(row.get("values", [])) for row in raw_rows if isinstance(row, dict) and isinstance(row.get("values"), list)),
726
+ default=0,
727
+ )
728
+ first_is_label = bool(columns) and (
729
+ any(word in columns[0].lower() for word in ("维度", "指标", "dimension", "criterion", "metric"))
730
+ or (max_values and len(columns) == max_values + 1)
731
+ )
732
+ if first_is_label:
733
+ row_label, columns = columns[0], columns[1:]
734
+
735
+ normalized_rows: list[tuple[str, list[Any]]] = []
736
+ for row in raw_rows:
737
+ if not isinstance(row, dict):
738
+ continue
739
+ label = next((str(row[key]) for key in row_keys if row.get(key) not in (None, "")), "")
740
+ values = row.get("values")
741
+ if isinstance(values, dict):
742
+ cells = [values.get(column, "") for column in columns]
743
+ elif isinstance(values, list):
744
+ cells = list(values)
745
+ if len(cells) == len(columns) + 1 and str(cells[0]) == label:
746
+ cells = cells[1:]
747
+ else:
748
+ cells = [row.get(column, "") for column in columns]
749
+ cells = (cells + [""] * len(columns))[:len(columns)]
750
+ normalized_rows.append((label, cells))
751
+ return row_label, columns, normalized_rows
752
+
753
+
754
  def render_section_content(section: dict[str, Any]) -> str:
755
  content, layout = section.get("content", {}), section.get("layout", "text")
756
  if layout == "timeline":
 
761
  for n in content.get("nodes", [])
762
  ) + "</div>"
763
  if layout == "comparison":
764
+ row_label, columns, rows = normalize_comparison(content)
765
  head = "".join(f"<th>{esc(x)}</th>" for x in columns)
 
 
 
 
 
 
 
 
766
  body = "".join(
767
+ f'<tr><th scope="row">{esc(label)}</th>{"".join(f"<td>{render_value(value)}</td>" for value in values)}</tr>'
768
+ for label, values in rows
 
769
  )
770
+ return f'<div class="table-wrap"><table class="comparison-table"><thead><tr><th>{esc(row_label)}</th>{head}</tr></thead><tbody>{body}</tbody></table></div>'
771
  if layout == "process":
772
  return '<div class="process">' + "".join(
773
  f'<article class="process-step"><span>{i}</span>{render_value(step)}</article>'
static/dynamic-content.css CHANGED
@@ -59,3 +59,17 @@
59
  border-radius: 8px;
60
  background: #231c12;
61
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  border-radius: 8px;
60
  background: #231c12;
61
  }
62
+ .comparison-table {
63
+ min-width: 760px;
64
+ table-layout: fixed;
65
+ }
66
+ .comparison-table th:first-child {
67
+ width: 18%;
68
+ }
69
+ .comparison-table td,
70
+ .comparison-table th {
71
+ overflow-wrap: anywhere;
72
+ }
73
+ .comparison-table td > p {
74
+ margin: 0;
75
+ }
tests/test_app.py CHANGED
@@ -2,7 +2,7 @@ import unittest
2
 
3
  from pydantic import ValidationError
4
 
5
- from app import ResearchReport, deduplicate, prepare_evidence_inputs, render_presentation, render_section_content, render_value, safe_slug
6
 
7
 
8
  def base_report(section):
@@ -79,6 +79,22 @@ class AppTests(unittest.TestCase):
79
  self.assertIn("Memory", output)
80
  self.assertNotIn("{'", output)
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  if __name__ == "__main__":
84
  unittest.main()
 
2
 
3
  from pydantic import ValidationError
4
 
5
+ from app import ResearchReport, deduplicate, normalize_comparison, prepare_evidence_inputs, render_presentation, render_section_content, render_value, safe_slug
6
 
7
 
8
  def base_report(section):
 
79
  self.assertIn("Memory", output)
80
  self.assertNotIn("{'", output)
81
 
82
+ def test_comparison_does_not_duplicate_dimension_column(self):
83
+ content = {
84
+ "columns": ["方法维度", "Debevec & Malik (1997) - HDR重建", "Mertens et al. (2007) - 多曝光融合"],
85
+ "rows": [
86
+ {"name": "核心目标", "values": ["恢复场景物理辐射度图", "直接生成视觉质量高的显示图像"]},
87
+ {"dimension": "是否需要CRF标定", "values": ["是", "否"]},
88
+ ],
89
+ }
90
+ row_label, columns, rows = normalize_comparison(content)
91
+ self.assertEqual(row_label, "方法维度")
92
+ self.assertEqual(len(columns), 2)
93
+ self.assertTrue(all(len(values) == 2 for _, values in rows))
94
+ output = render_section_content({"layout": "comparison", "content": content})
95
+ self.assertEqual(output.count("<th>方法维度</th>"), 1)
96
+ self.assertIn("恢复场景物理辐射度图", output)
97
+
98
 
99
  if __name__ == "__main__":
100
  unittest.main()