directory changes and restructuring

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 02:01:41 +09:00
parent eea49f9f8c
commit 236be6c580
598 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# CLAUDE.md
## Overview
Notion-to-presentation workflow. Transforms research content into branded PowerPoint/Figma presentations.
## Quick Start
```bash
# Full automated workflow
python scripts/run_workflow.py --notion-url [URL] --output presentation.pptx
# Step-by-step
python scripts/extract_notion.py [URL] > research.json
python scripts/synthesize_content.py research.json > synthesis.json
python scripts/apply_brand.py synthesis.json --output presentation.pptx
```
## Scripts
| Script | Purpose |
|--------|---------|
| `run_workflow.py` | Orchestrate full pipeline |
| `extract_notion.py` | Extract content from Notion pages |
| `synthesize_content.py` | Analyze and structure content |
| `apply_brand.py` | Apply corporate styling |
## Workflow Pipeline
```
extract_notion.py → synthesize_content.py → apply_brand.py
↓ ↓ ↓
research.json synthesis.json presentation.pptx
```
## Extract Notion
```bash
# Single page
python scripts/extract_notion.py notion://page/abc123 > research.json
# Database query
python scripts/extract_notion.py notion://database/def456 --filter "Status=Done"
```
## Synthesize Content
```bash
# Generate slide structure
python scripts/synthesize_content.py research.json > synthesis.json
# With presentation type
python scripts/synthesize_content.py research.json \
--type executive \
--max-slides 10
```
## Apply Brand
```bash
# PowerPoint output
python scripts/apply_brand.py synthesis.json \
--config assets/brand_config.json \
--output presentation.pptx
# Preview HTML
python scripts/apply_brand.py synthesis.json --preview
```
## Brand Configuration
See `assets/brand_config.json` for:
- Logo placement
- Color scheme
- Font settings
- Slide templates
## Output Formats
- PowerPoint (.pptx)
- Figma (via API)
- HTML preview

View File

@@ -0,0 +1,108 @@
{
"name": "Corporate Brand Guidelines",
"version": "1.0",
"colors": {
"primary": "#1a73e8",
"secondary": "#34a853",
"accent": "#ea4335",
"warning": "#fbbc04",
"dark": "#202124",
"light": "#f8f9fa",
"text": "#3c4043",
"subtext": "#5f6368",
"background": "#ffffff",
"surface": "#f1f3f4"
},
"fonts": {
"heading": "Poppins",
"subheading": "Poppins",
"body": "Lora",
"caption": "Arial",
"fallback": {
"heading": "Arial",
"body": "Georgia"
}
},
"typography": {
"title": {
"size": 44,
"weight": "bold",
"lineHeight": 1.2
},
"heading1": {
"size": 32,
"weight": "bold",
"lineHeight": 1.3
},
"heading2": {
"size": 24,
"weight": "semibold",
"lineHeight": 1.4
},
"body": {
"size": 18,
"weight": "normal",
"lineHeight": 1.6
},
"caption": {
"size": 14,
"weight": "normal",
"lineHeight": 1.5
}
},
"logos": {
"primary": "assets/media/logo.png",
"secondary": "assets/media/logo_white.png",
"icon": "assets/media/icon.png"
},
"spacing": {
"small": 8,
"medium": 16,
"large": 24,
"xlarge": 32
},
"borderRadius": {
"small": 4,
"medium": 8,
"large": 12
},
"shadows": {
"small": "0 1px 3px rgba(0,0,0,0.12)",
"medium": "0 4px 6px rgba(0,0,0,0.16)",
"large": "0 10px 20px rgba(0,0,0,0.19)"
},
"animations": {
"transition": "all 0.3s ease",
"fadeIn": "fadeIn 0.5s ease-in",
"slideIn": "slideIn 0.3s ease-out"
},
"layouts": {
"slideMargins": {
"top": 0.5,
"right": 0.5,
"bottom": 0.5,
"left": 0.5
},
"contentPadding": {
"top": 1.5,
"right": 1,
"bottom": 1,
"left": 1
}
},
"chartColors": [
"#1a73e8",
"#34a853",
"#fbbc04",
"#ea4335",
"#9333ea",
"#06b6d4",
"#f97316",
"#84cc16"
],
"gradients": {
"primary": "linear-gradient(135deg, #1a73e8 0%, #34a853 100%)",
"secondary": "linear-gradient(135deg, #34a853 0%, #fbbc04 100%)",
"accent": "linear-gradient(135deg, #ea4335 0%, #fbbc04 100%)"
}
}

View File

@@ -0,0 +1,269 @@
# Agenda Templates Guide
## Overview
Templates for deriving meeting agendas from research content.
## Agenda Derivation Process
### Step 1: Topic Identification
Extract potential agenda items from research:
1. Main section headers → Primary topics
2. Key findings → Discussion points
3. Data insights → Review items
4. Recommendations → Decision points
5. Open questions → Discussion topics
### Step 2: Priority Assignment
Score each topic by:
- **Urgency**: Time-sensitive? (0-5)
- **Impact**: Business impact? (0-5)
- **Complexity**: Discussion needed? (0-5)
- **Dependencies**: Blocks other work? (0-5)
### Step 3: Time Allocation
Estimate discussion time:
- **Simple update**: 3-5 minutes
- **Data review**: 5-10 minutes
- **Discussion topic**: 10-15 minutes
- **Decision point**: 15-20 minutes
- **Strategic planning**: 20-30 minutes
## Meeting Type Templates
### Executive Review Meeting
```
1. Executive Summary (5 min)
- Key achievements
- Critical metrics
2. Performance Review (10 min)
- KPI dashboard
- Trend analysis
3. Strategic Topics (20 min)
- Market opportunities
- Competitive landscape
4. Decision Points (15 min)
- Resource allocation
- Priority changes
5. Next Steps (5 min)
- Action items
- Follow-up schedule
```
### Project Status Meeting
```
1. Project Overview (3 min)
- Current phase
- Overall timeline
2. Progress Update (10 min)
- Completed milestones
- Current activities
3. Blockers & Risks (15 min)
- Issue discussion
- Mitigation plans
4. Resource Needs (10 min)
- Team requirements
- Budget status
5. Next Sprint Planning (10 min)
- Upcoming milestones
- Task assignments
```
### Strategy Planning Session
```
1. Context Setting (10 min)
- Market analysis
- Current position
2. Opportunity Assessment (20 min)
- Growth areas
- Innovation possibilities
3. Challenge Analysis (15 min)
- Competitive threats
- Internal limitations
4. Strategic Options (20 min)
- Scenario planning
- Trade-off analysis
5. Decision Framework (15 min)
- Evaluation criteria
- Next steps
```
### Customer/Partner Meeting
```
1. Introductions & Objectives (5 min)
- Meeting goals
- Agenda overview
2. Current Status (10 min)
- Project update
- Recent achievements
3. Discussion Topics (25 min)
- Requirements review
- Feedback discussion
4. Next Steps (10 min)
- Action items
- Timeline alignment
5. Q&A (10 min)
- Open questions
- Clarifications
```
### Team Brainstorming Session
```
1. Problem Statement (5 min)
- Challenge definition
- Success criteria
2. Context Sharing (10 min)
- Research findings
- Constraints
3. Idea Generation (25 min)
- Open brainstorming
- Concept exploration
4. Idea Prioritization (15 min)
- Feasibility assessment
- Impact evaluation
5. Action Planning (10 min)
- Next steps
- Owner assignment
```
## Agenda Item Formulation
### From Finding to Agenda Item
**Research Finding**: "Customer satisfaction decreased 15% in Q4"
**Agenda Item**: "Q4 Customer Satisfaction Analysis & Recovery Plan"
**Time**: 15 minutes
**Discussion Points**:
- Root cause analysis
- Customer feedback themes
- Proposed improvements
- Success metrics
### From Data to Discussion
**Data Point**: "Conversion rate: 2.3% (industry avg: 3.5%)"
**Agenda Item**: "Conversion Optimization Strategy"
**Time**: 20 minutes
**Discussion Points**:
- Benchmark analysis
- Bottleneck identification
- Improvement initiatives
- Resource requirements
### From Recommendation to Decision
**Recommendation**: "Expand into European market"
**Agenda Item**: "European Market Expansion Decision"
**Time**: 25 minutes
**Decision Points**:
- Market opportunity validation
- Investment requirements
- Risk assessment
- Go/No-go decision
## Dynamic Agenda Adjustments
### For Limited Time (30 min meeting)
- Focus on critical decisions only
- Combine related topics
- Move updates to pre-read
- Schedule follow-ups for details
### For Extended Discussion (90+ min)
- Add break time (10 min at 45 min mark)
- Include deep-dive sections
- Add buffer time for Q&A
- Include working session time
### For Mixed Audience
- Start with common interests
- Segment specialized topics
- Plan parallel tracks if needed
- End with unified next steps
## Agenda Optimization Tips
### Topic Sequencing
1. **Energy curve**: High-energy topics after warm-up
2. **Decision fatigue**: Critical decisions early
3. **Logical flow**: Dependencies first
4. **Engagement**: Mix presentation and discussion
### Time Management
- **80% rule**: Plan for 80% of available time
- **Buffer zones**: 5-minute buffers between major topics
- **Parking lot**: Designate time for unexpected items
- **Hard stops**: Clear end times for each section
### Participation Planning
- **Speaker rotation**: Distribute presentation duties
- **Discussion leaders**: Assign topic owners
- **Note takers**: Rotate responsibility
- **Timekeeper**: Designated role
## Output Formats
### Detailed Agenda
```markdown
## Meeting: [Title]
**Date**: [Date] | **Time**: [Duration] | **Location**: [Place/Virtual]
### Attendees
- Required: [List]
- Optional: [List]
### Objectives
1. [Primary objective]
2. [Secondary objective]
### Agenda
#### 1. [Topic Name] (10 min) - [Owner]
**Objective**: [What to achieve]
**Materials**: [Pre-read links]
**Discussion Points**:
- [Point 1]
- [Point 2]
**Decision Needed**: [Yes/No - what decision]
[Continue for each topic...]
### Pre-Work
- [ ] Review [document]
- [ ] Prepare [input]
### Success Metrics
- [ ] [Metric 1]
- [ ] [Metric 2]
```
### Quick Agenda
```markdown
## [Meeting Title] - [Date]
1. [Topic 1] - [Duration] - [Owner]
2. [Topic 2] - [Duration] - [Owner]
3. [Topic 3] - [Duration] - [Owner]
4. Next Steps - 5 min
Total: [Total Duration]
```

View File

@@ -0,0 +1,150 @@
# Research Patterns Guide
## Overview
Common patterns for extracting and structuring research content from Notion.
## Content Extraction Patterns
### Hierarchical Extraction
Extract content respecting the document hierarchy:
```
1. Main Topics (H1 headers)
- Key Points (H2 headers)
- Supporting Details (H3 headers)
- Data points
- Examples
```
### Data Point Extraction
Identify and extract quantitative information:
- **Metrics**: Numbers with units (e.g., "25% growth")
- **KPIs**: Key Performance Indicators
- **Comparisons**: Year-over-year, benchmarks
- **Trends**: Directional indicators
### Action Item Detection
Look for action-oriented language:
- Keywords: "recommend", "suggest", "propose", "should"
- Imperatives: "implement", "deploy", "analyze"
- Future tense: "will", "plan to", "intend to"
## Synthesis Patterns
### Theme Clustering
Group related content by theme:
1. **Technical themes**: Technology, infrastructure, systems
2. **Business themes**: Strategy, market, competition
3. **Operational themes**: Process, efficiency, workflow
4. **Customer themes**: Satisfaction, feedback, needs
### Priority Scoring
Rank content by importance:
- **Critical**: Urgent, high-impact, deadline-driven
- **Important**: Strategic, long-term value
- **Relevant**: Supporting information, context
- **Optional**: Nice-to-have, future consideration
### Evidence Mapping
Connect claims to supporting evidence:
```
Claim → Data Point → Source
"Market is growing" → "25% YoY growth" → "Q4 Market Report"
```
## Notion-Specific Patterns
### Database Queries
When extracting from Notion databases:
1. Filter by date range for recent content
2. Sort by priority or importance fields
3. Group by category or project
4. Aggregate metrics across entries
### Page Relationships
Follow page links strategically:
- **Parent pages**: For context and background
- **Child pages**: For detailed information
- **Linked pages**: For related topics
- **Mentioned pages**: For cross-references
### Content Types
Handle different Notion content types:
- **Text blocks**: Extract as-is
- **Toggle lists**: Expand and include all content
- **Tables**: Convert to structured data
- **Embeds**: Note source and type
- **Code blocks**: Preserve formatting
## Quality Checks
### Completeness
Ensure extraction captures:
- [ ] All main sections
- [ ] Key data points
- [ ] Action items
- [ ] Recommendations
- [ ] Links and references
### Accuracy
Verify extracted content:
- [ ] Numbers match source
- [ ] Quotes are exact
- [ ] Context is preserved
- [ ] Relationships are maintained
### Relevance
Filter content by:
- [ ] Topic relevance
- [ ] Time relevance (recent vs outdated)
- [ ] Audience relevance
- [ ] Objective relevance
## Advanced Patterns
### Sentiment Analysis
Identify tone and sentiment:
- **Positive indicators**: Success, achievement, growth
- **Negative indicators**: Challenge, risk, decline
- **Neutral indicators**: Stable, maintained, unchanged
### Temporal Analysis
Track changes over time:
- **Historical context**: Past performance
- **Current state**: Present situation
- **Future projections**: Plans and forecasts
### Cross-Reference Analysis
Connect information across sources:
- **Confirmatory**: Multiple sources agree
- **Contradictory**: Sources conflict
- **Complementary**: Sources add different perspectives
## Output Formatting
### Executive Summary Pattern
```
1. High-level conclusion (1-2 sentences)
2. Key findings (3-5 bullets)
3. Critical metrics (top 3-5)
4. Immediate actions (top 3)
```
### Detailed Report Pattern
```
1. Background and context
2. Methodology
3. Findings by category
4. Data analysis
5. Conclusions
6. Recommendations
7. Appendices
```
### Quick Brief Pattern
```
1. What: Core message
2. Why: Importance/impact
3. How: Key data/evidence
4. When: Timeline/urgency
5. Who: Stakeholders/owners
```

