JacobLinCool Codex commited on
Commit
ded41ce
·
verified ·
1 Parent(s): dd8c015

fix: label exported goals for users

Browse files

Co-authored-by: Codex <noreply@openai.com>

README.md CHANGED
@@ -66,18 +66,18 @@ is intentionally kept out of the main user workflow.
66
  ## Field Notes Artifact
67
 
68
  The `field_notes` Gradio API endpoint and `Notes` button export a Markdown build note from the exact session state:
69
- builder profile, target badges, idea board, cited Spaces, latest build plan, planner calls, and the share caption. This
70
- keeps the Field Notes badge path tied to auditable app evidence instead of a separate hand-written summary.
71
 
72
  ## Chapter Artifact
73
 
74
  The `chapter` Gradio API endpoint and `Chapter` button export the public-facing idea board as an Almanac chapter:
75
- one fate page per idea, each with verdict, score, targets, and closest cited pages. It is the shareable companion to
76
- the private Field Notes artifact.
77
 
78
  ## Idea Board Ranking
79
 
80
- The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
81
  strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
82
  Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
83
  Users can also click any Idea Board page to make it current before pressing `Plan`.
@@ -121,7 +121,7 @@ as the app instead of a separate hand-curated checklist.
121
  ## Demo Rehearsal
122
 
123
  `/api/demo-session` and the `Example` button load a deterministic two-turn sample: a complete project idea, profile,
124
- target badges, score seal, build plan, trace, and wood map. It is built by running the same advisor engine as a normal
125
  user session, so the visible app stays focused on the builder's idea while API exports remain available for submission
126
  evidence.
127
 
@@ -151,7 +151,7 @@ watchdog updates the page text so the demo never sits in a silent blank state du
151
 
152
  ## Session Persistence
153
 
154
- The frontend stores the current advisor session in browser `localStorage`: profile notes, selected targets, idea board,
155
  trace, latest build plan, and last share artifact. Refreshing the Space restores the same cockpit state; the `Reset`
156
  button clears the saved session and returns to the current snapshot defaults.
157
 
 
66
  ## Field Notes Artifact
67
 
68
  The `field_notes` Gradio API endpoint and `Notes` button export a Markdown build note from the exact session state:
69
+ builder profile, selected goals, idea board, cited Spaces, latest build plan, planner calls, and the share caption. This
70
+ keeps the note tied to auditable app evidence instead of a separate hand-written summary.
71
 
72
  ## Chapter Artifact
73
 
74
  The `chapter` Gradio API endpoint and `Chapter` button export the public-facing idea board as an Almanac chapter:
75
+ one idea page per saved direction, each with verdict, score, selected goals, and closest cited pages. It is the
76
+ shareable companion to the working notes artifact.
77
 
78
  ## Idea Board Ranking
79
 
80
+ The `Rank` command rescans the saved idea board, recalculates each seal against the selected goals, selects the
81
  strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
82
  Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
83
  Users can also click any Idea Board page to make it current before pressing `Plan`.
 
121
  ## Demo Rehearsal
122
 
123
  `/api/demo-session` and the `Example` button load a deterministic two-turn sample: a complete project idea, profile,
124
+ selected goals, score seal, build plan, trace, and wood map. It is built by running the same advisor engine as a normal
125
  user session, so the visible app stays focused on the builder's idea while API exports remain available for submission
126
  evidence.
127
 
 
151
 
152
  ## Session Persistence
153
 
154
+ The frontend stores the current advisor session in browser `localStorage`: profile notes, selected goals, idea board,
155
  trace, latest build plan, and last share artifact. Refreshing the Space restores the same cockpit state; the `Reset`
156
  button clears the saved session and returns to the current snapshot defaults.
157
 
hackathon_advisor/chapter.py CHANGED
@@ -3,22 +3,24 @@ from __future__ import annotations
3
  from datetime import datetime, timezone
4
  from typing import Any
5
 
 
 
6
 
7
  def build_chapter_markdown(session: dict[str, Any], metadata: dict[str, Any]) -> str:
8
  ideas = _list_of_dicts(session.get("ideas"))
9
- targets = [str(target) for target in session.get("targets") or []]
10
  artifact = session.get("last_artifact") if isinstance(session.get("last_artifact"), dict) else {}
11
  lines = [
12
  "# The Unwritten Almanac Chapter",
13
  "",
14
  f"Generated: {datetime.now(timezone.utc).isoformat(timespec='seconds')}",
15
  f"Snapshot: {_clean(metadata.get('snapshot_generated_at'))} · {_clean(metadata.get('project_count'))} pages",
16
- f"Targets: {', '.join(targets) if targets else 'No specific targets'}",
17
  "",
18
  ]
19
 
20
  if not ideas:
21
- lines.extend(["No fate pages have been written yet.", ""])
22
  return "\n".join(lines)
23
 
24
  for index, idea in enumerate(ideas, start=1):
@@ -33,7 +35,7 @@ def build_chapter_markdown(session: dict[str, Any], metadata: dict[str, Any]) ->
33
  def _idea_page(index: int, idea: dict[str, Any]) -> list[str]:
34
  title = _clean(idea.get("title") or f"Page {index}")
35
  pitch = _clean(idea.get("pitch"))
36
- targets = [str(target) for target in idea.get("targets") or []]
37
  score = idea.get("score") if isinstance(idea.get("score"), dict) else {}
38
  verdict = _clean(score.get("verdict")) if score else "DRAFT"
39
  overall = _clean(score.get("overall")) if score else "0.0"
@@ -41,14 +43,14 @@ def _idea_page(index: int, idea: dict[str, Any]) -> list[str]:
41
  f"## Page {index}: {title}",
42
  "",
43
  f"Verdict: {verdict} · {overall}/10",
44
- f"Targets: {', '.join(targets) if targets else 'No specific targets'}",
45
  "",
46
- pitch or "No prophecy text recorded.",
47
  "",
48
  ]
49
  echoes = _list_of_dicts(score.get("echoes")) if score else []
50
  if echoes:
51
- lines.extend(["Closest inked pages:", ""])
52
  for echo in echoes[:3]:
53
  project = echo.get("project") if isinstance(echo.get("project"), dict) else {}
54
  page = _clean(echo.get("page_number")) or "?"
@@ -69,6 +71,12 @@ def _list_of_dicts(value: Any) -> list[dict[str, Any]]:
69
  return [item for item in value if isinstance(item, dict)]
70
 
71
 
 
 
 
 
 
 
72
  def _clean(value: Any) -> str:
73
  if value is None:
74
  return ""
 
3
  from datetime import datetime, timezone
4
  from typing import Any
5
 
6
+ from hackathon_advisor.tools import target_label
7
+
8
 
9
  def build_chapter_markdown(session: dict[str, Any], metadata: dict[str, Any]) -> str:
10
  ideas = _list_of_dicts(session.get("ideas"))
11
+ goals = _goal_labels(session.get("targets"))
12
  artifact = session.get("last_artifact") if isinstance(session.get("last_artifact"), dict) else {}
13
  lines = [
14
  "# The Unwritten Almanac Chapter",
15
  "",
16
  f"Generated: {datetime.now(timezone.utc).isoformat(timespec='seconds')}",
17
  f"Snapshot: {_clean(metadata.get('snapshot_generated_at'))} · {_clean(metadata.get('project_count'))} pages",
18
+ f"Goals: {', '.join(goals) if goals else 'No specific goals'}",
19
  "",
20
  ]
21
 
22
  if not ideas:
23
+ lines.extend(["No idea pages have been written yet.", ""])
24
  return "\n".join(lines)
25
 
26
  for index, idea in enumerate(ideas, start=1):
 
35
  def _idea_page(index: int, idea: dict[str, Any]) -> list[str]:
36
  title = _clean(idea.get("title") or f"Page {index}")
37
  pitch = _clean(idea.get("pitch"))
38
+ goals = _goal_labels(idea.get("targets"))
39
  score = idea.get("score") if isinstance(idea.get("score"), dict) else {}
40
  verdict = _clean(score.get("verdict")) if score else "DRAFT"
41
  overall = _clean(score.get("overall")) if score else "0.0"
 
43
  f"## Page {index}: {title}",
44
  "",
45
  f"Verdict: {verdict} · {overall}/10",
46
+ f"Goals: {', '.join(goals) if goals else 'No specific goals'}",
47
  "",
48
+ pitch or "No pitch recorded.",
49
  "",
50
  ]
51
  echoes = _list_of_dicts(score.get("echoes")) if score else []
52
  if echoes:
53
+ lines.extend(["Closest cited pages:", ""])
54
  for echo in echoes[:3]:
55
  project = echo.get("project") if isinstance(echo.get("project"), dict) else {}
56
  page = _clean(echo.get("page_number")) or "?"
 
71
  return [item for item in value if isinstance(item, dict)]
72
 
73
 
