Improve semantic classification precision for BUG-015

This commit is contained in:
2026-08-09 16:15:35 +02:00
parent 0b24351127
commit fd7d5e1424
19 changed files with 464 additions and 15 deletions
@@ -0,0 +1,8 @@
# progeo_action_precision
Minimal German regression derived from Progeo chunks 2, 10, 17 and 18. It
separates suggestions and exploratory possibilities from established work and
tests explicit acceptance of responsibility.
Expected semantics: only the accepted review task and the already-established
article work are Action Items.
@@ -0,0 +1,21 @@
{
"facts": [],
"decisions": [],
"todos": [
{
"task": "Messdaten bis Freitag prüfen.",
"responsible": "Nina",
"deadline": "Freitag",
"evidence": "Nina: Ja, ich übernehme die Prüfung bis Freitag."
},
{
"task": "CET-Artikel erstellen und anschließend im Konsortium verteilen.",
"responsible": null,
"deadline": null,
"evidence": "Den CET-Artikel erstellen wir bereits und schicken ihn anschließend im Konsortium herum."
}
],
"questions": [],
"positions": [],
"technical": []
}
@@ -0,0 +1,11 @@
Martin: Die Geometrie kann man vielleicht noch optimieren. Dann würde man mal gucken, was herauskommt.
Antonius: Marleen müsste vielleicht mal äußern, was Hüsker sich unter dem Versuch vorstellt.
Tim: Wir könnten Dirk Textor vielleicht noch einmal kontaktieren.
Antonius: Nina, übernimmst du die Prüfung der Messdaten bis Freitag?
Nina: Ja, ich übernehme die Prüfung bis Freitag.
Tim: Den CET-Artikel erstellen wir bereits und schicken ihn anschließend im Konsortium herum.
@@ -0,0 +1,6 @@
# progeo_decision_precision
Minimal German regression derived from Progeo chunks 12 and 16. It contrasts
an option and a personal preference with an explicit group rejection.
Expected semantics: only the explicit rejection is a Decision.
@@ -0,0 +1,13 @@
{
"facts": [],
"decisions": [
{
"decision": "Die Gruppe lehnt die Zusammenarbeit mit Dr. Schlummer ab.",
"evidence": "Dann haben wir gesagt: Nein, die Zusammenarbeit mit Dr. Schlummer machen wir nicht. Antonius: Ja, das ist entschieden."
}
],
"todos": [],
"questions": [],
"positions": [],
"technical": []
}
@@ -0,0 +1,9 @@
Tim: Die Option steht im Raum, das Material chemisch recyceln zu lassen.
Martin: Ich würde nicht in eine reale Anlage gehen. Wenn überhaupt, können wir über ein Technikum reden.
Antonius: Das Angebot von Dr. Schlummer kostet 30.000 Euro.
Tim: Dann haben wir gesagt: Nein, die Zusammenarbeit mit Dr. Schlummer machen wir nicht.
Antonius: Ja, das ist entschieden.
@@ -0,0 +1,8 @@
# progeo_question_precision
Minimal German regression derived from Progeo chunks 8 and 18. It contrasts
uncertainty, an observation and an immediately answered question with an
explicitly unresolved information need.
Expected semantics: only the explicitly unresolved publication question is an
Open Question.
@@ -0,0 +1,13 @@
{
"facts": [],
"decisions": [],
"todos": [],
"questions": [
{
"question": "Welche Daten aus dem Energieaudit dürfen veröffentlicht werden?",
"evidence": "Welche Daten aus dem Energieaudit dürfen wir veröffentlichen? Martin: Das ist weiterhin ungeklärt."
}
],
"positions": [],
"technical": []
}
@@ -0,0 +1,11 @@
Martin: Ob sich das Waschen lohnt, weiß ich nicht.
Tim: Das lässt sich teilweise optimieren und teilweise nicht.
Antonius: Gibt es schon ein Programm für die Veranstaltung?
Tim: Ja, das Programm steht auf der Dechema-Seite und ist vollständig.
Antonius: Welche Daten aus dem Energieaudit dürfen wir veröffentlichen?
Martin: Das ist weiterhin ungeklärt. Wir müssen die Freigabe noch klären.
+36
View File
@@ -0,0 +1,36 @@
import unittest
from src.meeting_lab.extraction.extract_chunks import build_prompt
class ClassificationInvariantPromptTests(unittest.TestCase):
def setUp(self) -> None:
self.prompt = build_prompt("regression.txt", "Meeting transcript.")
def test_decision_requires_settled_outcome_and_prefers_precision(self) -> None:
self.assertIn("settled, selected, approved", self.prompt)
self.assertIn("possibility, option", self.prompt)
self.assertIn("Prefer\nomission over a false Decision or Action Item", self.prompt)
def test_action_requires_established_future_work(self) -> None:
self.assertIn("concrete future action that the meeting establishes as", self.prompt)
self.assertIn("suggestion, hypothetical action, possible next step", self.prompt.lower())
self.assertIn("Do not create an Action Item merely because", self.prompt)
self.assertIn("statement contains an", self.prompt)
self.assertIn("imperative-like verb", self.prompt)
def test_action_existence_is_separate_from_responsibility(self) -> None:
self.assertIn("First decide whether the action itself exists", self.prompt)
self.assertIn("An Action Item may exist without a named responsible", self.prompt)
self.assertIn("responsible person", self.prompt)
self.assertIn("explicitly assigned, volunteering", self.prompt)
self.assertIn("accepting/confirming the task", self.prompt)
def test_open_question_requires_unresolved_need(self) -> None:
self.assertIn("concrete unresolved information, decision, or", self.prompt)
self.assertIn("Uncertainty, doubt, ignorance, difficulty", self.prompt)
self.assertIn("answered in the available context is not open", self.prompt)
if __name__ == "__main__":
unittest.main()
+25 -7
View File
@@ -101,17 +101,20 @@ Final answer:
prompt = build_prompt("transcript.txt", "Anna: Agreed.")
self.assertIn("Du extrahierst Informationen aus Meeting-Transkripten.", prompt)
self.assertIn("You extract decisions from meeting transcript text.", prompt)
self.assertIn("Extract each decision as one atomic commitment.", prompt)
self.assertIn("Classification contract for Decisions", prompt)
self.assertIn("A Decision requires evidence", prompt)
self.assertIn("Anna: Agreed.", prompt)
def test_build_prompt_includes_todo_prompt_file(self) -> None:
prompt = build_prompt("transcript.txt", "Nina: I will update it.")
self.assertEqual(EXTRACTION_TASK_PROMPT_NAMES, ("decisions.md", "todos.md"))
self.assertIn("Action-item responsibility rule:", prompt)
self.assertEqual(
EXTRACTION_TASK_PROMPT_NAMES,
("classification.md",),
)
self.assertIn("Action Item:", prompt)
self.assertIn(
"A named responsible person may be extracted only when the transcript contains",
"person may be named only when explicitly assigned",
prompt,
)
@@ -131,8 +134,9 @@ Final answer:
)
for prompt in (no_context_prompt, context_prompt):
self.assertIn("You extract decisions from meeting transcript text.", prompt)
self.assertIn("Action-item responsibility rule:", prompt)
self.assertIn("Classification contract for Decisions", prompt)
self.assertIn("Action Item:", prompt)
self.assertIn("Open Question:", prompt)
def test_prompt_assembly_is_deterministic(self) -> None:
first = build_prompt("transcript.txt", "Nina: I will update it.")
@@ -140,6 +144,20 @@ Final answer:
self.assertEqual(first, second)
def test_classification_contracts_follow_transcript(self) -> None:
prompt = build_prompt("transcript.txt", "SOURCE_SENTINEL")
transcript_end = prompt.index("--- ENDE TRANSKRIPT ---")
self.assertGreater(prompt.index("Classification contract", transcript_end), transcript_end)
self.assertGreater(
prompt.index("Action Item:", transcript_end),
transcript_end,
)
self.assertGreater(
prompt.index("Open Question:", transcript_end),
transcript_end,
)
def test_build_protocol_groups_extraction_items(self) -> None:
with tempfile.TemporaryDirectory() as directory:
input_dir = Path(directory)