#!/usr/bin/env python3 '''Build an interactive HTML review page for PlantGeneBench-100 questions.''' from __future__ import annotations import argparse import json from collections import Counter from datetime import datetime from html import escape from pathlib import Path from typing import Any def load_jsonl(path: Path) -> list[dict[str, Any]]: rows = [] with path.open('r', encoding='utf-8') as handle: for lineno, line in enumerate(handle, start=1): line = line.strip() if not line: continue try: rows.append(json.loads(line)) except json.JSONDecodeError as exc: raise SystemExit(f'{path}:{lineno}: invalid JSON: {exc}') from exc return rows def pretty(value: Any) -> str: if value in (None, '', [], {}): return '' return json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) def inline_list(value: Any) -> str: if not value: return '' if isinstance(value, list): return ', '.join(str(v) for v in value) return str(value) def item_html(task: dict[str, Any], gold_row: dict[str, Any], index: int) -> str: gold = gold_row.get('gold') or {} task_id = task.get('task_id', '') layer = task.get('layer') or task.get('category') or 'unknown' difficulty = task.get('difficulty', '') stages = gold.get('target_stages') or gold.get('expected_stages') or [] plan = gold.get('execution_plan') or gold.get('expected_execution_plan') or [] entities = gold.get('entities') or {} params = gold.get('parameters') or gold.get('required_params') or {} artifacts = gold.get('artifacts') or {} must_not = gold.get('must_not_do') or [] must_include = gold.get('must_include') or [] can_execute = gold.get('can_execute') support_status = gold.get('support_status') boundary_action = gold.get('boundary_action') scoring_profile = gold_row.get('scoring_profile') or task.get('scoring_profile') or [] full_task = pretty(task) full_gold = pretty(gold) must_not_html = ''.join(f'
  • {escape(str(x))}
  • ' for x in must_not) or '
  • 无
  • ' must_include_html = ''.join(f'
  • {escape(str(x))}
  • ' for x in must_include) or '
  • 无
  • ' return f'''

    {index:03d} {escape(task_id)}

    {escape(layer)}{escape(difficulty)}scoring: {escape(inline_list(scoring_profile))}

    #

    题目正文

    {escape(task.get('user_query', ''))}

    Gold 摘要

    Intent
    {escape(str(gold.get('intent') or ''))}
    Analysis type
    {escape(str(gold.get('analysis_type') or ''))}
    Entities
    {escape(pretty(entities))}
    Target stages
    {escape(inline_list(stages))}
    Execution plan
    {escape(inline_list(plan))}
    Parameters
    {escape(pretty(params))}
    Artifacts
    {escape(pretty(artifacts))}
    Can execute
    {escape(str(can_execute))}
    Support status
    {escape(str(support_status))}
    Boundary action
    {escape(str(boundary_action))}

    修订意见

    Must-not-do

      {must_not_html}

    Must-include

      {must_include_html}
    查看 rationale / input_context / 完整 JSON

    Rationale

    {escape(str(gold.get('rationale') or ''))}

    Input context

    {escape(pretty(task.get('input_context') or {}))}

    Full task JSON

    {escape(full_task)}

    Full gold JSON

    {escape(full_gold)}
    ''' def build_html(tasks: list[dict[str, Any]], gold_rows: list[dict[str, Any]], task_path: Path, gold_path: Path) -> str: gold_by_id = {row['task_id']: row for row in gold_rows} layers = Counter(task.get('layer') or task.get('category') or 'unknown' for task in tasks) difficulties = Counter(task.get('difficulty', 'unknown') for task in tasks) cards = [] for index, task in enumerate(tasks, start=1): task_id = task.get('task_id') gold_row = gold_by_id.get(task_id) or {'task_id': task_id, 'gold': {}, 'layer': task.get('layer')} cards.append(item_html(task, gold_row, index)) data_json = json.dumps([task.get('task_id') for task in tasks], ensure_ascii=False) generated = datetime.now().strftime('%Y-%m-%d %H:%M:%S') layer_options = ''.join(f'' for k, v in sorted(layers.items())) diff_options = ''.join(f'' for k, v in sorted(difficulties.items())) layer_summary = escape(', '.join(f'{k}={v}' for k, v in sorted(layers.items()))) diff_summary = escape(', '.join(f'{k}={v}' for k, v in sorted(difficulties.items()))) cards_html = ''.join(cards) return f''' PlantGeneBench-100 题库修订审阅

    PlantGeneBench-100 题库修订审阅

    Generated: {escape(generated)} | tasks={escape(str(task_path))} | gold={escape(str(gold_path))}
    题目数
    {len(tasks)}
    Layer
    {layer_summary}
    难度
    {diff_summary}
    提示
    输入会自动保存在浏览器 localStorage;正式提交请导出 JSON 或 TSV。
    {cards_html}
    ''' def run() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--tasks', type=Path, default=Path('benchmarks/plantgenebench/tasks/plantgenebench_100_v1.full.jsonl')) parser.add_argument('--gold', type=Path, default=Path('benchmarks/plantgenebench/gold/plantgenebench_100_v1.full.gold.jsonl')) parser.add_argument('--out', type=Path, default=Path('benchmarks/plantgenebench/plantgenebench_100_v1_review.html')) args = parser.parse_args() tasks = load_jsonl(args.tasks) gold_rows = load_jsonl(args.gold) html = build_html(tasks, gold_rows, args.tasks, args.gold) args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(html, encoding='utf-8') print(f'Wrote {len(tasks)} review items to {args.out}') return 0 if __name__ == '__main__': raise SystemExit(run())