74
+ def _goal_labels(value: Any) -> list[str]:
75
+ if not isinstance(value, list):
76
+ return []
77
+ return [target_label(str(target)) for target in value]
78
+
79
+
80
  def _clean(value: Any) -> str:
81
  if value is None:
82
  return ""
hackathon_advisor/field_notes.py CHANGED
@@ -3,12 +3,14 @@ from __future__ import annotations
3
  from datetime import datetime, timezone
4
  from typing import Any
5
 
 
 
6
 
7
  def build_field_notes_markdown(session: dict[str, Any], metadata: dict[str, Any]) -> str:
8
  ideas = _list_of_dicts(session.get("ideas"))
9
  trace = _list_of_dicts(session.get("trace"))
10
  profile = session.get("profile") if isinstance(session.get("profile"), dict) else {}
11
- targets = [str(target) for target in session.get("targets") or []]
12
  last_plan = [str(step) for step in session.get("last_plan") or []]
13
  last_artifact = session.get("last_artifact") if isinstance(session.get("last_artifact"), dict) else {}
14
 
@@ -35,7 +37,7 @@ def build_field_notes_markdown(session: dict[str, Any], metadata: dict[str, Any]
35
  lines.append(f"- {field.title()}: {value}")
36
  else:
37
  lines.append("- No profile notes recorded.")
38
- lines.append(f"- Targets: {', '.join(targets) if targets else 'No specific targets'}")
39
 
40
  lines.extend(["", "## Idea Board", ""])
41
  if ideas:
@@ -49,7 +51,7 @@ def build_field_notes_markdown(session: dict[str, Any], metadata: dict[str, Any]
49
  for index, step in enumerate(last_plan, start=1):
50
  lines.append(f"{index}. {_clean(step)}")
51
  else:
52
- lines.append("No build plan was pressed yet.")
53
 
54
  lines.extend(["", "## Turn Trace", ""])
55
  if trace:
@@ -89,13 +91,13 @@ def build_field_notes_markdown(session: dict[str, Any], metadata: dict[str, Any]
89
  def _idea_section(index: int, idea: dict[str, Any]) -> list[str]:
90
  title = _clean(idea.get("title") or f"Idea {index}")
91
  pitch = _clean(idea.get("pitch"))
92
- targets = [str(target) for target in idea.get("targets") or []]
93
  score = idea.get("score") if isinstance(idea.get("score"), dict) else {}
94
  lines = [
95
  f"### {index}. {title}",
96
  "",
97
  f"- Pitch: {pitch or 'No pitch recorded.'}",
98
- f"- Targets: {', '.join(targets) if targets else 'No specific targets'}",
99
  ]
100
  if score:
101
  lines.append(f"- Seal: {_clean(score.get('overall'))}/10 - {_clean(score.get('verdict'))}")
@@ -150,6 +152,12 @@ def _list_of_dicts(value: Any) -> list[dict[str, Any]]:
150
  return [item for item in value if isinstance(item, dict)]
151
 
152
 
 
 
 
 
 
 
153
  def _clean(value: Any) -> str:
154
  if value is None:
155
  return ""
 
3
  from datetime import datetime, timezone
4
  from typing import Any
5
 
6
+ from hackathon_advisor.tools import target_label
7
+
8
 
9
  def build_field_notes_markdown(session: dict[str, Any], metadata: dict[str, Any]) -> str:
10
  ideas = _list_of_dicts(session.get("ideas"))
11
  trace = _list_of_dicts(session.get("trace"))
12
  profile = session.get("profile") if isinstance(session.get("profile"), dict) else {}
13
+ goals = _goal_labels(session.get("targets"))
14
  last_plan = [str(step) for step in session.get("last_plan") or []]
15
  last_artifact = session.get("last_artifact") if isinstance(session.get("last_artifact"), dict) else {}
16
 
 
37
  lines.append(f"- {field.title()}: {value}")
38
  else:
39
  lines.append("- No profile notes recorded.")
40
+ lines.append(f"- Goals: {', '.join(goals) if goals else 'No specific goals'}")
41
 
42
  lines.extend(["", "## Idea Board", ""])
43
  if ideas:
 
51
  for index, step in enumerate(last_plan, start=1):
52
  lines.append(f"{index}. {_clean(step)}")
53
  else:
54
+ lines.append("No build plan has been generated yet.")
55
 
56
  lines.extend(["", "## Turn Trace", ""])
57
  if trace:
 
91
  def _idea_section(index: int, idea: dict[str, Any]) -> list[str]:
92
  title = _clean(idea.get("title") or f"Idea {index}")
93
  pitch = _clean(idea.get("pitch"))
94
+ goals = _goal_labels(idea.get("targets"))
95
  score = idea.get("score") if isinstance(idea.get("score"), dict) else {}
96
  lines = [
97
  f"### {index}. {title}",
98
  "",
99
  f"- Pitch: {pitch or 'No pitch recorded.'}",
100
+ f"- Goals: {', '.join(goals) if goals else 'No specific goals'}",
101
  ]
102
  if score:
103
  lines.append(f"- Seal: {_clean(score.get('overall'))}/10 - {_clean(score.get('verdict'))}")
 
152
  return [item for item in value if isinstance(item, dict)]
153
 
154
 
155
+ def _goal_labels(value: Any) -> list[str]:
156
+ if not isinstance(value, list):
157
+ return []
158
+ return [target_label(str(target)) for target in value]
159
+
160
+
161
  def _clean(value: Any) -> str:
162
  if value is None:
163
  return ""
tests/test_app.py CHANGED
@@ -69,7 +69,8 @@ def test_field_notes_endpoint_exports_markdown() -> None:
69
 
70
  assert payload.startswith("# Hackathon Advisor Field Notes")
71
  assert "Skills: frontend" in payload
72
- assert "Targets: Field Notes" in payload
 
73
  assert "## Turn Trace" in payload
74
  assert "Record the trace and write build notes" in payload
75
 
@@ -83,7 +84,9 @@ def test_chapter_endpoint_exports_markdown() -> None:
83
  assert payload.startswith("# The Unwritten Almanac Chapter")
84
  assert "## Page 1:" in payload
85
  assert "## Page 2:" in payload
86
- assert "Closest inked pages:" in payload
 
 
87
 
88
 
89
  def test_lora_dataset_endpoint_exports_sft_jsonl() -> None:
 
69
 
70
  assert payload.startswith("# Hackathon Advisor Field Notes")
71
  assert "Skills: frontend" in payload
72
+ assert "Goals: Build notes" in payload
73
+ assert "Targets: Field Notes" not in payload
74
  assert "## Turn Trace" in payload
75
  assert "Record the trace and write build notes" in payload
76
 
 
84
  assert payload.startswith("# The Unwritten Almanac Chapter")
85
  assert "## Page 1:" in payload
86
  assert "## Page 2:" in payload
87
+ assert "Goals:" in payload
88
+ assert "Targets:" not in payload
89
+ assert "Closest cited pages:" in payload
90
 
91
 
92
  def test_lora_dataset_endpoint_exports_sft_jsonl() -> None:
tests/test_chapter.py CHANGED
@@ -23,8 +23,9 @@ def test_chapter_markdown_contains_idea_pages_and_citations() -> None:
23
  assert markdown.startswith("# The Unwritten Almanac Chapter")
24
  assert "## Page 1:" in markdown
25
  assert "## Page 2:" in markdown
26
- assert "Targets:" in markdown
27
- assert "Closest inked pages:" in markdown
 
28
  assert "Page 30:" in markdown
29
 
30
 
@@ -37,4 +38,4 @@ def test_empty_chapter_markdown_is_explicit() -> None:
37
  },
38
  )
39
 
40
- assert "No fate pages have been written yet." in markdown
 
23
  assert markdown.startswith("# The Unwritten Almanac Chapter")
24
  assert "## Page 1:" in markdown
25
  assert "## Page 2:" in markdown
26
+ assert "Goals:" in markdown
27
+ assert "Targets:" not in markdown
28
+ assert "Closest cited pages:" in markdown
29
  assert "Page 30:" in markdown
30
 
31
 
 
38
  },
39
  )
40
 
41
+ assert "No idea pages have been written yet." in markdown
tests/test_field_notes.py CHANGED
@@ -26,7 +26,8 @@ def test_field_notes_markdown_contains_session_decisions() -> None:
26
 
27
  assert "# Hackathon Advisor Field Notes" in markdown
28
  assert "frontend prototyping" in markdown
29
- assert "Targets: Field Notes" in markdown
 
30
  assert "A local-first archive cartographer for family photos" in markdown
31
  assert "## Build Plan" in markdown
32
  assert "Record the trace and write build notes" in markdown
 
26
 
27
  assert "# Hackathon Advisor Field Notes" in markdown
28
  assert "frontend prototyping" in markdown
29
+ assert "Goals: Build notes" in markdown
30
+ assert "Targets: Field Notes" not in markdown
31
  assert "A local-first archive cartographer for family photos" in markdown
32
  assert "## Build Plan" in markdown
33
  assert "Record the trace and write build notes" in markdown