refactor(skills): Restructure skills to dual-platform architecture
Major refactoring of ourdigital-custom-skills with new numbering system: ## Structure Changes - Each skill now has code/ (Claude Code) and desktop/ (Claude Desktop) versions - New progressive numbering: 01-09 General, 10-19 SEO, 20-29 GTM, 30-39 OurDigital, 40-49 Jamie ## Skill Reorganization - 01-notion-organizer (from 02) - 10-18: SEO tools split into focused skills (technical, on-page, local, schema, vitals, gsc, gateway) - 20-21: GTM audit and manager - 30-32: OurDigital designer, research, presentation - 40-41: Jamie brand editor and audit ## New Files - .claude/commands/: Slash command definitions for all skills - CLAUDE.md: Updated with new skill structure documentation - REFACTORING_PLAN.md: Migration documentation - COMPATIBILITY_REPORT.md, SKILLS_COMPARISON.md: Analysis docs ## Removed - Old skill directories (02-05, 10-14, 20-21 old numbering) - Consolidated into new structure with _archive/ for reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -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%)"
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user