Allow traceable chapter beat expansion
This commit is contained in:
parent
8a6eab543f
commit
864f3e80e2
3 changed files with 69 additions and 13 deletions
|
|
@ -68,15 +68,19 @@ Target words: {target_words}
|
||||||
|
|
||||||
Return strict JSON in this shape:
|
Return strict JSON in this shape:
|
||||||
{{"purpose":"","pov_character":"","tense":"past","location":"","time_context":"",
|
{{"purpose":"","pov_character":"","tense":"past","location":"","time_context":"",
|
||||||
"present":[],"target_words":{target_words},"beats":[{{"text":"","required":true}}],
|
"present":[],"target_words":{target_words},"beats":[{{"text":"","required":true,
|
||||||
|
"supports_beat_id":"","kind":""}}],
|
||||||
"exact_values":[],"constraints":[],"forbidden_events":[],"ending_state":"","final_image":"",
|
"exact_values":[],"constraints":[],"forbidden_events":[],"ending_state":"","final_image":"",
|
||||||
"boundary_constraints":[],"continuity_questions":[]}}
|
"boundary_constraints":[],"continuity_questions":[]}}
|
||||||
|
|
||||||
Use 3-8 concrete beats. Mark only indispensable events required:true. Preserve unresolved continuity questions
|
Use 3-8 concrete beats. Mark only indispensable events required:true. Preserve unresolved continuity questions
|
||||||
instead of guessing. The ending state and final image must define where the scene stops. For a book-bound
|
instead of guessing. The ending state and final image must define where the scene stops. For a book-bound
|
||||||
scene, repeat the approved chapter beats without expanding their events or state changes. Include an exact
|
scene, do not restate or replace the approved chapter beats. Propose subordinate execution beats instead.
|
||||||
value only when it appears explicitly in the approved chapter packet or scene brief; omit numbers and
|
Every proposed beat must name one approved supports_beat_id, use kind "dramatization" or "transition", and
|
||||||
classifications found only in background source context."""
|
remain non-required. It may stage action, resistance, dialogue pressure, sensory evidence, or movement that
|
||||||
|
realizes its parent beat, but it cannot add an outcome, agreement, state change, explanation, or arc movement.
|
||||||
|
Include an exact value only when it appears explicitly in the approved chapter packet or scene brief; omit
|
||||||
|
numbers and classifications found only in background source context."""
|
||||||
|
|
||||||
SCENE_IDEA_TYPES = {
|
SCENE_IDEA_TYPES = {
|
||||||
"quiet_connection": "A short, low-stakes character moment whose meaning comes from attention or choice.",
|
"quiet_connection": "A short, low-stakes character moment whose meaning comes from attention or choice.",
|
||||||
|
|
|
||||||
|
|
@ -677,19 +677,43 @@ class StandaloneSceneService:
|
||||||
)
|
)
|
||||||
approved_beats = [
|
approved_beats = [
|
||||||
{
|
{
|
||||||
|
"beat_id": str(item.get("beat_id") or "").strip(),
|
||||||
"text": str(item.get("text") or "").strip(),
|
"text": str(item.get("text") or "").strip(),
|
||||||
"required": bool(item.get("required")),
|
"required": bool(item.get("required")),
|
||||||
|
"kind": "contract",
|
||||||
}
|
}
|
||||||
for item in chapter.get("beats") or []
|
for item in chapter.get("beats") or []
|
||||||
if isinstance(item, dict) and str(item.get("text") or "").strip()
|
if isinstance(item, dict) and str(item.get("text") or "").strip()
|
||||||
]
|
]
|
||||||
approved_text = {item["text"].casefold() for item in approved_beats}
|
approved_by_text = {item["text"].casefold(): item for item in approved_beats}
|
||||||
discarded = [
|
approved_ids = {item["beat_id"] for item in approved_beats}
|
||||||
item
|
expansions_by_parent = {beat_id: [] for beat_id in approved_ids}
|
||||||
for item in plan["beats"]
|
discarded = []
|
||||||
if str(item.get("text") or "").strip().casefold() not in approved_text
|
for item in plan["beats"]:
|
||||||
]
|
text = str(item.get("text") or "").strip()
|
||||||
plan["beats"] = approved_beats
|
if text.casefold() in approved_by_text:
|
||||||
|
continue
|
||||||
|
supports_beat_id = str(item.get("supports_beat_id") or "").strip()
|
||||||
|
kind = str(item.get("kind") or "").strip().lower()
|
||||||
|
if supports_beat_id not in approved_ids or kind not in {
|
||||||
|
"dramatization",
|
||||||
|
"transition",
|
||||||
|
}:
|
||||||
|
discarded.append(item)
|
||||||
|
continue
|
||||||
|
expansions_by_parent[supports_beat_id].append(
|
||||||
|
{
|
||||||
|
"text": text,
|
||||||
|
"required": False,
|
||||||
|
"supports_beat_id": supports_beat_id,
|
||||||
|
"kind": kind,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
scoped_beats = []
|
||||||
|
for approved in approved_beats:
|
||||||
|
scoped_beats.append(approved)
|
||||||
|
scoped_beats.extend(expansions_by_parent[approved["beat_id"]])
|
||||||
|
plan["beats"] = scoped_beats
|
||||||
plan["purpose"] = str(chapter.get("purpose") or "").strip()
|
plan["purpose"] = str(chapter.get("purpose") or "").strip()
|
||||||
plan["ending_state"] = str(chapter.get("ending_state") or "").strip()
|
plan["ending_state"] = str(chapter.get("ending_state") or "").strip()
|
||||||
return plan, discarded
|
return plan, discarded
|
||||||
|
|
@ -1145,7 +1169,14 @@ class StandaloneSceneService:
|
||||||
raw = {"text": raw, "required": False}
|
raw = {"text": raw, "required": False}
|
||||||
text = str(raw.get("text") or "").strip()
|
text = str(raw.get("text") or "").strip()
|
||||||
if text:
|
if text:
|
||||||
beats.append({"text": text, "required": bool(raw.get("required"))})
|
beat = {"text": text, "required": bool(raw.get("required"))}
|
||||||
|
supports_beat_id = str(raw.get("supports_beat_id") or "").strip()
|
||||||
|
kind = str(raw.get("kind") or "").strip().lower()
|
||||||
|
if supports_beat_id:
|
||||||
|
beat["supports_beat_id"] = supports_beat_id
|
||||||
|
if kind:
|
||||||
|
beat["kind"] = kind
|
||||||
|
beats.append(beat)
|
||||||
if not beats:
|
if not beats:
|
||||||
raise ValueError("scene plan must contain at least one beat")
|
raise ValueError("scene plan must contain at least one beat")
|
||||||
plan = {
|
plan = {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ class OutOfScopePlanningRouter:
|
||||||
"present": ["Protagonist"],
|
"present": ["Protagonist"],
|
||||||
"beats": [
|
"beats": [
|
||||||
{"text": "The chapter performs turn 1.", "required": True},
|
{"text": "The chapter performs turn 1.", "required": True},
|
||||||
|
{
|
||||||
|
"text": "The protagonist opens the record and challenges its premise.",
|
||||||
|
"required": True,
|
||||||
|
"supports_beat_id": "beat-1",
|
||||||
|
"kind": "dramatization",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"text": "The planner adds a Floor ninety-six inspection.",
|
"text": "The planner adds a Floor ninety-six inspection.",
|
||||||
"required": True,
|
"required": True,
|
||||||
|
|
@ -395,10 +401,25 @@ def test_book_scene_plan_discards_exact_values_outside_approved_scope(work: Work
|
||||||
{"label": "approved turn", "value": "turn 1"}
|
{"label": "approved turn", "value": "turn 1"}
|
||||||
]
|
]
|
||||||
assert scene.plan["beats"] == [
|
assert scene.plan["beats"] == [
|
||||||
{"text": "The chapter performs turn 1.", "required": True}
|
{
|
||||||
|
"beat_id": "beat-1",
|
||||||
|
"text": "The chapter performs turn 1.",
|
||||||
|
"required": True,
|
||||||
|
"kind": "contract",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The protagonist opens the record and challenges its premise.",
|
||||||
|
"required": False,
|
||||||
|
"supports_beat_id": "beat-1",
|
||||||
|
"kind": "dramatization",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
assert scene.plan["purpose"] == "Advance turn 1."
|
assert scene.plan["purpose"] == "Advance turn 1."
|
||||||
assert scene.plan["ending_state"] == "Turn 1 is complete."
|
assert scene.plan["ending_state"] == "Turn 1 is complete."
|
||||||
|
beat_requirements = [
|
||||||
|
item for item in scene.contract_requirements if item["type"] == "BEAT"
|
||||||
|
]
|
||||||
|
assert [item["blocking"] for item in beat_requirements] == [True, False]
|
||||||
assert scene.generation_metadata["planning"][
|
assert scene.generation_metadata["planning"][
|
||||||
"discarded_out_of_scope_exact_values"
|
"discarded_out_of_scope_exact_values"
|
||||||
] == [{"label": "background floor", "value": "Floor ninety-six"}]
|
] == [{"label": "background floor", "value": "Floor ninety-six"}]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue