devZenaight commited on
Commit
de0b6bd
·
1 Parent(s): 18103ba

Working TOC but indexing incorrect

Browse files
app/DocumentGeneration/__init__.py CHANGED
@@ -12,7 +12,7 @@ if current_dir not in sys.path:
12
  from pdf_generator import create_pdf_document
13
  from word_generator import create_word_document
14
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
15
- from table_of_content import generate_table_of_contents, add_table_of_contents_page
16
  from document_helpers import (
17
  extract_business_name_from_content,
18
  filter_empty_sections,
@@ -38,7 +38,7 @@ __all__ = [
38
  'create_word_document',
39
  'parse_embedded_chart_data',
40
  'create_chart_from_data',
41
- 'generate_table_of_contents',
42
  'add_table_of_contents_page',
43
  'extract_business_name_from_content',
44
  'filter_empty_sections',
 
12
  from pdf_generator import create_pdf_document
13
  from word_generator import create_word_document
14
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
15
+ from table_of_content import generate_table_of_contents_after_content, add_table_of_contents_page
16
  from document_helpers import (
17
  extract_business_name_from_content,
18
  filter_empty_sections,
 
38
  'create_word_document',
39
  'parse_embedded_chart_data',
40
  'create_chart_from_data',
41
+ 'generate_table_of_contents_after_content',
42
  'add_table_of_contents_page',
43
  'extract_business_name_from_content',
44
  'filter_empty_sections',
app/DocumentGeneration/pdf_generator.py CHANGED
@@ -18,7 +18,7 @@ from document_helpers import (
18
  extract_business_name_from_content,
19
  remove_duplicate_headings
20
  )
21
- from table_of_content import generate_table_of_contents, add_table_of_contents_page
22
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
23
  from markdown_renderer import (
24
  clean_text_for_pdf,
@@ -34,6 +34,189 @@ from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
34
  # Import sort_sections_by_order from utils module
35
  from utils import sort_sections_by_order
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def create_pdf_document(request: ExportRequest):
38
  """Create a PROFESSIONAL PDF document from the business plan data using FPDF2
39
  NOTE: Now uses matplotlib charts exclusively - old base64 chart logic commented out"""
@@ -50,15 +233,13 @@ def create_pdf_document(request: ExportRequest):
50
  if not filtered_sections:
51
  raise Exception("No valid sections found after filtering. All sections appear to be empty.")
52
 
53
- # Generate table of contents
54
- try:
55
- toc_items = generate_table_of_contents(filtered_sections, request.businessIdea)
56
- except Exception as e:
57
- toc_items = []
58
- # Continue with a fallback; TOC page builder already has fallback handling
59
- # logging.info(f"Generated TOC with {len(toc_items)} items: {[item['title'] for item in toc_items[:3]]}...")
60
 
61
- # Create PDF with professional settings
62
  pdf = FPDF()
63
  pdf.set_auto_page_break(auto=True, margin=15) # Reduced margin to allow more charts per page
64
 
@@ -447,8 +628,8 @@ def create_pdf_document(request: ExportRequest):
447
  except Exception as e:
448
  raise Exception(f"Cover page failed: {str(e)}")
449
 
 
450
  try:
451
- # Ensure TOC items are generated before adding the page
452
  if toc_items:
453
  add_table_of_contents_page(pdf, toc_items, request.businessIdea)
454
  else:
 
18
  extract_business_name_from_content,
19
  remove_duplicate_headings
20
  )
21
+ from table_of_content import generate_table_of_contents_after_content, add_table_of_contents_page
22
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
23
  from markdown_renderer import (
24
  clean_text_for_pdf,
 
34
  # Import sort_sections_by_order from utils module
35
  from utils import sort_sections_by_order
36
 
37
+ def calculate_real_page_numbers(request: ExportRequest, filtered_sections):
38
+ """Calculate real page numbers by creating a temporary PDF with all content INCLUDING charts"""
39
+ try:
40
+ from fpdf import FPDF
41
+ except ImportError:
42
+ raise Exception("fpdf2 is not installed. Please install it with: pip install fpdf2")
43
+
44
+ # Create temporary PDF to calculate page numbers
45
+ temp_pdf = FPDF()
46
+ temp_pdf.set_auto_page_break(auto=True, margin=15)
47
+
48
+ # Add cover page
49
+ temp_pdf.add_page()
50
+
51
+ # Add TOC page (placeholder)
52
+ temp_pdf.add_page()
53
+
54
+ # Parse charts from embedded chart data in section content
55
+ embedded_charts = parse_embedded_chart_data(filtered_sections)
56
+
57
+ # Create charts and map them to sections
58
+ chart_info_map = {}
59
+ if embedded_charts:
60
+ for chart_info in embedded_charts:
61
+ chart_bytes = create_chart_from_data(chart_info, request.businessIdea or "Business")
62
+ if chart_bytes:
63
+ source_section = chart_info["source_section"]
64
+ if source_section not in chart_info_map:
65
+ chart_info_map[source_section] = []
66
+ chart_info_map[source_section].append({
67
+ "chart_bytes": chart_bytes,
68
+ "title": chart_info["title"],
69
+ "type": chart_info["type"]
70
+ })
71
+
72
+ # Add all sections to calculate real page numbers INCLUDING charts
73
+ section_page_map = {}
74
+ sorted_sections = sort_sections_by_order(filtered_sections)
75
+
76
+ for section in sorted_sections:
77
+ temp_pdf.add_page()
78
+ current_page = temp_pdf.page_no()
79
+ section_page_map[section.title] = current_page
80
+
81
+ # Add section title
82
+ temp_pdf.set_font("Arial", "B", 20)
83
+ temp_pdf.set_text_color(0, 0, 0)
84
+ temp_pdf.set_xy(25, 25)
85
+ temp_pdf.cell(0, 18, section.title, ln=True)
86
+
87
+ # Add subtle line under section title
88
+ text_width = temp_pdf.get_string_width(section.title)
89
+ line_width = min(text_width + 8, 160)
90
+ temp_pdf.set_draw_color(221, 221, 221)
91
+ temp_pdf.set_line_width(0.3)
92
+ temp_pdf.line(25, temp_pdf.get_y(), 25 + line_width, temp_pdf.get_y())
93
+ temp_pdf.ln(8)
94
+
95
+ # Add section content
96
+ temp_pdf.set_font("Arial", "", 12)
97
+ temp_pdf.set_text_color(51, 51, 51)
98
+ temp_pdf.set_xy(25, temp_pdf.get_y())
99
+
100
+ # Process content
101
+ try:
102
+ content_text = clean_text_for_pdf(section.content)
103
+ except Exception as e:
104
+ content_text = section.content or ""
105
+
106
+ # Remove chart data markers
107
+ content_text = re.sub(r'<!--CHARTDATASTART-->.*?<!--CHARTDATAEND-->', '', content_text, flags=re.DOTALL)
108
+ content_text = re.sub(r'<!--CHARTDATASTART-->.*$', '', content_text, flags=re.DOTALL)
109
+
110
+ # Remove the section title from content to prevent duplication
111
+ section_title_clean = section.title.strip()
112
+ content_lines = content_text.split('\n')
113
+ filtered_lines = []
114
+ for line in content_lines:
115
+ line_clean = line.strip()
116
+ if (line_clean == section_title_clean or
117
+ line_clean == f"**{section_title_clean}**" or
118
+ line_clean == f"# {section_title_clean}" or
119
+ line_clean == f"## {section_title_clean}" or
120
+ line_clean == f"### {section_title_clean}" or
121
+ line_clean == f"#### {section_title_clean}" or
122
+ line_clean.startswith(f"{section_title_clean}") and len(line_clean) <= len(section_title_clean) + 10):
123
+ continue
124
+ filtered_lines.append(line)
125
+ content_text = '\n'.join(filtered_lines)
126
+
127
+ # Add content with proper markdown rendering
128
+ if content_text and content_text.strip():
129
+ render_markdown_to_pdf_grouped(temp_pdf, content_text, (0,0,0), (75,0,130), (51,51,51))
130
+
131
+ # Add charts for this section (same logic as in actual PDF generation)
132
+ section_charts = chart_info_map.get(section.title, [])
133
+
134
+ # Try different matching strategies for charts
135
+ if not section_charts:
136
+ # Case-insensitive match
137
+ for chart_source, charts in chart_info_map.items():
138
+ if chart_source.lower() == section.title.lower():
139
+ section_charts = charts
140
+ break
141
+
142
+ # Partial match
143
+ if not section_charts:
144
+ for chart_source, charts in chart_info_map.items():
145
+ if (section.title.lower() in chart_source.lower() or
146
+ chart_source.lower() in section.title.lower()):
147
+ section_charts = charts
148
+ break
149
+
150
+ # Key-based match
151
+ if not section_charts and hasattr(section, 'key'):
152
+ for chart_source, charts in chart_info_map.items():
153
+ if section.key and section.key.replace('prompt_', '') in chart_source.lower():
154
+ section_charts = charts
155
+ break
156
+
157
+ # Add charts with same page break logic as actual PDF
158
+ if section_charts:
159
+ charts_per_page = 0
160
+ max_charts_per_page = 5
161
+
162
+ for chart_info in section_charts:
163
+ try:
164
+ chart_height_needed = 95 # Chart height (75) + title (20) + minimal spacing
165
+ current_y = temp_pdf.get_y()
166
+ charts_per_page += 1
167
+
168
+ # Only force page break if absolutely necessary
169
+ if (current_y + chart_height_needed > 290) or (charts_per_page > 5):
170
+ temp_pdf.add_page()
171
+ y_title = 25
172
+ charts_per_page = 1
173
+ else:
174
+ y_title = current_y
175
+
176
+ # Add minimal spacing before chart if not at top of page
177
+ if y_title > 25:
178
+ temp_pdf.ln(3)
179
+
180
+ # Add chart title
181
+ temp_pdf.set_font("Arial", "B", 16)
182
+ temp_pdf.set_text_color(0, 0, 0)
183
+ temp_pdf.set_xy(25, y_title)
184
+ temp_pdf.cell(0, 14, chart_info["title"], ln=True)
185
+
186
+ # Add subtle line under chart title
187
+ text_width = temp_pdf.get_string_width(chart_info["title"])
188
+ line_width = min(text_width + 8, 160)
189
+ temp_pdf.set_draw_color(221, 221, 221)
190
+ temp_pdf.set_line_width(0.3)
191
+ temp_pdf.line(25, y_title + 14, 25 + line_width, y_title + 14)
192
+
193
+ # Add chart image
194
+ img_width = 100
195
+ x = (210 - img_width) / 2
196
+ y_img = y_title + 20
197
+
198
+ # Save image to temp file
199
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_img:
200
+ tmp_img.write(chart_info["chart_bytes"])
201
+ temp_img_path = tmp_img.name
202
+
203
+ # Add chart
204
+ chart_height = 75
205
+ temp_pdf.image(temp_img_path, x=x, y=y_img, w=img_width)
206
+ temp_pdf.ln(chart_height + 5)
207
+
208
+ # Clean up temp file
209
+ try:
210
+ os.remove(temp_img_path)
211
+ except Exception:
212
+ pass
213
+
214
+ except Exception:
215
+ # Skip faulty chart and continue
216
+ continue
217
+
218
+ return section_page_map
219
+
220
  def create_pdf_document(request: ExportRequest):
221
  """Create a PROFESSIONAL PDF document from the business plan data using FPDF2
222
  NOTE: Now uses matplotlib charts exclusively - old base64 chart logic commented out"""
 
233
  if not filtered_sections:
234
  raise Exception("No valid sections found after filtering. All sections appear to be empty.")
235
 
236
+ # First pass: Calculate real page numbers by creating a temporary PDF
237
+ section_page_map = calculate_real_page_numbers(request, filtered_sections)
238
+
239
+ # Generate TOC with real page numbers
240
+ toc_items = generate_table_of_contents_after_content(filtered_sections, section_page_map, request.businessIdea)
 
 
241
 
242
+ # Second pass: Create final PDF with TOC on page 2
243
  pdf = FPDF()
244
  pdf.set_auto_page_break(auto=True, margin=15) # Reduced margin to allow more charts per page
245
 
 
628
  except Exception as e:
629
  raise Exception(f"Cover page failed: {str(e)}")
630
 
631
+ # Add TOC on page 2 with REAL page numbers (already calculated above)
632
  try:
 
633
  if toc_items:
634
  add_table_of_contents_page(pdf, toc_items, request.businessIdea)
635
  else:
app/DocumentGeneration/table_of_content.py CHANGED
@@ -1,5 +1,5 @@
1
  # Table of Contents Module
2
- # Contains generate_table_of_contents() and add_table_of_contents_page() functions
3
 
4
  import re
5
  import os
@@ -11,8 +11,9 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
11
  from models import ExportSection
12
  from constants import SECTION_ORDER
13
 
14
- def generate_table_of_contents(sections: List[ExportSection], business_idea: str) -> List[Dict[str, Any]]:
15
- """Generate a structured table of contents with page numbers following SECTION_ORDER"""
 
16
  toc_items = []
17
 
18
  # Create a mapping from section titles to section objects for easy lookup
@@ -50,23 +51,27 @@ def generate_table_of_contents(sections: List[ExportSection], business_idea: str
50
  section = section_obj
51
  break
52
 
53
- # Always add to TOC, even if section doesn't exist or has no content
54
- # Each section starts on a new page, so page number is 3 + i (cover=1, TOC=2, sections start at 3)
55
- page_number = 3 + i
56
- toc_items.append({
57
- "title": expected_title, # Use the expected title for consistency
58
- "level": 1,
59
- "page": page_number,
60
- "section_type": "section",
61
- "has_content": bool(section and section.content and section.content.strip())
62
- })
 
 
 
 
63
 
64
  return toc_items
65
-
66
  def add_table_of_contents_page(pdf, toc_items: List[Dict[str, Any]], business_idea: str):
67
- """Add a professional table of contents page following SECTION_ORDER"""
68
 
69
- # TOC items are already in the correct order from generate_table_of_contents
70
  sorted_toc_items = toc_items
71
 
72
  # Professional color scheme - Refined for bank/investor presentation
@@ -141,7 +146,7 @@ def add_table_of_contents_page(pdf, toc_items: List[Dict[str, Any]], business_id
141
  pdf.set_xy(x, y_position + 3)
142
  pdf.cell(1, 1, ".", ln=False)
143
 
144
- # Page number with hyperlink
145
  page_number_width = pdf.get_string_width(str(item["page"]))
146
  pdf.set_xy(160, y_position)
147
  try:
 
1
  # Table of Contents Module
2
+ # Contains functions to generate TOC AFTER content is placed with real page numbers
3
 
4
  import re
5
  import os
 
11
  from models import ExportSection
12
  from constants import SECTION_ORDER
13
 
14
+ def generate_table_of_contents_after_content(sections: List[ExportSection], section_page_map: Dict[str, int], business_idea: str) -> List[Dict[str, Any]]:
15
+ """Generate TOC AFTER all content is placed, using REAL page numbers from section_page_map
16
+ This ensures accurate page numbers based on actual content placement"""
17
  toc_items = []
18
 
19
  # Create a mapping from section titles to section objects for easy lookup
 
51
  section = section_obj
52
  break
53
 
54
+ # Only add sections that have actual content AND real page numbers - NO DUMMY DATA
55
+ has_content = bool(section and section.content and section.content.strip())
56
+ if has_content:
57
+ # Get the REAL page number from section_page_map
58
+ real_page = section_page_map.get(section.title, 0)
59
+ if real_page > 0: # Only add if we have a real page number
60
+ toc_items.append({
61
+ "title": expected_title, # Use the expected title for consistency
62
+ "level": 1,
63
+ "page": real_page, # REAL page number from actual content placement
64
+ "section_type": "section",
65
+ "has_content": True,
66
+ "section_obj": section # Store reference to actual section
67
+ })
68
 
69
  return toc_items
70
+
71
  def add_table_of_contents_page(pdf, toc_items: List[Dict[str, Any]], business_idea: str):
72
+ """Add a professional table of contents page with REAL page numbers"""
73
 
74
+ # TOC items are already in the correct order and have real page numbers
75
  sorted_toc_items = toc_items
76
 
77
  # Professional color scheme - Refined for bank/investor presentation
 
146
  pdf.set_xy(x, y_position + 3)
147
  pdf.cell(1, 1, ".", ln=False)
148
 
149
+ # Page number with hyperlink - NOW USING REAL PAGE NUMBERS
150
  page_number_width = pdf.get_string_width(str(item["page"]))
151
  pdf.set_xy(160, y_position)
152
  try:
app/DocumentGeneration/word_generator.py CHANGED
@@ -18,7 +18,7 @@ from document_helpers import (
18
  filter_empty_sections,
19
  extract_business_name_from_content
20
  )
21
- from table_of_content import generate_table_of_contents
22
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
23
  from markdown_renderer import (
24
  render_markdown_to_docx_grouped,
 
18
  filter_empty_sections,
19
  extract_business_name_from_content
20
  )
21
+ # from table_of_content import generate_table_of_contents # Not used in word generator
22
  from chart_generator import parse_embedded_chart_data, create_chart_from_data
23
  from markdown_renderer import (
24
  render_markdown_to_docx_grouped,
app/main.py CHANGED
@@ -118,7 +118,7 @@ from DocumentGeneration.pdf_generator import create_pdf_document
118
  from DocumentGeneration.word_generator import create_word_document
119
  from DocumentGeneration.live_pdf_generator import live_pdf_generator
120
  from DocumentGeneration.chart_generator import parse_embedded_chart_data, create_chart_from_data
121
- from DocumentGeneration.table_of_content import generate_table_of_contents, add_table_of_contents_page
122
  from DocumentGeneration.document_helpers import (
123
  extract_business_name_from_content,
124
  filter_empty_sections,
@@ -1674,7 +1674,7 @@ def root():
1674
  # @app.get("/test-toc")
1675
  # def test_toc():
1676
  # """Test endpoint to verify TOC generation"""
1677
- # from app.main import generate_table_of_contents, ExportSection
1678
 
1679
  # # Create test sections
1680
  # test_sections = [
@@ -1696,7 +1696,7 @@ def root():
1696
  # ]
1697
 
1698
  # # Generate TOC
1699
- # toc_items = generate_table_of_contents(test_sections, "Test Business")
1700
 
1701
  # return {
1702
  # "test_sections": [{"title": s.title, "key": s.key, "content_length": len(s.content)} for s in test_sections],
 
118
  from DocumentGeneration.word_generator import create_word_document
119
  from DocumentGeneration.live_pdf_generator import live_pdf_generator
120
  from DocumentGeneration.chart_generator import parse_embedded_chart_data, create_chart_from_data
121
+ from DocumentGeneration.table_of_content import generate_table_of_contents_after_content, add_table_of_contents_page
122
  from DocumentGeneration.document_helpers import (
123
  extract_business_name_from_content,
124
  filter_empty_sections,
 
1674
  # @app.get("/test-toc")
1675
  # def test_toc():
1676
  # """Test endpoint to verify TOC generation"""
1677
+ # from app.main import generate_table_of_contents_after_content, ExportSection
1678
 
1679
  # # Create test sections
1680
  # test_sections = [
 
1696
  # ]
1697
 
1698
  # # Generate TOC
1699
+ # toc_items = generate_table_of_contents_after_content(test_sections, {}, "Test Business")
1700
 
1701
  # return {
1702
  # "test_sections": [{"title": s.title, "key": s.key, "content_length": len(s.content)} for s in test_sections],