Spaces:
Running on Zero
Running on Zero
Prioritize Dream QA Today Tip card
#21
by ADJCJH - opened
- PRODUCT.md +2 -1
- README.md +3 -3
- docs/handoff.md +1 -0
- docs/prd.md +3 -1
- docs/smoke/2026-06-10-tip-first-card-smoke.md +88 -0
- docs/spec.md +3 -1
- docs/superpowers/plans/2026-06-10-dream-qa-tip-first-card.md +134 -0
- dream_customs/pipeline.py +29 -1
- dream_customs/render.py +9 -9
- scripts/evaluate_today_tip_quality.py +17 -0
- tests/fixtures/today_tip_eval_cases.json +3 -1
- tests/test_render.py +26 -2
- tests/test_ui_actions.py +26 -0
PRODUCT.md
CHANGED
|
@@ -14,7 +14,7 @@ The product helps a user unpack a dream through a guided, step-by-step Q&A. It a
|
|
| 14 |
|
| 15 |
The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
|
| 16 |
|
| 17 |
-
Success means the user receives something more useful than a one-shot interpretation:
|
| 18 |
|
| 19 |
## Brand Personality
|
| 20 |
|
|
@@ -44,6 +44,7 @@ The voice is a calm dream companion who asks good questions and takes odd detail
|
|
| 44 |
8. Recommendation quality is tested with a deterministic Today Tip eval set before deployment.
|
| 45 |
9. English mode must stay natural English, including translated or paraphrased dream anchors; mixed Chinese helper text is a regression.
|
| 46 |
10. Tiny actions should feel like a first step the user may choose, not a hard command to solve the real-world problem immediately.
|
|
|
|
| 47 |
|
| 48 |
## Accessibility & Inclusion
|
| 49 |
|
|
|
|
| 14 |
|
| 15 |
The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
|
| 16 |
|
| 17 |
+
Success means the user receives something more useful than a one-shot interpretation: one clear today tip first, an optional tiny action, and then a non-certain explanation grounded in concrete dream details and the user's answer. The tip can be practical, playful, or simply caring, but it must feel connected to the dream.
|
| 18 |
|
| 19 |
## Brand Personality
|
| 20 |
|
|
|
|
| 44 |
8. Recommendation quality is tested with a deterministic Today Tip eval set before deployment.
|
| 45 |
9. English mode must stay natural English, including translated or paraphrased dream anchors; mixed Chinese helper text is a regression.
|
| 46 |
10. Tiny actions should feel like a first step the user may choose, not a hard command to solve the real-world problem immediately.
|
| 47 |
+
11. The final card prioritizes the Today Tip before interpretation; interpretation is supporting reflection, not the main payoff.
|
| 48 |
|
| 49 |
## Accessibility & Inclusion
|
| 50 |
|
README.md
CHANGED
|
@@ -30,7 +30,7 @@ The public hackathon demo is English-first for international judges. A visible i
|
|
| 30 |
|
| 31 |
## Concept
|
| 32 |
|
| 33 |
-
Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, asks a grounded follow-up question, and returns a Today Tip card:
|
| 34 |
|
| 35 |
This is not a therapy, diagnosis, or prophecy product.
|
| 36 |
|
|
@@ -48,8 +48,8 @@ This is not a therapy, diagnosis, or prophecy product.
|
|
| 48 |
1. Enter a dream by text, image, voice, or a mix.
|
| 49 |
2. State or let the app infer the main question.
|
| 50 |
3. Answer or skip one or more follow-up questions.
|
| 51 |
-
4.
|
| 52 |
-
5.
|
| 53 |
|
| 54 |
## Language
|
| 55 |
|
|
|
|
| 30 |
|
| 31 |
## Concept
|
| 32 |
|
| 33 |
+
Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, asks a grounded follow-up question, and returns a Today Tip card: one practical or caring suggestion first, an optional tiny action, and a short non-certain supporting reflection.
|
| 34 |
|
| 35 |
This is not a therapy, diagnosis, or prophecy product.
|
| 36 |
|
|
|
|
| 48 |
1. Enter a dream by text, image, voice, or a mix.
|
| 49 |
2. State or let the app infer the main question.
|
| 50 |
3. Answer or skip one or more follow-up questions.
|
| 51 |
+
4. Receive one Today Tip tied to concrete dream details and the user's answer.
|
| 52 |
+
5. Read the supporting reflection if the user wants more context.
|
| 53 |
|
| 54 |
## Language
|
| 55 |
|
docs/handoff.md
CHANGED
|
@@ -56,6 +56,7 @@ Final positioning:
|
|
| 56 |
- English mode is not just English UI chrome; generated anchors, questions, and Today Tips should read as natural English.
|
| 57 |
- If the model receives Chinese anchors while the selected language is English, translate or paraphrase them unless a short exact quote is intentionally preserved.
|
| 58 |
- Tiny actions should feel like five-minute first steps. Avoid urgent command phrasing such as "address it immediately."
|
|
|
|
| 59 |
|
| 60 |
## Model Decision
|
| 61 |
|
|
|
|
| 56 |
- English mode is not just English UI chrome; generated anchors, questions, and Today Tips should read as natural English.
|
| 57 |
- If the model receives Chinese anchors while the selected language is English, translate or paraphrase them unless a short exact quote is intentionally preserved.
|
| 58 |
- Tiny actions should feel like five-minute first steps. Avoid urgent command phrasing such as "address it immediately."
|
| 59 |
+
- The final card should show the Today Tip before interpretation; interpretation is supporting reflection and should use the user's answer when it names a real task.
|
| 60 |
|
| 61 |
## Model Decision
|
| 62 |
|
docs/prd.md
CHANGED
|
@@ -98,11 +98,13 @@ As a user, I want a clear final result so that I leave with one small thing for
|
|
| 98 |
|
| 99 |
Acceptance criteria:
|
| 100 |
|
| 101 |
-
- Result includes dream summary, main question,
|
|
|
|
| 102 |
- Result is readable on mobile and desktop.
|
| 103 |
- Result avoids medical, prophetic, or deterministic framing.
|
| 104 |
- Today tip references a concrete dream anchor.
|
| 105 |
- Optional tiny action is a five-minute first step, not a command to solve the whole issue immediately.
|
|
|
|
| 106 |
|
| 107 |
#### Story 6: Bilingual Demo
|
| 108 |
|
|
|
|
| 98 |
|
| 99 |
Acceptance criteria:
|
| 100 |
|
| 101 |
+
- Result includes dream summary, main question, today tip, optional tiny action, supporting interpretation, optional caring note, and safety note when needed.
|
| 102 |
+
- Result presents the Today Tip before supporting interpretation.
|
| 103 |
- Result is readable on mobile and desktop.
|
| 104 |
- Result avoids medical, prophetic, or deterministic framing.
|
| 105 |
- Today tip references a concrete dream anchor.
|
| 106 |
- Optional tiny action is a five-minute first step, not a command to solve the whole issue immediately.
|
| 107 |
+
- Supporting interpretation uses the user's answer when the answer names a concrete waking-life task.
|
| 108 |
|
| 109 |
#### Story 6: Bilingual Demo
|
| 110 |
|
docs/smoke/2026-06-10-tip-first-card-smoke.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dream QA Tip-First Card Smoke - 2026-06-10
|
| 2 |
+
|
| 3 |
+
## Scope
|
| 4 |
+
|
| 5 |
+
- Put the final Today Tip before supporting interpretation.
|
| 6 |
+
- Make supporting interpretation use the user's waking-life answer when it names a concrete task.
|
| 7 |
+
- Preserve English-only output quality and the existing Chinese toggle.
|
| 8 |
+
|
| 9 |
+
## TDD Red
|
| 10 |
+
|
| 11 |
+
Focused red commands:
|
| 12 |
+
|
| 13 |
+
```text
|
| 14 |
+
.venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
|
| 15 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
Observed failures before implementation:
|
| 19 |
+
|
| 20 |
+
```text
|
| 21 |
+
test_today_tip_card_prioritizes_tip_before_interpretation
|
| 22 |
+
test_english_interpretation_uses_user_answer_before_tip
|
| 23 |
+
test_today_tip_quality_eval_passes_fixture_cases
|
| 24 |
+
elevator_email_first_sentence: missing_interpretation_terms:overdue email
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
## Local Verification
|
| 28 |
+
|
| 29 |
+
Commands:
|
| 30 |
+
|
| 31 |
+
```text
|
| 32 |
+
.venv/bin/python -m pytest -q
|
| 33 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 34 |
+
git diff --check
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
Observed:
|
| 38 |
+
|
| 39 |
+
```text
|
| 40 |
+
97 passed, 2 warnings
|
| 41 |
+
{"case_count": 11, "failures": {}, "passes": true}
|
| 42 |
+
git diff --check clean
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
## Local Gradio Config
|
| 46 |
+
|
| 47 |
+
Observed from `http://127.0.0.1:7860/config`:
|
| 48 |
+
|
| 49 |
+
```text
|
| 50 |
+
title=Dream QA
|
| 51 |
+
version=4.44.1
|
| 52 |
+
mode=blocks
|
| 53 |
+
component_count=72
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Local Action Smoke
|
| 57 |
+
|
| 58 |
+
English dream:
|
| 59 |
+
|
| 60 |
+
```text
|
| 61 |
+
I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
English answer:
|
| 65 |
+
|
| 66 |
+
```text
|
| 67 |
+
I want to make starting my overdue email easier without feeling trapped by it.
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
Observed:
|
| 71 |
+
|
| 72 |
+
```text
|
| 73 |
+
status=tip
|
| 74 |
+
title=Today Tip
|
| 75 |
+
tip_before_reflection=True
|
| 76 |
+
interpretation_has_email=True
|
| 77 |
+
interpretation_has_floor14=True
|
| 78 |
+
has_chinese_leakage=False
|
| 79 |
+
has_immediately=False
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Hugging Face Space Sync
|
| 83 |
+
|
| 84 |
+
Pending.
|
| 85 |
+
|
| 86 |
+
## Public User-View Review
|
| 87 |
+
|
| 88 |
+
Pending.
|
docs/spec.md
CHANGED
|
@@ -176,9 +176,9 @@ The final result must include:
|
|
| 176 |
|
| 177 |
- Dream summary.
|
| 178 |
- Main question.
|
| 179 |
-
- Grounded interpretation.
|
| 180 |
- One primary today tip.
|
| 181 |
- Optional tiny action.
|
|
|
|
| 182 |
- Optional caring note.
|
| 183 |
- Safety note if the input indicates severe distress.
|
| 184 |
|
|
@@ -191,6 +191,8 @@ The final result must:
|
|
| 191 |
- Use English UI/output labels by default and Chinese labels only when `中文` is selected.
|
| 192 |
- Keep English mode fully English unless preserving a deliberate short user quote.
|
| 193 |
- Make the optional tiny action a five-minute first step, not a demand to complete the whole waking-life problem.
|
|
|
|
|
|
|
| 194 |
|
| 195 |
## 10. UX Structure
|
| 196 |
|
|
|
|
| 176 |
|
| 177 |
- Dream summary.
|
| 178 |
- Main question.
|
|
|
|
| 179 |
- One primary today tip.
|
| 180 |
- Optional tiny action.
|
| 181 |
+
- Supporting grounded interpretation.
|
| 182 |
- Optional caring note.
|
| 183 |
- Safety note if the input indicates severe distress.
|
| 184 |
|
|
|
|
| 191 |
- Use English UI/output labels by default and Chinese labels only when `中文` is selected.
|
| 192 |
- Keep English mode fully English unless preserving a deliberate short user quote.
|
| 193 |
- Make the optional tiny action a five-minute first step, not a demand to complete the whole waking-life problem.
|
| 194 |
+
- Render the Today Tip before the interpretation so the user sees the actionable payoff first.
|
| 195 |
+
- When the user answer names a real task, the supporting interpretation should bridge a dream anchor to that task.
|
| 196 |
|
| 197 |
## 10. UX Structure
|
| 198 |
|
docs/superpowers/plans/2026-06-10-dream-qa-tip-first-card.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dream QA Tip-First Card Implementation Plan
|
| 2 |
+
|
| 3 |
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
| 4 |
+
|
| 5 |
+
**Goal:** Put the final Today Tip before interpretation and make the interpretation paragraph use the user's waking-life answer.
|
| 6 |
+
|
| 7 |
+
**Architecture:** Keep the existing `TodayTipCard` schema and Dream QA flow. Change HTML order in `render_today_tip_card`, add answer-aware interpretation polish in `pipeline.py`, and guard both with focused tests and a public Space smoke.
|
| 8 |
+
|
| 9 |
+
**Tech Stack:** Python, Pydantic, Gradio, pytest, Hugging Face Space.
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## File Map
|
| 14 |
+
|
| 15 |
+
- Modify `dream_customs/render.py`: render the primary Today Tip before interpretation, with interpretation as a quieter details block.
|
| 16 |
+
- Modify `dream_customs/pipeline.py`: when answers mention a concrete waking-life task such as email/message, make interpretation bridge the dream anchor to that answer.
|
| 17 |
+
- Modify `tests/test_render.py`: assert the rendered HTML puts the primary Today Tip before the interpretation block.
|
| 18 |
+
- Modify `tests/test_ui_actions.py`: assert the final card's interpretation references the user's email/message answer.
|
| 19 |
+
- Modify `scripts/evaluate_today_tip_quality.py` and `tests/fixtures/today_tip_eval_cases.json`: add optional answer-term quality checks for cases where the user gives a concrete answer.
|
| 20 |
+
- Modify `README.md`, `PRODUCT.md`, `docs/spec.md`, `docs/prd.md`, `docs/handoff.md`: record the tip-first output hierarchy and answer-aware interpretation rule.
|
| 21 |
+
- Add `docs/smoke/2026-06-10-tip-first-card-smoke.md`: record local, HF, and browser user-view verification.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
### Task 1: Tests First
|
| 26 |
+
|
| 27 |
+
**Files:**
|
| 28 |
+
- Modify: `tests/test_render.py`
|
| 29 |
+
- Modify: `tests/test_ui_actions.py`
|
| 30 |
+
- Modify: `scripts/evaluate_today_tip_quality.py`
|
| 31 |
+
- Modify: `tests/fixtures/today_tip_eval_cases.json`
|
| 32 |
+
|
| 33 |
+
- [x] **Step 1: Add render order test**
|
| 34 |
+
|
| 35 |
+
Add a test that renders a `TodayTipCard` and asserts `Today's small suggestion` appears before `Maybe this dream is pointing to`.
|
| 36 |
+
|
| 37 |
+
- [x] **Step 2: Add answer-aware interpretation test**
|
| 38 |
+
|
| 39 |
+
Add a UI action test using the elevator/email flow and assert the final card text contains `overdue email` in the interpretation or copyable card text before the Tiny action.
|
| 40 |
+
|
| 41 |
+
- [x] **Step 3: Add answer term eval**
|
| 42 |
+
|
| 43 |
+
Extend the deterministic evaluator so a fixture may include `required_answer_terms`, and fail if those terms are missing from English output.
|
| 44 |
+
|
| 45 |
+
- [x] **Step 4: Run focused red tests**
|
| 46 |
+
|
| 47 |
+
Run:
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
.venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
|
| 51 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Expected before implementation: at least one focused failure for card order or answer-aware interpretation.
|
| 55 |
+
|
| 56 |
+
### Task 2: Implement Tip-First Output
|
| 57 |
+
|
| 58 |
+
**Files:**
|
| 59 |
+
- Modify: `dream_customs/render.py`
|
| 60 |
+
- Modify: `dream_customs/pipeline.py`
|
| 61 |
+
|
| 62 |
+
- [x] **Step 1: Reorder result card HTML**
|
| 63 |
+
|
| 64 |
+
Move the primary Today Tip article before the interpretation article. Keep Tiny action after the main suggestion.
|
| 65 |
+
|
| 66 |
+
- [x] **Step 2: Quiet the interpretation block**
|
| 67 |
+
|
| 68 |
+
Keep interpretation visible, but label it as supporting reflection/details rather than the first thing a user sees.
|
| 69 |
+
|
| 70 |
+
- [x] **Step 3: Add answer-aware interpretation polish**
|
| 71 |
+
|
| 72 |
+
When the user's answer includes email/message language, set the English interpretation to bridge the dream anchor to starting that email/message gently.
|
| 73 |
+
|
| 74 |
+
- [x] **Step 4: Run focused green tests**
|
| 75 |
+
|
| 76 |
+
Run:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
.venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
|
| 80 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
Expected: focused tests pass.
|
| 84 |
+
|
| 85 |
+
### Task 3: Docs, Smoke, Deploy
|
| 86 |
+
|
| 87 |
+
**Files:**
|
| 88 |
+
- Modify: `README.md`
|
| 89 |
+
- Modify: `PRODUCT.md`
|
| 90 |
+
- Modify: `docs/spec.md`
|
| 91 |
+
- Modify: `docs/prd.md`
|
| 92 |
+
- Modify: `docs/handoff.md`
|
| 93 |
+
- Add: `docs/smoke/2026-06-10-tip-first-card-smoke.md`
|
| 94 |
+
- Modify: this plan file checkbox state.
|
| 95 |
+
|
| 96 |
+
- [x] **Step 1: Align product docs**
|
| 97 |
+
|
| 98 |
+
Record that the final screen prioritizes Today Tip first, then optional supporting interpretation.
|
| 99 |
+
|
| 100 |
+
- [x] **Step 2: Run full local verification**
|
| 101 |
+
|
| 102 |
+
Run:
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
.venv/bin/python -m pytest -q
|
| 106 |
+
.venv/bin/python scripts/evaluate_today_tip_quality.py
|
| 107 |
+
git diff --check
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
Expected: all commands pass.
|
| 111 |
+
|
| 112 |
+
- [x] **Step 3: Run local config/action smoke**
|
| 113 |
+
|
| 114 |
+
Start the local app, check `/config`, and run the elevator/email action flow.
|
| 115 |
+
|
| 116 |
+
- [ ] **Step 4: Commit and push**
|
| 117 |
+
|
| 118 |
+
Commit implementation, docs, plan, and smoke evidence. Push `feature/dream-qa-tip-first-card`.
|
| 119 |
+
|
| 120 |
+
- [ ] **Step 5: Merge GitHub and HF Space**
|
| 121 |
+
|
| 122 |
+
Create/merge GitHub PR. Upload to HF Space with `create_pr=True`, use Chrome to click Merge, then verify public `/config`.
|
| 123 |
+
|
| 124 |
+
- [ ] **Step 6: Public user-view review**
|
| 125 |
+
|
| 126 |
+
Use the public Space in browser with the elevator/email dream. Confirm Today Tip appears before interpretation and interpretation references the email answer.
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## Self-Review
|
| 131 |
+
|
| 132 |
+
- Spec coverage: Covers the exact requested next optimization: Today Tip first, interpretation as support, interpretation tied to user answer, docs and deployment.
|
| 133 |
+
- Placeholder scan: No TBD/TODO/fill-later placeholders remain.
|
| 134 |
+
- Type consistency: Reuses existing `TodayTipCard`, `render_today_tip_card`, and UI action APIs without schema changes.
|
dream_customs/pipeline.py
CHANGED
|
@@ -312,6 +312,31 @@ def _answer_based_today_tip(answers: str, anchor: str, language: str = "en") ->
|
|
| 312 |
return ""
|
| 313 |
|
| 314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
def _anchor_in_text(text: str, anchors: List[str]) -> bool:
|
| 316 |
clean = (text or "").lower()
|
| 317 |
for anchor in anchors:
|
|
@@ -594,7 +619,10 @@ def _polish_today_tip(card: TodayTipCard, intake: DreamIntake, answers: str = ""
|
|
| 594 |
polished.dream_summary = _summary_from_intake(intake, language)
|
| 595 |
if not polished.main_question.strip():
|
| 596 |
polished.main_question = _main_question_from_intake(intake, language)
|
| 597 |
-
|
|
|
|
|
|
|
|
|
|
| 598 |
polished.interpretation = _fallback_interpretation(intake, language)
|
| 599 |
generic_tip_markers = ["drink water", "hydrate", "多休息", "保持积极", "take a walk"]
|
| 600 |
answer_tip = _answer_based_today_tip(answers, anchors[0], language)
|
|
|
|
| 312 |
return ""
|
| 313 |
|
| 314 |
|
| 315 |
+
def _answer_based_interpretation(answers: str, anchor: str, language: str = "en") -> str:
|
| 316 |
+
lowered = (answers or "").lower()
|
| 317 |
+
if _is_zh(language):
|
| 318 |
+
if "邮件" in lowered or "email" in lowered:
|
| 319 |
+
return f"也许「{anchor}」不是在催你立刻完成什么,而是在提醒你:那封邮件可以先从一句话开始。"
|
| 320 |
+
if "消息" in lowered or "message" in lowered:
|
| 321 |
+
return f"也许「{anchor}」不是在催你立刻回应什么,而是在提醒你:那条消息可以先从一句草稿开始。"
|
| 322 |
+
return ""
|
| 323 |
+
if "email" in lowered:
|
| 324 |
+
return (
|
| 325 |
+
f"Maybe the {anchor} is not asking you to finish the overdue email at once. "
|
| 326 |
+
"It is pointing to the gentler threshold: opening it and writing one first sentence."
|
| 327 |
+
)
|
| 328 |
+
if "message" in lowered:
|
| 329 |
+
return (
|
| 330 |
+
f"Maybe the {anchor} is not asking you to answer every message at once. "
|
| 331 |
+
"It is pointing to the gentler threshold: opening one thread and drafting one first sentence."
|
| 332 |
+
)
|
| 333 |
+
return ""
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def _answer_bridge_anchor(anchors: List[str]) -> str:
|
| 337 |
+
return next((anchor for anchor in anchors if "14" in anchor), anchors[0])
|
| 338 |
+
|
| 339 |
+
|
| 340 |
def _anchor_in_text(text: str, anchors: List[str]) -> bool:
|
| 341 |
clean = (text or "").lower()
|
| 342 |
for anchor in anchors:
|
|
|
|
| 619 |
polished.dream_summary = _summary_from_intake(intake, language)
|
| 620 |
if not polished.main_question.strip():
|
| 621 |
polished.main_question = _main_question_from_intake(intake, language)
|
| 622 |
+
answer_interpretation = _answer_based_interpretation(answers, _answer_bridge_anchor(anchors), language)
|
| 623 |
+
if answer_interpretation:
|
| 624 |
+
polished.interpretation = answer_interpretation
|
| 625 |
+
elif not polished.interpretation.strip() or not _anchor_in_text(polished.interpretation, anchors):
|
| 626 |
polished.interpretation = _fallback_interpretation(intake, language)
|
| 627 |
generic_tip_markers = ["drink water", "hydrate", "多休息", "保持积极", "take a walk"]
|
| 628 |
answer_tip = _answer_based_today_tip(answers, anchors[0], language)
|
dream_customs/render.py
CHANGED
|
@@ -33,7 +33,7 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
|
| 33 |
"thanks": "谢谢你的分享。根据你的梦境,我们为你整理了今天的参考。"
|
| 34 |
if is_zh
|
| 35 |
else "Thanks for sharing this. Here is one grounded suggestion for today.",
|
| 36 |
-
"interpretation": "
|
| 37 |
"today_tip": "今天的小建议" if is_zh else "Today's small suggestion",
|
| 38 |
"tiny_action": "没试过的小事" if is_zh else "Tiny action",
|
| 39 |
"anchors": "关键词:" if is_zh else "Anchors:",
|
|
@@ -73,14 +73,6 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
|
| 73 |
<p>{labels['thanks']}</p>
|
| 74 |
</div>
|
| 75 |
</div>
|
| 76 |
-
<article class="dqa-result-card is-interpretation">
|
| 77 |
-
<div class="dqa-card-icon">☘</div>
|
| 78 |
-
<div>
|
| 79 |
-
<h3>{labels['interpretation']}</h3>
|
| 80 |
-
<p>{escape(card.interpretation)}</p>
|
| 81 |
-
<div class="dqa-anchor-strip">{labels['anchors']} {anchors}</div>
|
| 82 |
-
</div>
|
| 83 |
-
</article>
|
| 84 |
<article class="dqa-result-card is-primary-tip">
|
| 85 |
<div class="dqa-card-icon">☼</div>
|
| 86 |
<div>
|
|
@@ -90,6 +82,14 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
|
|
| 90 |
</div>
|
| 91 |
</article>
|
| 92 |
{tiny_action}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
{qa_history}
|
| 94 |
{safety}
|
| 95 |
{caring_note}
|
|
|
|
| 33 |
"thanks": "谢谢你的分享。根据你的梦境,我们为你整理了今天的参考。"
|
| 34 |
if is_zh
|
| 35 |
else "Thanks for sharing this. Here is one grounded suggestion for today.",
|
| 36 |
+
"interpretation": "补充解读" if is_zh else "Supporting reflection",
|
| 37 |
"today_tip": "今天的小建议" if is_zh else "Today's small suggestion",
|
| 38 |
"tiny_action": "没试过的小事" if is_zh else "Tiny action",
|
| 39 |
"anchors": "关键词:" if is_zh else "Anchors:",
|
|
|
|
| 73 |
<p>{labels['thanks']}</p>
|
| 74 |
</div>
|
| 75 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
<article class="dqa-result-card is-primary-tip">
|
| 77 |
<div class="dqa-card-icon">☼</div>
|
| 78 |
<div>
|
|
|
|
| 82 |
</div>
|
| 83 |
</article>
|
| 84 |
{tiny_action}
|
| 85 |
+
<article class="dqa-result-card is-interpretation">
|
| 86 |
+
<div class="dqa-card-icon">☘</div>
|
| 87 |
+
<div>
|
| 88 |
+
<h3>{labels['interpretation']}</h3>
|
| 89 |
+
<p>{escape(card.interpretation)}</p>
|
| 90 |
+
<div class="dqa-anchor-strip">{labels['anchors']} {anchors}</div>
|
| 91 |
+
</div>
|
| 92 |
+
</article>
|
| 93 |
{qa_history}
|
| 94 |
{safety}
|
| 95 |
{caring_note}
|
scripts/evaluate_today_tip_quality.py
CHANGED
|
@@ -52,6 +52,13 @@ def evaluate_case(case: Dict[str, Any]) -> List[str]:
|
|
| 52 |
view = _text_for_case(case)
|
| 53 |
combined = "\n".join([view.get("card_title", ""), view.get("card_text", ""), view.get("card_html", "")])
|
| 54 |
lowered = combined.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
failures: List[str] = []
|
| 56 |
|
| 57 |
if view.get("status") != "tip":
|
|
@@ -60,6 +67,16 @@ def evaluate_case(case: Dict[str, Any]) -> List[str]:
|
|
| 60 |
if not any(anchor.lower() in lowered for anchor in case.get("required_anchors", [])):
|
| 61 |
failures.append("missing_required_anchor")
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
old_terms = [term for term in OLD_CUSTOMS_TERMS if term in lowered]
|
| 64 |
if old_terms:
|
| 65 |
failures.append("old_customs_terms:" + ",".join(old_terms))
|
|
|
|
| 52 |
view = _text_for_case(case)
|
| 53 |
combined = "\n".join([view.get("card_title", ""), view.get("card_text", ""), view.get("card_html", "")])
|
| 54 |
lowered = combined.lower()
|
| 55 |
+
interpretation = (
|
| 56 |
+
view.get("debug", {})
|
| 57 |
+
.get("session", {})
|
| 58 |
+
.get("draft_tip", {})
|
| 59 |
+
.get("interpretation", "")
|
| 60 |
+
.lower()
|
| 61 |
+
)
|
| 62 |
failures: List[str] = []
|
| 63 |
|
| 64 |
if view.get("status") != "tip":
|
|
|
|
| 67 |
if not any(anchor.lower() in lowered for anchor in case.get("required_anchors", [])):
|
| 68 |
failures.append("missing_required_anchor")
|
| 69 |
|
| 70 |
+
missing_answer_terms = [term for term in case.get("required_answer_terms", []) if term.lower() not in lowered]
|
| 71 |
+
if missing_answer_terms:
|
| 72 |
+
failures.append("missing_answer_terms:" + ",".join(missing_answer_terms))
|
| 73 |
+
|
| 74 |
+
missing_interpretation_terms = [
|
| 75 |
+
term for term in case.get("required_interpretation_terms", []) if term.lower() not in interpretation
|
| 76 |
+
]
|
| 77 |
+
if missing_interpretation_terms:
|
| 78 |
+
failures.append("missing_interpretation_terms:" + ",".join(missing_interpretation_terms))
|
| 79 |
+
|
| 80 |
old_terms = [term for term in OLD_CUSTOMS_TERMS if term in lowered]
|
| 81 |
if old_terms:
|
| 82 |
failures.append("old_customs_terms:" + ",".join(old_terms))
|
tests/fixtures/today_tip_eval_cases.json
CHANGED
|
@@ -11,7 +11,9 @@
|
|
| 11 |
"dream_text": "I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.",
|
| 12 |
"mood": "Uneasy",
|
| 13 |
"answer": "I want to make starting my overdue email easier without feeling trapped by it.",
|
| 14 |
-
"required_anchors": ["elevator", "button", "14"]
|
|
|
|
|
|
|
| 15 |
},
|
| 16 |
{
|
| 17 |
"id": "exam_without_pencil",
|
|
|
|
| 11 |
"dream_text": "I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.",
|
| 12 |
"mood": "Uneasy",
|
| 13 |
"answer": "I want to make starting my overdue email easier without feeling trapped by it.",
|
| 14 |
+
"required_anchors": ["elevator", "button", "14"],
|
| 15 |
+
"required_answer_terms": ["overdue email", "first sentence"],
|
| 16 |
+
"required_interpretation_terms": ["overdue email", "floor 14"]
|
| 17 |
},
|
| 18 |
{
|
| 19 |
"id": "exam_without_pencil",
|
tests/test_render.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
| 1 |
from dream_customs.pipeline import add_evidence, create_session
|
| 2 |
-
from dream_customs.render import
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
def test_render_pact_card_contains_core_fields():
|
|
@@ -72,3 +78,21 @@ def test_status_bar_is_status_not_fake_navigation():
|
|
| 72 |
assert "Current: Empty desk" in html
|
| 73 |
assert "dc-phase-rail" not in html
|
| 74 |
assert "<nav" not in html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from dream_customs.pipeline import add_evidence, create_session
|
| 2 |
+
from dream_customs.render import (
|
| 3 |
+
render_pact_card,
|
| 4 |
+
render_pact_inspector,
|
| 5 |
+
render_status_bar,
|
| 6 |
+
render_timeline,
|
| 7 |
+
render_today_tip_card,
|
| 8 |
+
)
|
| 9 |
+
from dream_customs.schema import PactCard, TodayTipCard
|
| 10 |
|
| 11 |
|
| 12 |
def test_render_pact_card_contains_core_fields():
|
|
|
|
| 78 |
assert "Current: Empty desk" in html
|
| 79 |
assert "dc-phase-rail" not in html
|
| 80 |
assert "<nav" not in html
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def test_today_tip_card_prioritizes_tip_before_interpretation():
|
| 84 |
+
html = render_today_tip_card(
|
| 85 |
+
TodayTipCard(
|
| 86 |
+
dream_summary="You dreamed about an elevator.",
|
| 87 |
+
main_question="What feels hard to start?",
|
| 88 |
+
dream_anchors=["floor 14", "elevator"],
|
| 89 |
+
followup_questions=["What real task feels stuck?"],
|
| 90 |
+
user_answers=["An overdue email feels hard to start."],
|
| 91 |
+
interpretation="Maybe this dream is pointing to a stuck beginning.",
|
| 92 |
+
today_tip="Open the overdue email and write only the first sentence.",
|
| 93 |
+
tiny_action="Set a five-minute timer and stop after one sentence.",
|
| 94 |
+
caring_note="One line is enough for now.",
|
| 95 |
+
)
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
assert html.index("Today's small suggestion") < html.index("Maybe this dream is pointing to")
|
tests/test_ui_actions.py
CHANGED
|
@@ -160,3 +160,29 @@ def test_english_today_tip_has_no_chinese_anchor_leakage():
|
|
| 160 |
assert "overdue email" in combined.lower()
|
| 161 |
assert "first sentence" in combined.lower()
|
| 162 |
assert "immediately" not in combined.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
assert "overdue email" in combined.lower()
|
| 161 |
assert "first sentence" in combined.lower()
|
| 162 |
assert "immediately" not in combined.lower()
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def test_english_interpretation_uses_user_answer_before_tip():
|
| 166 |
+
state, _view_json = submit_dream_action(
|
| 167 |
+
dream_text=(
|
| 168 |
+
"I dreamed I was in an elevator where the floor buttons melted like wax. "
|
| 169 |
+
"The number 14 kept blinking, and I felt late but strangely calm."
|
| 170 |
+
),
|
| 171 |
+
mood="Uneasy",
|
| 172 |
+
text_backend="demo",
|
| 173 |
+
vision_backend="demo",
|
| 174 |
+
language="en",
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
_state, view_json = answer_to_card_action(
|
| 178 |
+
state,
|
| 179 |
+
"I want to make starting my overdue email easier without feeling trapped by it.",
|
| 180 |
+
text_backend="demo",
|
| 181 |
+
vision_backend="demo",
|
| 182 |
+
language="en",
|
| 183 |
+
)
|
| 184 |
+
view = json.loads(view_json)
|
| 185 |
+
interpretation_line = next(line for line in view["card_text"].splitlines() if line.startswith("Interpretation:"))
|
| 186 |
+
|
| 187 |
+
assert "overdue email" in interpretation_line.lower()
|
| 188 |
+
assert "floor 14" in interpretation_line.lower()
|