deckbuilder 1.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Layout Intelligence Engine for Content-First Presentation Generation
4
+
5
+ Provides intelligent layout recommendations based on content analysis using
6
+ semantic metadata and convention-based placeholder names.
7
+ """
8
+
9
+ import json
10
+ import re
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Dict, List, Optional, Tuple
14
+
15
+
16
+ @dataclass
17
+ class LayoutRecommendation:
18
+ """Layout recommendation with confidence score and reasoning"""
19
+
20
+ layout_name: str
21
+ confidence: float
22
+ reasoning: List[str]
23
+ placeholder_mapping: Dict[str, str]
24
+ optimization_hints: List[str]
25
+
26
+
27
+ @dataclass
28
+ class ContentAnalysis:
29
+ """Content analysis results"""
30
+
31
+ intent: str
32
+ content_type: str
33
+ structure_indicators: List[str]
34
+ keywords_found: List[str]
35
+ content_blocks: int
36
+ has_images: bool
37
+ has_numbers: bool
38
+
39
+
40
+ class LayoutIntelligence:
41
+ """
42
+ Content-first layout intelligence engine that analyzes content and
43
+ recommends optimal layouts with confidence scoring.
44
+ """
45
+
46
+ def __init__(self, intelligence_file: Optional[str] = None):
47
+ """
48
+ Initialize layout intelligence engine.
49
+
50
+ Args:
51
+ intelligence_file: Path to layout_intelligence.json file
52
+ """
53
+ if intelligence_file is None:
54
+ # Default to src/layout_intelligence.json relative to this file
55
+ current_dir = Path(__file__).parent.parent
56
+ intelligence_file = str(current_dir / "layout_intelligence.json")
57
+
58
+ self.intelligence_file = intelligence_file
59
+ self.intelligence_data = self._load_intelligence_data()
60
+
61
+ def _load_intelligence_data(self) -> Dict:
62
+ """Load layout intelligence metadata"""
63
+ try:
64
+ with open(self.intelligence_file, "r", encoding="utf-8") as f:
65
+ return json.load(f)
66
+ except FileNotFoundError:
67
+ raise FileNotFoundError(f"Layout intelligence file not found: {self.intelligence_file}")
68
+ except json.JSONDecodeError as e:
69
+ raise ValueError(f"Invalid JSON in intelligence file: {e}")
70
+
71
+ def analyze_content(self, content: str) -> ContentAnalysis:
72
+ """
73
+ Analyze content to extract semantic information for layout recommendations.
74
+
75
+ Args:
76
+ content: Raw markdown content to analyze
77
+
78
+ Returns:
79
+ ContentAnalysis with detected patterns and characteristics
80
+ """
81
+ content_lower = content.lower()
82
+
83
+ # Detect intent patterns
84
+ intent = self._detect_intent(content_lower)
85
+
86
+ # Detect content type
87
+ content_type = self._detect_content_type(content_lower)
88
+
89
+ # Analyze structure
90
+ structure_indicators = self._analyze_structure(content)
91
+
92
+ # Find relevant keywords
93
+ keywords_found = self._find_keywords(content_lower)
94
+
95
+ # Count content characteristics
96
+ content_blocks = self._count_content_blocks(content)
97
+ has_images = self._has_image_content(content_lower)
98
+ has_numbers = self._has_numeric_content(content_lower)
99
+
100
+ return ContentAnalysis(
101
+ intent=intent,
102
+ content_type=content_type,
103
+ structure_indicators=structure_indicators,
104
+ keywords_found=keywords_found,
105
+ content_blocks=content_blocks,
106
+ has_images=has_images,
107
+ has_numbers=has_numbers,
108
+ )
109
+
110
+ def recommend_layouts(
111
+ self, content: str, max_recommendations: int = 3
112
+ ) -> List[LayoutRecommendation]:
113
+ """
114
+ Recommend optimal layouts based on content analysis.
115
+
116
+ Args:
117
+ content: Content to analyze
118
+ max_recommendations: Maximum number of recommendations to return
119
+
120
+ Returns:
121
+ List of LayoutRecommendation objects sorted by confidence
122
+ """
123
+ analysis = self.analyze_content(content)
124
+ recommendations = []
125
+
126
+ # Get layout compatibility data
127
+ layout_compatibility = self.intelligence_data.get("layout_compatibility", {})
128
+ content_patterns = self.intelligence_data.get("content_patterns", {})
129
+ scoring_weights = self.intelligence_data.get("recommendation_engine", {}).get(
130
+ "scoring_weights", {}
131
+ )
132
+
133
+ # Score each layout
134
+ for layout_name, layout_info in layout_compatibility.items():
135
+ confidence, reasoning = self._score_layout(
136
+ analysis, layout_name, layout_info, content_patterns, scoring_weights
137
+ )
138
+
139
+ if confidence >= self.intelligence_data.get("recommendation_engine", {}).get(
140
+ "minimum_confidence", 0.6
141
+ ):
142
+ placeholder_mapping = self._generate_placeholder_mapping(analysis, layout_info)
143
+ optimization_hints = self._get_optimization_hints(layout_name, analysis)
144
+
145
+ recommendations.append(
146
+ LayoutRecommendation(
147
+ layout_name=layout_name,
148
+ confidence=confidence,
149
+ reasoning=reasoning,
150
+ placeholder_mapping=placeholder_mapping,
151
+ optimization_hints=optimization_hints,
152
+ )
153
+ )
154
+
155
+ # Sort by confidence and return top recommendations
156
+ recommendations.sort(key=lambda x: x.confidence, reverse=True)
157
+ return recommendations[:max_recommendations]
158
+
159
+ def _detect_intent(self, content_lower: str) -> str:
160
+ """Detect primary intent from content"""
161
+ intent_patterns = self.intelligence_data.get("content_patterns", {}).get(
162
+ "intent_recognition", {}
163
+ )
164
+
165
+ best_intent = "overview"
166
+ best_score = 0
167
+
168
+ for intent, pattern_info in intent_patterns.items():
169
+ score = 0
170
+ keywords = pattern_info.get("keywords", [])
171
+
172
+ for keyword in keywords:
173
+ if keyword in content_lower:
174
+ score += 1
175
+
176
+ # Normalize score by number of keywords
177
+ if keywords:
178
+ score = score / len(keywords)
179
+
180
+ if score > best_score:
181
+ best_score = score
182
+ best_intent = intent
183
+
184
+ return best_intent
185
+
186
+ def _detect_content_type(self, content_lower: str) -> str:
187
+ """Detect primary content type"""
188
+ # Simple heuristics for content type detection
189
+ if any(word in content_lower for word in ["image", "picture", "photo", "diagram"]):
190
+ return "image_content"
191
+ elif any(word in content_lower for word in ["vs", "versus", "compare", "option"]):
192
+ return "comparison_content"
193
+ elif re.search(r"\d+[%]|\d+\.\d+", content_lower):
194
+ return "statistics"
195
+ elif any(word in content_lower for word in ["step", "agenda", "process"]):
196
+ return "agenda_content"
197
+ elif len(re.findall(r"^#{1,3}\s", content_lower, re.MULTILINE)) >= 3:
198
+ return "column_content"
199
+ else:
200
+ return "body_content"
201
+
202
+ def _analyze_structure(self, content: str) -> List[str]:
203
+ """Analyze content structure indicators"""
204
+ indicators = []
205
+
206
+ # Count headings
207
+ heading_count = len(re.findall(r"^#{1,6}\s", content, re.MULTILINE))
208
+ if heading_count >= 4:
209
+ indicators.append("multiple_columns")
210
+ elif heading_count == 2:
211
+ indicators.append("paired_content")
212
+
213
+ # Check for lists
214
+ if re.search(r"^\s*[-*+]\s", content, re.MULTILINE):
215
+ indicators.append("bullet_list")
216
+ if re.search(r"^\s*\d+\.\s", content, re.MULTILINE):
217
+ indicators.append("numbered_list")
218
+
219
+ # Check for structured frontmatter
220
+ if content.strip().startswith("---"):
221
+ indicators.append("structured_frontmatter")
222
+
223
+ # Check for tables
224
+ if "|" in content and re.search(r"\|.*\|.*\|", content):
225
+ indicators.append("table_content")
226
+
227
+ return indicators
228
+
229
+ def _find_keywords(self, content_lower: str) -> List[str]:
230
+ """Find relevant keywords in content"""
231
+ found_keywords = []
232
+ intent_patterns = self.intelligence_data.get("content_patterns", {}).get(
233
+ "intent_recognition", {}
234
+ )
235
+
236
+ for intent, pattern_info in intent_patterns.items():
237
+ keywords = pattern_info.get("keywords", [])
238
+ for keyword in keywords:
239
+ if keyword in content_lower:
240
+ found_keywords.append(keyword)
241
+
242
+ return list(set(found_keywords)) # Remove duplicates
243
+
244
+ def _count_content_blocks(self, content: str) -> int:
245
+ """Count distinct content blocks"""
246
+ # Count major sections (headings + paragraphs)
247
+ headings = len(re.findall(r"^#{1,6}\s", content, re.MULTILINE))
248
+ paragraphs = len(
249
+ [p for p in content.split("\n\n") if p.strip() and not p.strip().startswith("#")]
250
+ )
251
+ return max(headings, paragraphs // 2) # Estimate content blocks
252
+
253
+ def _has_image_content(self, content_lower: str) -> bool:
254
+ """Check if content references images"""
255
+ image_indicators = ["image", "picture", "photo", "diagram", "visual", "media"]
256
+ return any(indicator in content_lower for indicator in image_indicators)
257
+
258
+ def _has_numeric_content(self, content_lower: str) -> bool:
259
+ """Check if content has significant numeric data"""
260
+ return bool(re.search(r"\d+[%]|\$\d+|\d+\.\d+|^\d+$", content_lower, re.MULTILINE))
261
+
262
+ def _score_layout(
263
+ self,
264
+ analysis: ContentAnalysis,
265
+ layout_name: str,
266
+ layout_info: Dict,
267
+ content_patterns: Dict,
268
+ scoring_weights: Dict,
269
+ ) -> Tuple[float, List[str]]:
270
+ """Score a layout's compatibility with analyzed content"""
271
+ score = 0.0
272
+ reasoning = []
273
+
274
+ # Content structure scoring
275
+ optimal_for = layout_info.get("optimal_for", [])
276
+ structure_match = any(
277
+ indicator in analysis.structure_indicators for indicator in optimal_for
278
+ )
279
+ if structure_match:
280
+ score += scoring_weights.get("content_structure", 0.4)
281
+ reasoning.append(f"Content structure matches {layout_name} purpose")
282
+
283
+ # Keyword matching scoring
284
+ confidence_factors = layout_info.get("confidence_factors", {})
285
+ for factor, weight in confidence_factors.items():
286
+ if factor in analysis.keywords_found or factor in analysis.structure_indicators:
287
+ score += scoring_weights.get("keyword_matching", 0.3) * weight
288
+ reasoning.append(f"Found {factor} indicator")
289
+
290
+ # Intent recognition scoring
291
+ intent_patterns = content_patterns.get("intent_recognition", {})
292
+ if analysis.intent in intent_patterns:
293
+ intent_layouts = intent_patterns[analysis.intent].get("layouts", [])
294
+ if layout_name in intent_layouts:
295
+ score += scoring_weights.get("intent_recognition", 0.2)
296
+ reasoning.append(f"Layout matches {analysis.intent} intent")
297
+
298
+ # Layout compatibility scoring
299
+ if analysis.content_blocks == 4 and "Four Columns" in layout_name:
300
+ score += scoring_weights.get("layout_compatibility", 0.1)
301
+ reasoning.append("Content blocks match four-column structure")
302
+ elif analysis.content_blocks == 3 and "Three Columns" in layout_name:
303
+ score += scoring_weights.get("layout_compatibility", 0.1)
304
+ reasoning.append("Content blocks match three-column structure")
305
+ elif analysis.has_images and "Picture" in layout_name:
306
+ score += scoring_weights.get("layout_compatibility", 0.1)
307
+ reasoning.append("Image content matches picture layout")
308
+
309
+ return min(score, 1.0), reasoning
310
+
311
+ def _generate_placeholder_mapping(
312
+ self, analysis: ContentAnalysis, layout_info: Dict
313
+ ) -> Dict[str, str]:
314
+ """Generate placeholder mapping suggestions"""
315
+ mapping = {}
316
+ placeholders = layout_info.get("placeholders", {})
317
+ content_hints = layout_info.get("content_hints", {})
318
+
319
+ for placeholder_type in ["required", "optional"]:
320
+ if placeholder_type in placeholders:
321
+ for placeholder in placeholders[placeholder_type]:
322
+ if placeholder in content_hints:
323
+ mapping[placeholder] = content_hints[placeholder]
324
+ else:
325
+ mapping[placeholder] = f"Content for {placeholder}"
326
+
327
+ return mapping
328
+
329
+ def _get_optimization_hints(self, layout_name: str, analysis: ContentAnalysis) -> List[str]:
330
+ """Get optimization hints for the layout"""
331
+ hints = []
332
+ optimization_data = self.intelligence_data.get("optimization_hints", {})
333
+
334
+ # General content length hints
335
+ content_length = optimization_data.get("content_length", {})
336
+ for placeholder, hint in content_length.items():
337
+ hints.append(hint)
338
+
339
+ # Layout-specific hints
340
+ layout_specific = optimization_data.get("layout_specific", {})
341
+ if layout_name in layout_specific:
342
+ hints.append(layout_specific[layout_name])
343
+
344
+ # Analysis-based hints
345
+ if analysis.has_images:
346
+ hints.append("Consider using high-quality images with proper aspect ratios")
347
+
348
+ if analysis.has_numbers:
349
+ hints.append("Use consistent number formatting and consider highlighting key metrics")
350
+
351
+ return hints[:3] # Limit to top 3 hints
352
+
353
+
354
+ def test_layout_intelligence():
355
+ """Test the layout intelligence system"""
356
+ engine = LayoutIntelligence()
357
+
358
+ test_content = """
359
+ # Feature Comparison: Our Platform vs Competition
360
+
361
+ ## Performance
362
+ Our platform delivers **fast processing** with optimized algorithms
363
+
364
+ ## Security
365
+ ***Enterprise-grade*** encryption with SOC2 compliance
366
+
367
+ ## Usability
368
+ *Intuitive* interface with minimal learning curve
369
+ ## Cost
370
+ Transparent pricing with flexible plans
371
+ """
372
+
373
+ print("Testing Layout Intelligence Engine")
374
+ print("=" * 50)
375
+
376
+ # Analyze content
377
+ analysis = engine.analyze_content(test_content)
378
+ print("Content Analysis:")
379
+ print(f" Intent: {analysis.intent}")
380
+ print(f" Content Type: {analysis.content_type}")
381
+ print(f" Structure: {analysis.structure_indicators}")
382
+ print(f" Keywords: {analysis.keywords_found}")
383
+ print(f" Content Blocks: {analysis.content_blocks}")
384
+
385
+ # Get recommendations
386
+ recommendations = engine.recommend_layouts(test_content)
387
+ print("\nLayout Recommendations:")
388
+
389
+ for i, rec in enumerate(recommendations, 1):
390
+ print(f"\n{i}. {rec.layout_name} (Confidence: {rec.confidence:.2f})")
391
+ print(f" Reasoning: {'; '.join(rec.reasoning)}")
392
+ print(f" Key Placeholders: {list(rec.placeholder_mapping.keys())[:3]}")
393
+ if rec.optimization_hints:
394
+ print(f" Hint: {rec.optimization_hints[0]}")
395
+
396
+
397
+ if __name__ == "__main__":
398
+ test_layout_intelligence()