open-navigator / examples /tuscaloosa_decision_analysis.py
jcbowyer's picture
Clean HuggingFace deployment without binary files
61d29fc
#!/usr/bin/env python3
"""
Example: Deep decision analysis for Tuscaloosa meetings.
This script demonstrates how to extract:
- How decisions were framed
- What options were evaluated
- What tradeoffs were discussed
- What rationales were provided
Usage:
python examples/tuscaloosa_decision_analysis.py
"""
import asyncio
import json
from pathlib import Path
from loguru import logger
from extraction.decision_analyzer import DecisionAnalysisAgent, PolicyDecision
async def analyze_tuscaloosa_decisions():
"""
Analyze decision-making in Tuscaloosa meetings.
"""
logger.info("="*60)
logger.info("TUSCALOOSA DECISION ANALYSIS")
logger.info("="*60)
# Initialize decision analyzer
analyzer = DecisionAnalysisAgent()
# Load Tuscaloosa documents
output_dir = Path("output/tuscaloosa")
all_documents = []
if output_dir.exists():
for json_file in output_dir.glob("*.json"):
try:
with open(json_file) as f:
docs = json.load(f)
if isinstance(docs, list):
all_documents.extend(docs)
except Exception as e:
logger.warning(f"Could not load {json_file}: {e}")
if not all_documents:
logger.error("No Tuscaloosa documents found in output/tuscaloosa/")
logger.info("Run the scraper first:")
logger.info(" python main.py scrape --state AL --municipality Tuscaloosa \\")
logger.info(" --url https://tuscaloosaal.suiteonemedia.com --platform suiteonemedia")
return
logger.info(f"Analyzing {len(all_documents)} Tuscaloosa documents")
# Analyze decisions in each document
all_decisions = []
for i, doc in enumerate(all_documents[:10], 1): # Limit to 10 for demo
logger.info(f"\n[{i}/{min(10, len(all_documents))}] Analyzing: {doc.get('title', 'Unknown')[:60]}...")
# Focus on health-related topics
decisions = analyzer.analyze_document(
document=doc,
focus_topics=["health", "dental", "water", "fluoride", "public health"]
)
if decisions:
logger.success(f" Found {len(decisions)} decisions")
all_decisions.extend(decisions)
# Show summary
for decision in decisions:
logger.info(f" • {decision.decision_summary[:80]}...")
logger.info(f" Frame: {decision.primary_frame}")
logger.info(f" Outcome: {decision.outcome}")
else:
logger.info(f" No decisions found")
# Save results
logger.info(f"\n{'='*60}")
logger.info(f"ANALYSIS COMPLETE")
logger.info(f"{'='*60}")
logger.info(f"Total decisions extracted: {len(all_decisions)}")
if all_decisions:
# Save as JSON
json_output = Path("output/tuscaloosa_decisions_analysis.json")
with open(json_output, 'w') as f:
json.dump(
[analyzer._decision_to_dict(d) for d in all_decisions],
f,
indent=2,
default=str
)
logger.success(f"✓ Saved detailed analysis to {json_output}")
# Save as Markdown report
md_output = Path("output/tuscaloosa_decisions_report.md")
with open(md_output, 'w') as f:
f.write(analyzer.export_decision_analysis(all_decisions, format="markdown"))
logger.success(f"✓ Saved markdown report to {md_output}")
# Print sample
logger.info("\n" + "="*60)
logger.info("SAMPLE DECISION ANALYSIS")
logger.info("="*60)
sample = all_decisions[0]
print(f"\n📋 Decision: {sample.decision_summary}")
print(f"\n🎯 Outcome: {sample.outcome}")
print(f"\n🔍 How was this framed?")
print(f" Primary frame: {sample.primary_frame}")
if sample.competing_frames:
print(f" Competing frames: {', '.join(sample.competing_frames)}")
print(f"\n⚖️ What options were considered?")
if sample.options_considered:
for i, opt in enumerate(sample.options_considered, 1):
print(f" {i}. {opt.get('option', 'Unknown')}")
if opt.get('pros'):
print(f" Pros: {', '.join(opt['pros'])}")
if opt.get('cons'):
print(f" Cons: {', '.join(opt['cons'])}")
else:
print(" (No options explicitly listed)")
print(f"\n Chosen: {sample.chosen_option}")
print(f"\n🔄 What tradeoffs were discussed?")
if sample.tradeoffs_discussed:
for tradeoff in sample.tradeoffs_discussed:
print(f" • {tradeoff.get('tradeoff', 'Unknown')}")
print(f" {tradeoff.get('discussion', '')[:100]}...")
else:
print(" (No explicit tradeoffs mentioned)")
print(f"\n💡 Primary rationale:")
print(f" {sample.primary_rationale}")
if sample.supporting_rationales:
print(f"\n Supporting reasons:")
for reason in sample.supporting_rationales:
print(f" • {reason}")
print(f"\n📊 Evidence cited:")
if sample.evidence_cited:
for evidence in sample.evidence_cited:
print(f" • {evidence.get('type', 'Unknown')}: {evidence.get('description', '')}")
else:
print(" (No evidence explicitly cited)")
print(f"\n👥 Stakeholder positions:")
print(f" Supporters: {len(sample.supporters)}")
for supporter in sample.supporters[:3]:
print(f" • {supporter.get('name', 'Unknown')} ({supporter.get('role', '')})")
print(f" Argument: {supporter.get('argument', '')[:80]}...")
print(f" Opponents: {len(sample.opponents)}")
for opponent in sample.opponents[:3]:
print(f" • {opponent.get('name', 'Unknown')} ({opponent.get('role', '')})")
print(f" Concern: {opponent.get('argument', '')[:80]}...")
if sample.vote_result:
print(f"\n🗳️ Vote: {sample.vote_result}")
print(f"\n✅ Confidence: {sample.confidence_score:.1%}")
print("\n" + "="*60)
# Analysis by frame type
logger.info("\nDECISION FRAMING ANALYSIS")
logger.info("="*60)
frame_counts = {}
for decision in all_decisions:
frame = decision.primary_frame or "unspecified"
frame_counts[frame] = frame_counts.get(frame, 0) + 1
print("\nHow Tuscaloosa frames policy decisions:")
for frame, count in sorted(frame_counts.items(), key=lambda x: x[1], reverse=True):
print(f" {count:2d}x {frame}")
# Evidence usage analysis
logger.info("\nEVIDENCE USAGE ANALYSIS")
logger.info("="*60)
evidence_types = {}
for decision in all_decisions:
for evidence in decision.evidence_cited:
ev_type = evidence.get('type', 'unknown')
evidence_types[ev_type] = evidence_types.get(ev_type, 0) + 1
if evidence_types:
print("\nTypes of evidence cited:")
for ev_type, count in sorted(evidence_types.items(), key=lambda x: x[1], reverse=True):
print(f" {count:2d}x {ev_type}")
else:
print("\n(No explicit evidence citations found)")
# Tradeoff analysis
logger.info("\nTRADEOFF ANALYSIS")
logger.info("="*60)
all_tradeoffs = []
for decision in all_decisions:
for tradeoff in decision.tradeoffs_discussed:
all_tradeoffs.append(tradeoff.get('tradeoff', ''))
if all_tradeoffs:
print(f"\nCommon tradeoffs in Tuscaloosa decision-making:")
from collections import Counter
tradeoff_counts = Counter(all_tradeoffs)
for tradeoff, count in tradeoff_counts.most_common(5):
print(f" {count}x {tradeoff}")
else:
print("\n(No explicit tradeoffs documented)")
else:
logger.warning("No decisions were extracted from the documents")
logger.info("This could mean:")
logger.info(" 1. Documents don't contain explicit decisions/votes")
logger.info(" 2. Documents are agendas (not minutes with outcomes)")
logger.info(" 3. More specific meeting minutes are needed")
if __name__ == "__main__":
asyncio.run(analyze_tuscaloosa_decisions())