View File

@@ -0,0 +1,327 @@
# Slide Layouts Guide
## Overview
Layout patterns and best practices for different slide types.
## Layout Principles
### Visual Hierarchy
1. **Primary**: Title/headline (largest, boldest)
2. **Secondary**: Key points (medium, clear)
3. **Tertiary**: Supporting details (smaller, lighter)
4. **Quaternary**: Metadata (smallest, subtle)
### Balance & Alignment
- **Rule of thirds**: Divide slide into 9 sections
- **White space**: 30-40% minimum
- **Alignment grid**: Consistent margins and gutters
- **Visual weight**: Balance text, images, and data
## Core Slide Types
### 1. Title Slide
```
┌─────────────────────────────────┐
│ │
│ │
│ [Company Logo] │
│ │
│ PRESENTATION TITLE │
│ Subtitle Text │
│ │
│ Date | Author │
│ │
└─────────────────────────────────┘
```
**Elements**:
- Title: 44-48pt, bold, centered
- Subtitle: 24-28pt, regular, centered
- Metadata: 14-16pt, light, centered
- Background: Gradient or image with overlay
### 2. Section Divider
```
┌─────────────────────────────────┐
│ │
│ │
│ │
│ SECTION TITLE │
│ Brief Context │
│ │
│ │
│ │
└─────────────────────────────────┘
```
**Elements**:
- Section title: 36-40pt, bold
- Context: 20-24pt, regular
- Background: Brand color or pattern
- Number indicator (optional): Large, subtle
### 3. Content Slide - Text Only
```
┌─────────────────────────────────┐
│ SLIDE TITLE │
├─────────────────────────────────┤
│ │
│ • Key point one with detail │
│ Supporting information │
│ │
│ • Key point two with detail │
│ Supporting information │
│ │
│ • Key point three with detail │
│ Supporting information │
│ │
└─────────────────────────────────┘
```
**Elements**:
- Title: 32pt, bold, left-aligned
- Bullets: 18-20pt, regular
- Sub-bullets: 16-18pt, light
- Line spacing: 1.5x
### 4. Content Slide - Text + Image
```
┌─────────────────────────────────┐
│ SLIDE TITLE │
├────────────────┬────────────────┤
│ │ │
│ • Point one │ │
│ │ [IMAGE/ │
│ • Point two │ CHART] │
│ │ │
│ • Point three │ │
│ │ │
└────────────────┴────────────────┘
```
**Proportions**:
- Text: 50-60% width
- Visual: 40-50% width
- Padding: 0.5" between columns
### 5. Data Visualization
```
┌─────────────────────────────────┐
│ DATA INSIGHT TITLE │
├─────────────────────────────────┤
│ ┌──────────┐ │
│ │ │ │
│ │ CHART/ │ │
│ │ GRAPH │ │
│ │ │ │
│ └──────────┘ │
│ │
│ Key Insight: [Callout text] │
└─────────────────────────────────┘
```
**Elements**:
- Chart: 60-70% of slide area
- Title: Descriptive, not just "Sales Data"
- Callout: Highlight key finding
- Legend: Only if necessary
### 6. Comparison Slide
```
┌─────────────────────────────────┐
│ COMPARISON TITLE │
├────────────────┬────────────────┤
│ Option A │ Option B │
├────────────────┼────────────────┤
│ │ │
│ ✓ Advantage 1 │ ✓ Advantage 1 │
│ ✓ Advantage 2 │ ✓ Advantage 2 │
│ ✗ Drawback 1 │ ✗ Drawback 1 │
│ │ │
│ Metric: 85% │ Metric: 72% │
│ │ │
└────────────────┴────────────────┘
```
**Layout**:
- Equal column widths
- Consistent formatting
- Visual indicators (✓/✗, +/-)
- Highlight winner (if applicable)
### 7. Process/Timeline
```
┌─────────────────────────────────┐
│ PROCESS TITLE │
├─────────────────────────────────┤
│ │
│ [1]────►[2]────►[3]────►[4] │
│ Start Step Step End │
│ One Two │
│ │
│ Description of the process │
│ │
└─────────────────────────────────┘
```
**Variations**:
- Horizontal timeline
- Vertical timeline
- Circular process
- Chevron diagram
### 8. Quote/Testimonial
```
┌─────────────────────────────────┐
│ │
│ │
│ "Impactful quote │
│ that supports │
│ your message" │
│ │
│ - Attribution │
│ Title/Company │
│ │
└─────────────────────────────────┘
```
**Styling**:
- Quote: 24-28pt, italic or serif
- Attribution: 16-18pt, regular
- Quotation marks: Large, subtle
- Background: Minimal, maybe texture
### 9. Image-Focused
```
┌─────────────────────────────────┐
│ │
│ │
│ [FULL BLEED IMAGE] │
│ │
│ │
├─────────────────────────────────┤
│ Caption or Context Text │
└─────────────────────────────────┘
```
**Best Practices**:
- High-resolution images only
- Text overlay needs contrast
- Consider semi-transparent overlay
- Keep text minimal
### 10. Call-to-Action
```
┌─────────────────────────────────┐
│ │
│ │
│ CLEAR ACTION ITEM │
│ │
│ [ BUTTON/ACTION ] │
│ │
│ Supporting context │
│ │
└─────────────────────────────────┘
```
**Elements**:
- Action: 32-36pt, bold
- Button: Contrasting color
- Context: 18-20pt, regular
- Contact info (if applicable)
## Responsive Layouts
### Mobile-First Considerations
- Larger text (minimum 14pt)
- Single column layouts
- Touch-friendly spacing
- Simplified graphics
### Aspect Ratios
- **16:9** (Standard): 1920×1080px
- **4:3** (Traditional): 1024×768px
- **16:10** (Modern): 1920×1200px
- **1:1** (Social): 1080×1080px
## Color Usage
### Background Options
1. **White**: Maximum readability
2. **Light gray**: Reduces glare
3. **Dark**: For high contrast
4. **Gradient**: Adds depth
5. **Pattern**: Subtle texture
### Text Colors
- **Primary text**: High contrast (90%+)
- **Secondary text**: Medium contrast (70%+)
- **Accent text**: Brand color
- **Disabled text**: Low contrast (40%)
## Typography Guidelines
### Font Pairing
```
Heading Font + Body Font
- Poppins + Lora
- Arial + Georgia
- Helvetica + Times
- Roboto + Merriweather
```
### Size Hierarchy
```
Title: 44-48pt
Subtitle: 32-36pt
Heading: 28-32pt
Subheading: 20-24pt
Body: 16-20pt
Caption: 12-14pt
```
### Line Spacing
- Titles: 1.0-1.2x
- Body text: 1.5-1.6x
- Bullets: 1.4-1.5x
- Captions: 1.2-1.3x
## Animation & Transitions
### Entrance Animations
- **Fade in**: Universal, professional
- **Slide in**: Directional emphasis
- **Grow**: Importance emphasis
- **Appear**: Simple, no distraction
### Transition Timing
- Fast: 0.3s (between related slides)
- Medium: 0.5s (section transitions)
- Slow: 1.0s (major transitions)
### Build Sequences
1. Title appears
2. Main content fades in
3. Supporting elements slide in
4. Call-to-action grows
## Accessibility Considerations
### Color Contrast
- WCAG AA: 4.5:1 for normal text
- WCAG AA: 3:1 for large text
- WCAG AAA: 7:1 for normal text
### Font Sizes
- Minimum: 14pt for body text
- Minimum: 18pt for important text
- Maximum: 54pt for titles
### Alt Text
- Describe all images
- Explain all charts
- Summarize complex diagrams
## Quality Checklist
### Before Finalizing
- [ ] Consistent alignment across slides
- [ ] Proper contrast ratios
- [ ] No orphaned bullets
- [ ] Images are high resolution
- [ ] Data is clearly labeled
- [ ] Slide numbers present
- [ ] Brand elements applied
- [ ] Spell check completed
- [ ] Animations tested
- [ ] Export quality verified

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
Apply brand styling to PowerPoint presentations
Customizes colors, fonts, and visual elements based on brand guidelines
"""
import json
import argparse
from pathlib import Path
# Note: In production, install python-pptx via pip
try:
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.util import Pt
except ImportError:
print("📦 Please install python-pptx: pip install python-pptx")
exit(1)
class BrandStyler:
"""Apply brand guidelines to PowerPoint presentations"""
def __init__(self, brand_config_path: str):
"""Load brand configuration"""
with open(brand_config_path, 'r') as f:
self.brand_config = json.load(f)
self.colors = self.brand_config.get('colors', {})
self.fonts = self.brand_config.get('fonts', {})
self.logos = self.brand_config.get('logos', {})
def apply_to_presentation(self, pptx_path: str, output_path: str = None):
"""Apply brand styling to PowerPoint file"""
if not output_path:
output_path = pptx_path.replace('.pptx', '_branded.pptx')
print(f"🎨 Applying brand styles to: {pptx_path}")
# Load presentation
prs = Presentation(pptx_path)
# Apply styles to each slide
for slide_num, slide in enumerate(prs.slides, 1):
self.style_slide(slide, slide_num)
# Save branded version
prs.save(output_path)
print(f"✅ Branded presentation saved: {output_path}")
return output_path
def style_slide(self, slide, slide_num: int):
"""Apply brand styling to individual slide"""
# Style text elements
for shape in slide.shapes:
if shape.has_text_frame:
self.style_text_frame(shape.text_frame, slide_num)
# Style tables
if shape.has_table:
self.style_table(shape.table)
def style_text_frame(self, text_frame, slide_num: int):
"""Apply brand fonts and colors to text"""
for paragraph in text_frame.paragraphs:
for run in paragraph.runs:
# Apply font
if slide_num == 1: # Title slide
run.font.name = self.fonts.get('heading', 'Arial')
if paragraph.level == 0:
run.font.size = Pt(44)
else:
run.font.name = self.fonts.get('body', 'Arial')
# Apply colors based on context
if slide_num == 1: # Title slide - white text
run.font.color.rgb = RGBColor(255, 255, 255)
elif paragraph.level == 0: # Headings
color_hex = self.colors.get('primary', '#1a73e8').lstrip('#')
run.font.color.rgb = RGBColor(
int(color_hex[0:2], 16),
int(color_hex[2:4], 16),
int(color_hex[4:6], 16)
)
else: # Body text
color_hex = self.colors.get('text', '#3c4043').lstrip('#')
run.font.color.rgb = RGBColor(
int(color_hex[0:2], 16),
int(color_hex[2:4], 16),
int(color_hex[4:6], 16)
)
def style_table(self, table):
"""Apply brand styling to tables"""
# Style header row
if len(table.rows) > 0:
for cell in table.rows[0].cells:
# Apply header background color
if hasattr(cell, 'fill'):
color_hex = self.colors.get('secondary', '#34a853').lstrip('#')
# Note: python-pptx table cell fill is complex
# This is simplified for example
pass
# Style table text
for row in table.rows:
for cell in row.cells:
if cell.text_frame:
for paragraph in cell.text_frame.paragraphs:
for run in paragraph.runs:
run.font.name = self.fonts.get('body', 'Arial')
run.font.size = Pt(14)
def add_logo(self, slide, position='top-right'):
"""Add company logo to slide"""
logo_path = self.logos.get('primary')
if not logo_path or not Path(logo_path).exists():
return
# Position mappings
positions = {
'top-right': {'left': 8.5, 'top': 0.25, 'width': 1.5},
'bottom-right': {'left': 8.5, 'top': 4.75, 'width': 1.5},
'top-left': {'left': 0.25, 'top': 0.25, 'width': 1.5}
}
pos = positions.get(position, positions['top-right'])
# Add logo image
# Note: Requires python-pptx inches conversion
# slide.shapes.add_picture(logo_path, left, top, width)
def main():
parser = argparse.ArgumentParser(
description="Apply brand styling to PowerPoint presentations"
)
parser.add_argument(
"presentation",
help="Path to PowerPoint presentation"
)
parser.add_argument(
"--config",
default="assets/brand_config.json",
help="Path to brand configuration JSON"
)
parser.add_argument(
"--output",
help="Output path for branded presentation"
)
args = parser.parse_args()
# Apply branding
styler = BrandStyler(args.config)
output_path = styler.apply_to_presentation(
args.presentation,
args.output
)
print(f"✨ Brand styling complete: {output_path}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""
Extract research content from Notion pages and databases
Outputs structured JSON for downstream processing
"""
import json
import argparse
from typing import Dict, List, Any
from datetime import datetime
def extract_notion_content(notion_url: str) -> Dict[str, Any]:
"""
Extract and structure content from Notion
This function would integrate with Notion MCP tools:
- notion-search for finding related pages
- notion-fetch for getting full content
Args:
notion_url: URL of Notion page or database
Returns:
Structured research data
"""
# Parse Notion URL to get page/database ID
page_id = parse_notion_url(notion_url)
# This would use actual Notion MCP tools in production
# Simulating the structure for now
extracted_data = {
"source": {
"url": notion_url,
"id": page_id,
"type": "page", # or "database"
"extracted_at": datetime.now().isoformat()
},
"metadata": {
"title": "Q4 Research Summary",
"last_edited": "2024-12-15T10:30:00Z",
"created_by": "user@company.com",
"tags": ["research", "Q4", "strategy"]
},
"content": {
"sections": [
{
"title": "Executive Summary",
"content": "Key findings from Q4 research indicate...",
"level": 1,
"data_points": [
{"metric": "Growth Rate", "value": "25%"},
{"metric": "User Satisfaction", "value": "4.5/5"}
]
},
{
"title": "Market Analysis",
"content": "The market landscape shows...",
"level": 1,
"subsections": [
{
"title": "Competitive Landscape",
"content": "Our position relative to competitors...",
"level": 2
},
{
"title": "Growth Opportunities",
"content": "Identified opportunities include...",
"level": 2
}
]
},
{
"title": "Customer Insights",
"content": "Customer feedback reveals...",
"level": 1,
"data_points": [
{"metric": "NPS Score", "value": "72"},
{"metric": "Retention Rate", "value": "89%"}
]
},
{
"title": "Recommendations",
"content": "Based on the research, we recommend...",
"level": 1,
"action_items": [
"Expand into new market segments",
"Enhance product features based on feedback",
"Increase investment in customer success"
]
}
]
},
"linked_pages": [
{
"title": "Detailed Customer Survey Results",
"url": "notion://page/survey-results-id",
"relevance": "high"
},
{
"title": "Competitor Analysis Deep Dive",
"url": "notion://page/competitor-analysis-id",
"relevance": "medium"
}
],
"attachments": [
{
"type": "spreadsheet",
"title": "Q4 Metrics Dashboard",
"url": "notion://attachment/metrics-id"
}
]
}
return extracted_data
def parse_notion_url(url: str) -> str:
"""Extract page/database ID from Notion URL"""
# Simplified URL parsing
if "notion.so/" in url or "notion://" in url:
parts = url.split("/")
return parts[-1].split("?")[0]
return url
def fetch_linked_content(linked_pages: List[Dict], depth: int = 1) -> List[Dict]:
"""
Recursively fetch linked page content
Args:
linked_pages: List of linked page references
depth: How deep to follow links
Returns:
Expanded content from linked pages
"""
if depth <= 0:
return []
expanded_content = []
for page in linked_pages:
if page.get("relevance") in ["high", "medium"]:
# Would fetch actual content here
expanded_content.append({
"source": page["url"],
"title": page["title"],
"content": f"Content from {page['title']}..."
})
return expanded_content
def main():
parser = argparse.ArgumentParser(
description="Extract research content from Notion"
)
parser.add_argument(
"notion_url",
help="URL of Notion page or database"
)
parser.add_argument(
"--output",
default="research.json",
help="Output JSON file (default: research.json)"
)
parser.add_argument(
"--include-linked",
action="store_true",
help="Include content from linked pages"
)
parser.add_argument(
"--depth",
type=int,
default=1,
help="Link following depth (default: 1)"
)
args = parser.parse_args()
print(f"📚 Extracting content from: {args.notion_url}")
# Extract main content
research_data = extract_notion_content(args.notion_url)
# Optionally fetch linked content
if args.include_linked and research_data.get("linked_pages"):
print("📎 Fetching linked pages...")
linked_content = fetch_linked_content(
research_data["linked_pages"],
args.depth
)
research_data["linked_content"] = linked_content
# Save to JSON
with open(args.output, 'w', encoding='utf-8') as f:
json.dump(research_data, f, indent=2, ensure_ascii=False)
print(f"✅ Research data saved to: {args.output}")
print(f"📊 Extracted {len(research_data['content']['sections'])} sections")
if research_data.get("linked_pages"):
print(f"🔗 Found {len(research_data['linked_pages'])} linked pages")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,473 @@
#!/usr/bin/env node
/**
* Generate PowerPoint presentation from synthesis data
* Uses pptxgenjs library to create professional presentations
*/
const fs = require('fs');
const path = require('path');
// Note: In production, install via npm install pptxgenjs
// For this example, we'll show the structure
let PptxGenJS;
try {
PptxGenJS = require('pptxgenjs');
} catch (e) {
console.log('📦 Installing pptxgenjs... Run: npm install pptxgenjs');
process.exit(1);
}
class PresentationGenerator {
constructor(synthesisData) {
this.synthesis = synthesisData;
this.pptx = new PptxGenJS();
this.setupPresentation();
this.defineLayouts();
}
setupPresentation() {
// Set presentation properties
this.pptx.layout = 'LAYOUT_16x9';
this.pptx.author = this.synthesis.metadata.author || 'Research Team';
this.pptx.company = 'Generated by Research-to-Presentation';
this.pptx.title = this.synthesis.metadata.title || 'Research Presentation';
// Define theme colors
this.colors = {
primary: '#1a73e8',
secondary: '#34a853',
accent: '#ea4335',
dark: '#202124',
light: '#f8f9fa',
text: '#3c4043',
subtext: '#5f6368'
};
// Define font styles
this.fonts = {
title: { face: 'Arial', size: 44, bold: true },
heading: { face: 'Arial', size: 32, bold: true },
subheading: { face: 'Arial', size: 24, bold: false },
body: { face: 'Arial', size: 18 },
small: { face: 'Arial', size: 14 }
};
}
defineLayouts() {
// Define master slides/layouts
this.pptx.defineSlideMaster({
title: 'CUSTOM_LAYOUT',
background: { color: this.colors.light },
objects: [
// Footer with slide number
{
text: {
text: '[[slideNumber]]',
options: {
x: 8.5,
y: 5,
w: 1,
h: 0.4,
fontSize: 12,
color: this.colors.subtext
}
}
}
]
});
}
generate() {
console.log('🎨 Generating slides...');
this.synthesis.slide_plan.forEach(slide => {
switch(slide.type) {
case 'title':
this.createTitleSlide(slide);
break;
case 'executive_summary':
this.createExecutiveSummarySlide(slide);
break;
case 'agenda':
this.createAgendaSlide(slide);
break;
case 'content':
this.createContentSlide(slide);
break;
case 'data_visualization':
this.createDataSlide(slide);
break;
case 'recommendations':
this.createRecommendationsSlide(slide);
break;
case 'closing':
this.createClosingSlide(slide);
break;
default:
this.createContentSlide(slide);
}
});
return this.pptx;
}
createTitleSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Add background gradient
slide.background = {
fill: {
type: 'gradient',
colors: [
{ color: this.colors.primary, position: 0 },
{ color: this.colors.secondary, position: 100 }
]
}
};
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 2,
w: 9,
h: 1.5,
fontSize: 44,
bold: true,
color: 'FFFFFF',
align: 'center'
});
// Subtitle
if (slideData.subtitle) {
slide.addText(slideData.subtitle, {
x: 0.5,
y: 3.5,
w: 9,
h: 0.75,
fontSize: 24,
color: 'FFFFFF',
align: 'center'
});
}
// Speaker notes
if (slideData.speaker_notes) {
slide.addNotes(slideData.speaker_notes);
}
}
createExecutiveSummarySlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.75,
...this.fonts.heading,
color: this.colors.primary
});
// Summary content
slide.addText(slideData.content, {
x: 0.5,
y: 1.5,
w: 9,
h: 1.5,
...this.fonts.body,
color: this.colors.text
});
// Key points
if (slideData.key_points && slideData.key_points.length > 0) {
const bulletPoints = slideData.key_points.map(point => ({
text: point,
options: { bullet: true }
}));
slide.addText(bulletPoints, {
x: 0.5,
y: 3.25,
w: 9,
h: 2,
...this.fonts.body,
color: this.colors.text,
bullet: { type: 'circle' }
});
}
slide.addNotes(slideData.speaker_notes || '');
}
createAgendaSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.75,
...this.fonts.heading,
color: this.colors.primary
});
// Agenda items
const agendaItems = slideData.items.map((item, index) => ({
text: `${index + 1}. ${item}`,
options: {
fontSize: 20,
bullet: false,
indentLevel: 0
}
}));
slide.addText(agendaItems, {
x: 1,
y: 1.5,
w: 8,
h: 3.5,
color: this.colors.text,
lineSpacing: 32
});
// Duration footer
if (slideData.total_duration) {
slide.addText(`Total Duration: ${slideData.total_duration} minutes`, {
x: 0.5,
y: 5,
w: 4,
h: 0.4,
...this.fonts.small,
color: this.colors.subtext
});
}
slide.addNotes(slideData.speaker_notes || '');
}
createContentSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.75,
...this.fonts.heading,
color: this.colors.primary
});
// Determine layout based on content
const hasData = slideData.data && slideData.data.length > 0;
const contentWidth = hasData ? 5 : 9;
// Bullet points
if (slideData.bullets && slideData.bullets.length > 0) {
const bulletPoints = slideData.bullets.map(point => ({
text: point,
options: { bullet: true }
}));
slide.addText(bulletPoints, {
x: 0.5,
y: 1.5,
w: contentWidth,
h: 3.5,
...this.fonts.body,
color: this.colors.text,
bullet: { type: 'circle' },
lineSpacing: 24
});
}
// Data table if present
if (hasData) {
const tableData = [
['Metric', 'Value'],
...slideData.data.map(d => [d.metric, d.value])
];
slide.addTable(tableData, {
x: 6,
y: 1.5,
w: 3.5,
h: 2,
fontSize: 14,
border: { pt: 1, color: this.colors.secondary },
fill: { color: this.colors.light }
});
}
slide.addNotes(slideData.speaker_notes || '');
}
createDataSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.75,
...this.fonts.heading,
color: this.colors.primary
});
// Create data table
if (slideData.data_points && slideData.data_points.length > 0) {
const tableData = [
['Source', 'Metric', 'Value'],
...slideData.data_points.map(dp => [
dp.source || '',
dp.metric,
dp.value
])
];
slide.addTable(tableData, {
x: 0.5,
y: 1.5,
w: 9,
h: 3.5,
fontSize: 16,
border: { pt: 1, color: this.colors.secondary },
fill: { color: this.colors.light },
rowH: 0.5
});
}
slide.addNotes(slideData.speaker_notes || '');
}
createRecommendationsSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Title
slide.addText(slideData.title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.75,
...this.fonts.heading,
color: this.colors.primary
});
// Recommendations as numbered list
const recommendations = slideData.items.map((item, index) => ({
text: `${index + 1}. ${item}`,
options: {
fontSize: 20,
bullet: false
}
}));
slide.addText(recommendations, {
x: 0.5,
y: 1.5,
w: 9,
h: 3.5,
color: this.colors.text,
lineSpacing: 28
});
slide.addNotes(slideData.speaker_notes || '');
}
createClosingSlide(slideData) {
const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' });
// Gradient background
slide.background = {
fill: {
type: 'gradient',
colors: [
{ color: this.colors.secondary, position: 0 },
{ color: this.colors.primary, position: 100 }
]
}
};
// Thank you text
slide.addText(slideData.title, {
x: 0.5,
y: 2,
w: 9,
h: 1,
fontSize: 48,
bold: true,
color: 'FFFFFF',
align: 'center'
});
// Subtitle
if (slideData.subtitle) {
slide.addText(slideData.subtitle, {
x: 0.5,
y: 3.5,
w: 9,
h: 0.75,
fontSize: 28,
color: 'FFFFFF',
align: 'center'
});
}
slide.addNotes(slideData.speaker_notes || '');
}
async save(outputPath) {
try {
await this.pptx.writeFile({ fileName: outputPath });
console.log(`✅ Presentation saved: ${outputPath}`);
} catch (error) {
console.error(`❌ Error saving presentation: ${error}`);
throw error;
}
}
}
// Main execution
async function main() {
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('Usage: node generate_pptx.js <synthesis.json> <output.pptx>');
process.exit(1);
}
const synthesisFile = args[0];
const outputFile = args[1];
console.log(`📊 Loading synthesis from: ${synthesisFile}`);
try {
// Load synthesis data
const synthesisData = JSON.parse(
fs.readFileSync(synthesisFile, 'utf8')
);
// Generate presentation
const generator = new PresentationGenerator(synthesisData);
const presentation = generator.generate();
// Save to file
await generator.save(outputFile);
console.log(`📈 Created ${synthesisData.slide_plan.length} slides`);
} catch (error) {
console.error(`❌ Error: ${error.message}`);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = { PresentationGenerator };

View File

@@ -0,0 +1,6 @@
# 32-ourdigital-presentation dependencies
notion-client>=2.0.0
python-pptx>=0.6.21
jinja2>=3.1.0
pyyaml>=6.0.0
python-dotenv>=1.0.0

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""
Main workflow orchestrator for Research to Presentation
Coordinates the complete pipeline from Notion to final presentation
"""
import json
import argparse
import subprocess
import sys
from pathlib import Path
from datetime import datetime
def run_workflow(notion_url, output_format='pptx', brand_config=None):
"""
Execute the complete research to presentation workflow
Args:
notion_url: URL of Notion page or database
output_format: 'pptx' or 'figma'
brand_config: Path to brand configuration JSON
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
work_dir = Path(f"/tmp/r2p_{timestamp}")
work_dir.mkdir(parents=True, exist_ok=True)
print("🚀 Starting Research to Presentation Workflow")
print(f"📍 Source: {notion_url}")
print(f"📄 Output Format: {output_format}")
print("-" * 50)
try:
# Step 1: Extract Notion content
print("\n📚 Step 1: Extracting Notion research...")
research_file = work_dir / "research.json"
subprocess.run([
sys.executable, "scripts/extract_notion.py",
notion_url,
"--output", str(research_file)
], check=True)
print(f"✅ Research extracted to {research_file}")
# Step 2: Synthesize content
print("\n🔍 Step 2: Synthesizing content and extracting topics...")
synthesis_file = work_dir / "synthesis.json"
subprocess.run([
sys.executable, "scripts/synthesize_content.py",
str(research_file),
"--output", str(synthesis_file)
], check=True)
print(f"✅ Synthesis completed: {synthesis_file}")
# Step 3: Generate presentation
print(f"\n🎨 Step 3: Generating {output_format.upper()} presentation...")
if output_format == 'pptx':
output_file = f"presentation_{timestamp}.pptx"
subprocess.run([
"node", "scripts/generate_pptx.js",
str(synthesis_file),
output_file
], check=True)
print(f"✅ PowerPoint created: {output_file}")
elif output_format == 'figma':
output_file = f"figma_slides_{timestamp}.json"
subprocess.run([
"node", "scripts/export_to_figma.js",
str(synthesis_file),
"--output", output_file
], check=True)
print(f"✅ Figma slides exported: {output_file}")
# Step 4: Apply branding (if config provided)
if brand_config and output_format == 'pptx':
print("\n🎨 Step 4: Applying brand styles...")
subprocess.run([
sys.executable, "scripts/apply_brand.py",
output_file,
"--config", brand_config
], check=True)
print("✅ Brand styling applied")
print("\n" + "=" * 50)
print(f"🎉 Workflow completed successfully!")
print(f"📁 Output: {output_file}")
print(f"🗂️ Work files: {work_dir}")
return output_file
except subprocess.CalledProcessError as e:
print(f"\n❌ Error in workflow: {e}")
print(f"💡 Check work directory for debugging: {work_dir}")
raise
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
raise
def main():
parser = argparse.ArgumentParser(
description="Transform Notion research into presentations"
)
parser.add_argument(
"--notion-url",
required=True,
help="URL of Notion page or database"
)
parser.add_argument(
"--output-format",
choices=['pptx', 'figma'],
default='pptx',
help="Output format (default: pptx)"
)
parser.add_argument(
"--brand-config",
help="Path to brand configuration JSON"
)
parser.add_argument(
"--preview",
action='store_true',
help="Generate HTML preview only"
)
args = parser.parse_args()
if args.preview:
print("🔍 Preview mode - generating HTML only")
# Preview implementation here
else:
run_workflow(
args.notion_url,
args.output_format,
args.brand_config
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,375 @@
#!/usr/bin/env python3
"""
Synthesize research content and extract presentation topics
Analyzes research data to identify key themes, agenda items, and slide structure
"""
import json
import argparse
from typing import Dict, List, Any
from collections import Counter
import re
class ContentSynthesizer:
"""Analyzes research and generates presentation structure"""
def __init__(self, research_data: Dict):
self.research_data = research_data
self.synthesis = {
"metadata": {},
"executive_summary": "",
"key_topics": [],
"agenda_items": [],
"supporting_data": [],
"recommendations": [],
"slide_plan": []
}
def synthesize(self) -> Dict[str, Any]:
"""Execute complete synthesis pipeline"""
self.extract_metadata()
self.generate_executive_summary()
self.extract_key_topics()
self.derive_agenda_items()
self.collect_supporting_data()
self.extract_recommendations()
self.create_slide_plan()
return self.synthesis
def extract_metadata(self):
"""Extract presentation metadata from research"""
source = self.research_data.get("metadata", {})
self.synthesis["metadata"] = {
"title": source.get("title", "Research Presentation"),
"date": source.get("last_edited", ""),
"author": source.get("created_by", ""),
"tags": source.get("tags", [])
}
def generate_executive_summary(self):
"""Create concise executive summary"""
sections = self.research_data.get("content", {}).get("sections", [])
# Find executive summary section or generate from content
for section in sections:
if "executive" in section.get("title", "").lower():
self.synthesis["executive_summary"] = section.get("content", "")
return
# Generate summary from first paragraph of each section
summary_parts = []
for section in sections[:3]: # First 3 sections
content = section.get("content", "")
first_sentence = content.split(".")[0] + "."
summary_parts.append(first_sentence)
self.synthesis["executive_summary"] = " ".join(summary_parts)
def extract_key_topics(self):
"""Identify main topics from research"""
sections = self.research_data.get("content", {}).get("sections", [])
for section in sections:
topic = {
"title": section.get("title", ""),
"importance": self.calculate_importance(section),
"key_points": self.extract_key_points(section),
"data_points": section.get("data_points", []),
"speaker_notes": self.generate_speaker_notes(section)
}
# Include subsections as subtopics
if section.get("subsections"):
topic["subtopics"] = [
{
"title": sub.get("title", ""),
"key_points": self.extract_key_points(sub)
}
for sub in section["subsections"]
]
self.synthesis["key_topics"].append(topic)
# Sort by importance
self.synthesis["key_topics"].sort(
key=lambda x: x["importance"],
reverse=True
)
def calculate_importance(self, section: Dict) -> float:
"""Calculate topic importance score"""
score = 1.0
# Higher level sections are more important
if section.get("level") == 1:
score += 0.5
# Sections with data are more important
if section.get("data_points"):
score += 0.3 * len(section["data_points"])
# Sections with action items are important
if section.get("action_items"):
score += 0.4
# Length indicates detail
content_length = len(section.get("content", ""))
if content_length > 500:
score += 0.2
return score
def extract_key_points(self, section: Dict) -> List[str]:
"""Extract bullet points from section content"""
content = section.get("content", "")
# Extract sentences that look like key points
key_points = []
sentences = content.split(".")
for sentence in sentences:
sentence = sentence.strip()
# Look for important indicators
if any(indicator in sentence.lower() for indicator in
["key", "important", "significant", "critical", "major"]):
key_points.append(sentence + ".")
# Or if it's short and punchy
elif 10 < len(sentence) < 100:
key_points.append(sentence + ".")
# Add action items if present
if section.get("action_items"):
key_points.extend(section["action_items"])
return key_points[:5] # Limit to 5 points per slide
def generate_speaker_notes(self, section: Dict) -> str:
"""Generate speaker notes for section"""
content = section.get("content", "")
# Take first 2-3 sentences as speaker notes
sentences = content.split(".")[:3]
notes = ". ".join(sentences).strip()
# Add context about data if present
if section.get("data_points"):
notes += " Key metrics to highlight: "
metrics = [f"{dp['metric']}: {dp['value']}"
for dp in section["data_points"]]
notes += ", ".join(metrics)
return notes
def derive_agenda_items(self):
"""Generate meeting agenda from topics"""
# Create agenda from top topics
for i, topic in enumerate(self.synthesis["key_topics"][:5]):
agenda_item = {
"order": i + 1,
"title": topic["title"],
"duration": self.estimate_duration(topic),
"discussion_points": topic["key_points"][:3],
"decision_required": self.needs_decision(topic)
}
self.synthesis["agenda_items"].append(agenda_item)
def estimate_duration(self, topic: Dict) -> int:
"""Estimate discussion time in minutes"""
base_time = 5
# Add time for subtopics
if topic.get("subtopics"):
base_time += 2 * len(topic["subtopics"])
# Add time for data discussion
if topic.get("data_points"):
base_time += 3
# Cap at 15 minutes per topic
return min(base_time, 15)
def needs_decision(self, topic: Dict) -> bool:
"""Check if topic requires a decision"""
indicators = ["recommend", "decide", "choose", "select", "approve"]
content = " ".join(topic.get("key_points", []))
return any(ind in content.lower() for ind in indicators)
def collect_supporting_data(self):
"""Aggregate all data points from research"""
sections = self.research_data.get("content", {}).get("sections", [])
for section in sections:
if section.get("data_points"):
for data_point in section["data_points"]:
self.synthesis["supporting_data"].append({
"source": section["title"],
"metric": data_point["metric"],
"value": data_point["value"],
"context": section.get("title", "")
})
def extract_recommendations(self):
"""Extract actionable recommendations"""
sections = self.research_data.get("content", {}).get("sections", [])
for section in sections:
# Look for recommendation sections
if "recommend" in section.get("title", "").lower():
if section.get("action_items"):
self.synthesis["recommendations"].extend(
section["action_items"]
)
else:
# Extract from content
content = section.get("content", "")
if "recommend" in content.lower():
# Simple extraction of recommendation sentences
sentences = content.split(".")
for sentence in sentences:
if "recommend" in sentence.lower():
self.synthesis["recommendations"].append(
sentence.strip() + "."
)
def create_slide_plan(self):
"""Generate detailed slide-by-slide plan"""
slides = []
# Title slide
slides.append({
"number": 1,
"type": "title",
"title": self.synthesis["metadata"]["title"],
"subtitle": f"Research Synthesis - {self.synthesis['metadata']['date']}",
"speaker_notes": self.synthesis["executive_summary"]
})
# Executive Summary slide
slides.append({
"number": 2,
"type": "executive_summary",
"title": "Executive Summary",
"content": self.synthesis["executive_summary"],
"key_points": self.synthesis["key_topics"][0]["key_points"][:3],
"speaker_notes": "Overview of key findings and recommendations"
})
# Agenda slide
if self.synthesis["agenda_items"]:
slides.append({
"number": 3,
"type": "agenda",
"title": "Agenda",
"items": [item["title"] for item in self.synthesis["agenda_items"]],
"total_duration": sum(item["duration"] for item in self.synthesis["agenda_items"]),
"speaker_notes": "Today's discussion topics and time allocation"
})
# Content slides for each major topic
slide_num = 4
for topic in self.synthesis["key_topics"][:6]: # Limit to 6 main topics
slides.append({
"number": slide_num,
"type": "content",
"title": topic["title"],
"bullets": topic["key_points"],
"data": topic.get("data_points", []),
"speaker_notes": topic["speaker_notes"]
})
slide_num += 1
# Add subtopic slides if important
if topic.get("subtopics") and topic["importance"] > 1.5:
for subtopic in topic["subtopics"][:2]: # Max 2 subtopic slides
slides.append({
"number": slide_num,
"type": "content",
"title": subtopic["title"],
"bullets": subtopic["key_points"],
"speaker_notes": f"Deep dive into {subtopic['title']}"
})
slide_num += 1
# Data summary slide if we have metrics
if self.synthesis["supporting_data"]:
slides.append({
"number": slide_num,
"type": "data_visualization",
"title": "Key Metrics",
"data_points": self.synthesis["supporting_data"][:8],
"chart_type": "dashboard",
"speaker_notes": "Summary of key performance indicators"
})
slide_num += 1
# Recommendations slide
if self.synthesis["recommendations"]:
slides.append({
"number": slide_num,
"type": "recommendations",
"title": "Recommendations",
"items": self.synthesis["recommendations"][:5],
"speaker_notes": "Proposed next steps based on research findings"
})
slide_num += 1
# Thank you / Questions slide
slides.append({
"number": slide_num,
"type": "closing",
"title": "Thank You",
"subtitle": "Questions & Discussion",
"speaker_notes": "Open floor for questions and discussion"
})
self.synthesis["slide_plan"] = slides
def main():
parser = argparse.ArgumentParser(
description="Synthesize research content into presentation structure"
)
parser.add_argument(
"research_file",
help="Input research JSON file"
)
parser.add_argument(
"--output",
default="synthesis.json",
help="Output synthesis JSON file"
)
parser.add_argument(
"--max-slides",
type=int,
default=15,
help="Maximum number of slides (default: 15)"
)
args = parser.parse_args()
print(f"🔍 Synthesizing content from: {args.research_file}")
# Load research data
with open(args.research_file, 'r', encoding='utf-8') as f:
research_data = json.load(f)
# Synthesize content
synthesizer = ContentSynthesizer(research_data)
synthesis = synthesizer.synthesize()
# Limit slides if specified
if args.max_slides and len(synthesis["slide_plan"]) > args.max_slides:
synthesis["slide_plan"] = synthesis["slide_plan"][:args.max_slides]
# Save synthesis
with open(args.output, 'w', encoding='utf-8') as f:
json.dump(synthesis, f, indent=2, ensure_ascii=False)
print(f"✅ Synthesis saved to: {args.output}")
print(f"📊 Generated plan for {len(synthesis['slide_plan'])} slides")
print(f"🎯 Identified {len(synthesis['key_topics'])} key topics")
print(f"📝 Created {len(synthesis['agenda_items'])} agenda items")
if __name__ == "__main__":
main()