Implement first end-to-end meeting analysis pipeline

This commit is contained in:
2026-07-30 09:15:38 +02:00
parent 46565233d8
commit 07b0d80113
6 changed files with 1294 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""
Build a readable Markdown protocol from chunk extraction JSON files.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
EXTRACTION_CATEGORIES = (
"facts",
"decisions",
"todos",
"questions",
"positions",
"technical",
)
CATEGORY_TITLES = {
"facts": "Facts",
"decisions": "Decisions",
"todos": "Action Items",
"questions": "Open Questions",
"positions": "Positions",
"technical": "Technical",
}
CHUNK_NUMBER_RE = re.compile(r"chunk_(\d+)_")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Build a Markdown meeting protocol from extraction JSON files."
)
parser.add_argument(
"input_dir",
type=Path,
help="Directory containing chunk_XX_extraction.json files",
)
parser.add_argument(
"-o",
"--output",
type=Path,
help="Output Markdown file; default: <input-dir>/meeting_protocol.md",
)
return parser.parse_args()
def chunk_sort_key(path: Path) -> tuple[int, str]:
match = CHUNK_NUMBER_RE.search(path.name)
if not match:
return (sys.maxsize, path.name)
return (int(match.group(1)), path.name)
def load_json(path: Path) -> Any:
return json.loads(path.read_text(encoding="utf-8-sig"))
def find_extraction_files(input_dir: Path) -> list[Path]:
return sorted(
input_dir.glob("chunk_*_extraction.json"),
key=chunk_sort_key,
)
def normalize_items(value: Any) -> list[str]:
if not isinstance(value, list):
return []
items: list[str] = []
for item in value:
if isinstance(item, str):
text = item.strip()
else:
text = json.dumps(item, ensure_ascii=False, sort_keys=True)
if text:
items.append(text)
return items
def collect_extractions(
extraction_files: list[Path],
) -> dict[str, list[str]]:
collected = {category: [] for category in EXTRACTION_CATEGORIES}
for path in extraction_files:
data = load_json(path)
if not isinstance(data, dict):
raise ValueError(
f"Extraction file must contain a JSON object: {path}"
)
for category in EXTRACTION_CATEGORIES:
collected[category].extend(
normalize_items(data.get(category, []))
)
return collected
def find_topic_files(input_dir: Path) -> list[Path]:
return sorted(
input_dir.glob("chunk_*_normalized_windowed_segments.json"),
key=chunk_sort_key,
)
def collect_topics(input_dir: Path) -> list[str]:
topics: list[str] = []
for path in find_topic_files(input_dir):
data = load_json(path)
if not isinstance(data, dict):
continue
source = data.get("source", {})
source_file = (
source.get("file")
if isinstance(source, dict)
else None
)
label = source_file or path.name
segments = data.get("segments", [])
if not isinstance(segments, list):
continue
for segment in segments:
if not isinstance(segment, dict):
continue
segment_id = segment.get("segment_id", "segment")
start_block = segment.get("start_block", "?")
end_block = segment.get("end_block", "?")
topics.append(
f"{label}: {segment_id} (blocks {start_block}-{end_block})"
)
return topics
def render_list(items: list[str]) -> list[str]:
if not items:
return ["- None recorded."]
return [f"- {item}" for item in items]
def build_protocol_markdown(
input_dir: Path,
extraction_files: list[Path],
) -> str:
extractions = collect_extractions(extraction_files)
topics = collect_topics(input_dir)
lines = [
"# Meeting Protocol",
"",
"## Topics",
*render_list(topics),
"",
"## Facts",
*render_list(extractions["facts"]),
"",
"## Decisions",
*render_list(extractions["decisions"]),
"",
"## Open Questions",
*render_list(extractions["questions"]),
"",
"## Action Items",
*render_list(extractions["todos"]),
"",
"## Positions",
*render_list(extractions["positions"]),
"",
"## Technical",
*render_list(extractions["technical"]),
"",
]
return "\n".join(lines)
def build_protocol(input_dir: Path, output_path: Path | None = None) -> Path:
if not input_dir.is_dir():
raise FileNotFoundError(f"Input directory not found: {input_dir}")
extraction_files = find_extraction_files(input_dir)
if not extraction_files:
raise ValueError(
f"No extraction JSON files found in {input_dir}"
)
output = output_path or input_dir / "meeting_protocol.md"
markdown = build_protocol_markdown(input_dir, extraction_files)
output.write_text(markdown, encoding="utf-8")
return output
def main() -> int:
args = parse_args()
try:
extraction_files = find_extraction_files(args.input_dir)
output_path = build_protocol(args.input_dir, args.output)
except (OSError, UnicodeError, ValueError, json.JSONDecodeError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print(f"Input: {args.input_dir}")
print(f"Extraction JSON files: {len(extraction_files)}")
print(f"Protocol: {output_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())