Spaces:
Runtime error
Runtime error
Commit ·
d5e836f
1
Parent(s): d734bf7
SOC is now in same state as HF Planify
Browse files- .gitignore +345 -0
- README.md +220 -0
- app/DocumentGeneration/__init__.py +1 -9
- app/DocumentGeneration/chart_generator.py +1 -1
- app/DocumentGeneration/document_helpers.py +1 -38
- app/DocumentGeneration/live_pdf_generator.py +1 -1
- app/DocumentGeneration/markdown_renderer.py +12 -7
- app/DocumentGeneration/mixed.py +6 -91
- app/DocumentGeneration/pdf_generator.py +3 -7
- app/DocumentGeneration/table_of_content.py +2 -2
- app/DocumentGeneration/word_generator.py +3 -7
- app/__init__.py +29 -0
- app/__pycache__/constants.cpython-311.pyc +0 -0
- app/__pycache__/main.cpython-311.pyc +0 -0
- app/__pycache__/models.cpython-311.pyc +0 -0
- app/__pycache__/utils.cpython-311.pyc +0 -0
- app/main.py +17 -19
- app/utils.py +2 -2
- bp _template.txt +293 -0
- maintest.txt +23 -134
.gitignore
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.pyo
|
| 6 |
+
*.pyd
|
| 7 |
+
|
| 8 |
+
# C extensions
|
| 9 |
+
*.so
|
| 10 |
+
|
| 11 |
+
# Distribution / packaging
|
| 12 |
+
.Python
|
| 13 |
+
build/
|
| 14 |
+
develop-eggs/
|
| 15 |
+
dist/
|
| 16 |
+
downloads/
|
| 17 |
+
eggs/
|
| 18 |
+
.eggs/
|
| 19 |
+
lib/
|
| 20 |
+
lib64/
|
| 21 |
+
parts/
|
| 22 |
+
sdist/
|
| 23 |
+
var/
|
| 24 |
+
wheels/
|
| 25 |
+
share/python-wheels/
|
| 26 |
+
*.egg-info/
|
| 27 |
+
.installed.cfg
|
| 28 |
+
*.egg
|
| 29 |
+
MANIFEST
|
| 30 |
+
|
| 31 |
+
# PyInstaller
|
| 32 |
+
# Usually these files are written by a python script from a template
|
| 33 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 34 |
+
*.manifest
|
| 35 |
+
*.spec
|
| 36 |
+
|
| 37 |
+
# Installer logs
|
| 38 |
+
pip-log.txt
|
| 39 |
+
pip-delete-this-directory.txt
|
| 40 |
+
|
| 41 |
+
# Unit test / coverage reports
|
| 42 |
+
htmlcov/
|
| 43 |
+
.tox/
|
| 44 |
+
.nox/
|
| 45 |
+
.coverage
|
| 46 |
+
.coverage.*
|
| 47 |
+
.cache
|
| 48 |
+
nosetests.xml
|
| 49 |
+
coverage.xml
|
| 50 |
+
*.cover
|
| 51 |
+
*.py,cover
|
| 52 |
+
.hypothesis/
|
| 53 |
+
.pytest_cache/
|
| 54 |
+
cover/
|
| 55 |
+
|
| 56 |
+
# Translations
|
| 57 |
+
*.mo
|
| 58 |
+
*.pot
|
| 59 |
+
|
| 60 |
+
# Django stuff:
|
| 61 |
+
*.log
|
| 62 |
+
local_settings.py
|
| 63 |
+
db.sqlite3
|
| 64 |
+
db.sqlite3-journal
|
| 65 |
+
|
| 66 |
+
# Flask stuff:
|
| 67 |
+
instance/
|
| 68 |
+
.webassets-cache
|
| 69 |
+
|
| 70 |
+
# Scrapy stuff:
|
| 71 |
+
.scrapy
|
| 72 |
+
|
| 73 |
+
# Sphinx documentation
|
| 74 |
+
docs/_build/
|
| 75 |
+
|
| 76 |
+
# PyBuilder
|
| 77 |
+
.pybuilder/
|
| 78 |
+
target/
|
| 79 |
+
|
| 80 |
+
# Jupyter Notebook
|
| 81 |
+
.ipynb_checkpoints
|
| 82 |
+
|
| 83 |
+
# IPython
|
| 84 |
+
profile_default/
|
| 85 |
+
ipython_config.py
|
| 86 |
+
|
| 87 |
+
# pyenv
|
| 88 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 89 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 90 |
+
# .python-version
|
| 91 |
+
|
| 92 |
+
# pipenv
|
| 93 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 94 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 95 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 96 |
+
# install all needed dependencies.
|
| 97 |
+
#Pipfile.lock
|
| 98 |
+
|
| 99 |
+
# poetry
|
| 100 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 101 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 102 |
+
# commonly ignored for libraries.
|
| 103 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 104 |
+
#poetry.lock
|
| 105 |
+
|
| 106 |
+
# pdm
|
| 107 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 108 |
+
#pdm.lock
|
| 109 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 110 |
+
# in version control.
|
| 111 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 112 |
+
.pdm.toml
|
| 113 |
+
|
| 114 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 115 |
+
__pypackages__/
|
| 116 |
+
|
| 117 |
+
# Celery stuff
|
| 118 |
+
celerybeat-schedule
|
| 119 |
+
celerybeat.pid
|
| 120 |
+
|
| 121 |
+
# SageMath parsed files
|
| 122 |
+
*.sage.py
|
| 123 |
+
|
| 124 |
+
# Environments
|
| 125 |
+
.env
|
| 126 |
+
.venv
|
| 127 |
+
env/
|
| 128 |
+
venv/
|
| 129 |
+
ENV/
|
| 130 |
+
env.bak/
|
| 131 |
+
venv.bak/
|
| 132 |
+
|
| 133 |
+
# Spyder project settings
|
| 134 |
+
.spyderproject
|
| 135 |
+
.spyproject
|
| 136 |
+
|
| 137 |
+
# Rope project settings
|
| 138 |
+
.ropeproject
|
| 139 |
+
|
| 140 |
+
# mkdocs documentation
|
| 141 |
+
/site
|
| 142 |
+
|
| 143 |
+
# mypy
|
| 144 |
+
.mypy_cache/
|
| 145 |
+
.dmypy.json
|
| 146 |
+
dmypy.json
|
| 147 |
+
|
| 148 |
+
# Pyre type checker
|
| 149 |
+
.pyre/
|
| 150 |
+
|
| 151 |
+
# pytype static type analyzer
|
| 152 |
+
.pytype/
|
| 153 |
+
|
| 154 |
+
# Cython debug symbols
|
| 155 |
+
cython_debug/
|
| 156 |
+
|
| 157 |
+
# PyCharm
|
| 158 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 159 |
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
| 160 |
+
# project, it is recommended to include the following files:
|
| 161 |
+
# .idea/
|
| 162 |
+
# *.iml
|
| 163 |
+
# *.ipr
|
| 164 |
+
# *.iws
|
| 165 |
+
.idea/
|
| 166 |
+
*.iml
|
| 167 |
+
*.ipr
|
| 168 |
+
*.iws
|
| 169 |
+
|
| 170 |
+
# VS Code
|
| 171 |
+
.vscode/
|
| 172 |
+
|
| 173 |
+
# Node.js (for frontend)
|
| 174 |
+
node_modules/
|
| 175 |
+
npm-debug.log*
|
| 176 |
+
yarn-debug.log*
|
| 177 |
+
yarn-error.log*
|
| 178 |
+
lerna-debug.log*
|
| 179 |
+
|
| 180 |
+
# Runtime data
|
| 181 |
+
pids
|
| 182 |
+
*.pid
|
| 183 |
+
*.seed
|
| 184 |
+
*.pid.lock
|
| 185 |
+
|
| 186 |
+
# Coverage directory used by tools like istanbul
|
| 187 |
+
coverage/
|
| 188 |
+
*.lcov
|
| 189 |
+
|
| 190 |
+
# nyc test coverage
|
| 191 |
+
.nyc_output
|
| 192 |
+
|
| 193 |
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
| 194 |
+
.grunt
|
| 195 |
+
|
| 196 |
+
# Bower dependency directory (https://bower.io/)
|
| 197 |
+
bower_components
|
| 198 |
+
|
| 199 |
+
# node-waf configuration
|
| 200 |
+
.lock-wscript
|
| 201 |
+
|
| 202 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
| 203 |
+
build/Release
|
| 204 |
+
|
| 205 |
+
# Dependency directories
|
| 206 |
+
jspm_packages/
|
| 207 |
+
|
| 208 |
+
# TypeScript v1 declaration files
|
| 209 |
+
typings/
|
| 210 |
+
|
| 211 |
+
# TypeScript cache
|
| 212 |
+
*.tsbuildinfo
|
| 213 |
+
|
| 214 |
+
# Optional npm cache directory
|
| 215 |
+
.npm
|
| 216 |
+
|
| 217 |
+
# Optional eslint cache
|
| 218 |
+
.eslintcache
|
| 219 |
+
|
| 220 |
+
# Optional stylelint cache
|
| 221 |
+
.stylelintcache
|
| 222 |
+
|
| 223 |
+
# Microbundle cache
|
| 224 |
+
.rpt2_cache/
|
| 225 |
+
.rts2_cache_cjs/
|
| 226 |
+
.rts2_cache_es/
|
| 227 |
+
.rts2_cache_umd/
|
| 228 |
+
|
| 229 |
+
# Optional REPL history
|
| 230 |
+
.node_repl_history
|
| 231 |
+
|
| 232 |
+
# Output of 'npm pack'
|
| 233 |
+
*.tgz
|
| 234 |
+
|
| 235 |
+
# Yarn Integrity file
|
| 236 |
+
.yarn-integrity
|
| 237 |
+
|
| 238 |
+
# dotenv environment variable files
|
| 239 |
+
.env.development.local
|
| 240 |
+
.env.test.local
|
| 241 |
+
.env.production.local
|
| 242 |
+
.env.local
|
| 243 |
+
|
| 244 |
+
# parcel-bundler cache (https://parceljs.org/)
|
| 245 |
+
.cache
|
| 246 |
+
.parcel-cache
|
| 247 |
+
|
| 248 |
+
# Next.js build output
|
| 249 |
+
.next
|
| 250 |
+
out
|
| 251 |
+
|
| 252 |
+
# Nuxt.js build / generate output
|
| 253 |
+
.nuxt
|
| 254 |
+
dist
|
| 255 |
+
|
| 256 |
+
# Gatsby files
|
| 257 |
+
.cache/
|
| 258 |
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
| 259 |
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
| 260 |
+
# public
|
| 261 |
+
|
| 262 |
+
# vuepress build output
|
| 263 |
+
.vuepress/dist
|
| 264 |
+
|
| 265 |
+
# vuepress v2.x temp and cache directory
|
| 266 |
+
.temp
|
| 267 |
+
.cache
|
| 268 |
+
|
| 269 |
+
# Docusaurus cache and generated files
|
| 270 |
+
.docusaurus
|
| 271 |
+
|
| 272 |
+
# Serverless directories
|
| 273 |
+
.serverless/
|
| 274 |
+
|
| 275 |
+
# FuseBox cache
|
| 276 |
+
.fusebox/
|
| 277 |
+
|
| 278 |
+
# DynamoDB Local files
|
| 279 |
+
.dynamodb/
|
| 280 |
+
|
| 281 |
+
# TernJS port file
|
| 282 |
+
.tern-port
|
| 283 |
+
|
| 284 |
+
# Stores VSCode versions used for testing VSCode extensions
|
| 285 |
+
.vscode-test
|
| 286 |
+
|
| 287 |
+
# yarn v2
|
| 288 |
+
.yarn/cache
|
| 289 |
+
.yarn/unplugged
|
| 290 |
+
.yarn/build-state.yml
|
| 291 |
+
.yarn/install-state.gz
|
| 292 |
+
.pnp.*
|
| 293 |
+
|
| 294 |
+
# OS generated files
|
| 295 |
+
.DS_Store
|
| 296 |
+
.DS_Store?
|
| 297 |
+
._*
|
| 298 |
+
.Spotlight-V100
|
| 299 |
+
.Trashes
|
| 300 |
+
ehthumbs.db
|
| 301 |
+
Thumbs.db
|
| 302 |
+
|
| 303 |
+
# Temporary files
|
| 304 |
+
*.tmp
|
| 305 |
+
*.temp
|
| 306 |
+
temp_exports/
|
| 307 |
+
|
| 308 |
+
# Logs
|
| 309 |
+
*.log
|
| 310 |
+
logs/
|
| 311 |
+
|
| 312 |
+
# Database files
|
| 313 |
+
*.db
|
| 314 |
+
*.sqlite
|
| 315 |
+
*.sqlite3
|
| 316 |
+
|
| 317 |
+
# Configuration files with sensitive data
|
| 318 |
+
config.ini
|
| 319 |
+
secrets.json
|
| 320 |
+
.env.local
|
| 321 |
+
.env.production
|
| 322 |
+
|
| 323 |
+
# Docker
|
| 324 |
+
.dockerignore
|
| 325 |
+
|
| 326 |
+
# Backup files
|
| 327 |
+
*.bak
|
| 328 |
+
*.backup
|
| 329 |
+
*~
|
| 330 |
+
|
| 331 |
+
# IDE and editor files
|
| 332 |
+
.vscode/
|
| 333 |
+
.idea/
|
| 334 |
+
*.swp
|
| 335 |
+
*.swo
|
| 336 |
+
*~
|
| 337 |
+
|
| 338 |
+
# FastAPI specific
|
| 339 |
+
.pytest_cache/
|
| 340 |
+
.coverage
|
| 341 |
+
htmlcov/
|
| 342 |
+
|
| 343 |
+
# Hugging Face specific
|
| 344 |
+
.cache/
|
| 345 |
+
transformers_cache/
|
README.md
CHANGED
|
@@ -10,3 +10,223 @@ short_description: Fast API Business Plan Generator
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 13 |
+
|
| 14 |
+
# Planify - Live PDF Generator
|
| 15 |
+
|
| 16 |
+
A real-time PDF generation system that updates automatically as content changes, providing an interactive experience for business plan creation.
|
| 17 |
+
|
| 18 |
+
## 🚀 Quick Start
|
| 19 |
+
|
| 20 |
+
### 1. Install Dependencies
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
pip install -r requirements.txt
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 2. Set Up Environment Variables
|
| 27 |
+
|
| 28 |
+
Create a `.env` file in the root directory with your API keys:
|
| 29 |
+
|
| 30 |
+
```env
|
| 31 |
+
OPENAI_API_KEY=your_openai_api_key_here
|
| 32 |
+
SUPABASE_URL=your_supabase_url_here
|
| 33 |
+
SUPABASE_KEY=your_supabase_key_here
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### 3. Start the Server
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
cd app
|
| 40 |
+
python main.py
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
The server will start on `http://127.0.0.1:8000`
|
| 44 |
+
|
| 45 |
+
### 4. Open the Live PDF Viewer
|
| 46 |
+
|
| 47 |
+
#### Option A: Use the HTML Example (Recommended)
|
| 48 |
+
1. Open `live_pdf_example.html` in your web browser
|
| 49 |
+
2. Click "Start Live Session" to begin
|
| 50 |
+
3. Type in the content editor and watch the PDF update in real-time
|
| 51 |
+
|
| 52 |
+
#### Option B: Use the API Directly
|
| 53 |
+
The live PDF viewer exposes these endpoints:
|
| 54 |
+
|
| 55 |
+
- `POST /live-pdf/start` - Start a new live session
|
| 56 |
+
- `POST /live-pdf/update/{session_id}` - Update content
|
| 57 |
+
- `GET /live-pdf/stream/{session_id}` - Stream PDF updates
|
| 58 |
+
- `DELETE /live-pdf/close/{session_id}` - Close session
|
| 59 |
+
|
| 60 |
+
## 🎯 Features
|
| 61 |
+
|
| 62 |
+
- **Real-time Updates**: PDFs update automatically as you type
|
| 63 |
+
- **Server-Sent Events (SSE)**: Efficient streaming updates without polling
|
| 64 |
+
- **Session Management**: Multiple concurrent live sessions supported
|
| 65 |
+
- **Auto-save**: Content changes trigger automatic PDF regeneration
|
| 66 |
+
- **Download Support**: Download the latest PDF version at any time
|
| 67 |
+
- **Error Handling**: Graceful error handling and recovery
|
| 68 |
+
|
| 69 |
+
## 📋 How to Use
|
| 70 |
+
|
| 71 |
+
### Using the HTML Interface
|
| 72 |
+
|
| 73 |
+
1. **Start the Server**
|
| 74 |
+
```bash
|
| 75 |
+
cd app
|
| 76 |
+
python main.py
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
2. **Open the HTML File**
|
| 80 |
+
- Navigate to the project root directory
|
| 81 |
+
- Double-click `live_pdf_example.html` or open it in your browser
|
| 82 |
+
- Or open: `file:///path/to/your/project/live_pdf_example.html`
|
| 83 |
+
|
| 84 |
+
3. **Start a Live Session**
|
| 85 |
+
- Click the "Start Live Session" button
|
| 86 |
+
- Wait for the "Session started successfully" message
|
| 87 |
+
|
| 88 |
+
4. **Edit Content**
|
| 89 |
+
- Type your business plan content in the text area
|
| 90 |
+
- The PDF will update automatically as you type
|
| 91 |
+
- Use Markdown formatting for better structure
|
| 92 |
+
|
| 93 |
+
5. **Download PDF**
|
| 94 |
+
- Click "Download PDF" to save the current version
|
| 95 |
+
- The PDF will contain your latest changes
|
| 96 |
+
|
| 97 |
+
6. **Stop Session**
|
| 98 |
+
- Click "Stop Session" when you're done
|
| 99 |
+
- This will clean up the server resources
|
| 100 |
+
|
| 101 |
+
### Using the API Directly
|
| 102 |
+
|
| 103 |
+
#### Start a Live Session
|
| 104 |
+
```bash
|
| 105 |
+
curl -X POST http://127.0.0.1:8000/live-pdf/start \
|
| 106 |
+
-H "Content-Type: application/json" \
|
| 107 |
+
-d '{
|
| 108 |
+
"businessPlanId": "test-plan-123",
|
| 109 |
+
"format": "pdf",
|
| 110 |
+
"sections": [
|
| 111 |
+
{
|
| 112 |
+
"key": "prompt_ExecutiveSummary",
|
| 113 |
+
"title": "Executive Summary",
|
| 114 |
+
"content": "# Executive Summary\n\nYour content here..."
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"chartImages": []
|
| 118 |
+
}'
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
#### Stream PDF Updates
|
| 122 |
+
```bash
|
| 123 |
+
curl -N http://127.0.0.1:8000/live-pdf/stream/{session_id}
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
#### Update Content
|
| 127 |
+
```bash
|
| 128 |
+
curl -X POST http://127.0.0.1:8000/live-pdf/update/{session_id} \
|
| 129 |
+
-H "Content-Type: application/json" \
|
| 130 |
+
-d '{
|
| 131 |
+
"businessPlanId": "test-plan-123",
|
| 132 |
+
"format": "pdf",
|
| 133 |
+
"sections": [
|
| 134 |
+
{
|
| 135 |
+
"key": "prompt_ExecutiveSummary",
|
| 136 |
+
"title": "Executive Summary",
|
| 137 |
+
"content": "# Updated Content\n\nNew content here..."
|
| 138 |
+
}
|
| 139 |
+
],
|
| 140 |
+
"chartImages": []
|
| 141 |
+
}'
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
## 🔧 Configuration
|
| 145 |
+
|
| 146 |
+
### Environment Variables
|
| 147 |
+
|
| 148 |
+
| Variable | Description | Required |
|
| 149 |
+
|----------|-------------|----------|
|
| 150 |
+
| `OPENAI_API_KEY` | Your OpenAI API key | Yes |
|
| 151 |
+
| `SUPABASE_URL` | Your Supabase project URL | Yes |
|
| 152 |
+
| `SUPABASE_KEY` | Your Supabase API key | Yes |
|
| 153 |
+
|
| 154 |
+
### Server Configuration
|
| 155 |
+
|
| 156 |
+
The server runs on `http://127.0.0.1:8000` by default. You can modify the port in `app/main.py`:
|
| 157 |
+
|
| 158 |
+
```python
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
import uvicorn
|
| 161 |
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
## 📁 Project Structure
|
| 165 |
+
|
| 166 |
+
```
|
| 167 |
+
planify/
|
| 168 |
+
├── app/
|
| 169 |
+
│ ├── main.py # FastAPI server with live PDF endpoints
|
| 170 |
+
│ ├── models.py # Data models
|
| 171 |
+
│ ├── utils.py # Utility functions
|
| 172 |
+
│ ├── constants.py # Constants and configurations
|
| 173 |
+
│ ├── CurrencyMapping/ # Currency mapping functionality
|
| 174 |
+
│ └── DocumentGeneration/ # PDF generation modules
|
| 175 |
+
├── frontend/ # Frontend components
|
| 176 |
+
├── live_pdf_example.html # Live PDF viewer interface
|
| 177 |
+
├── LIVE_PDF_README.md # Detailed API documentation
|
| 178 |
+
├── requirements.txt # Python dependencies
|
| 179 |
+
└── README.md # This file
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
## 🐛 Troubleshooting
|
| 183 |
+
|
| 184 |
+
### Common Issues
|
| 185 |
+
|
| 186 |
+
1. **Server won't start**
|
| 187 |
+
- Check if all dependencies are installed: `pip install -r requirements.txt`
|
| 188 |
+
- Verify your `.env` file has the correct API keys
|
| 189 |
+
- Check if port 8000 is available
|
| 190 |
+
|
| 191 |
+
2. **PDF not updating**
|
| 192 |
+
- Ensure the server is running
|
| 193 |
+
- Check browser console for errors
|
| 194 |
+
- Verify the session ID is correct
|
| 195 |
+
|
| 196 |
+
3. **Connection errors**
|
| 197 |
+
- Make sure the server URL in the HTML file matches your server
|
| 198 |
+
- Check CORS settings if accessing from a different domain
|
| 199 |
+
|
| 200 |
+
### Debug Mode
|
| 201 |
+
|
| 202 |
+
To run the server in debug mode:
|
| 203 |
+
|
| 204 |
+
```bash
|
| 205 |
+
cd app
|
| 206 |
+
python main.py --debug
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
## 📚 API Documentation
|
| 210 |
+
|
| 211 |
+
For detailed API documentation, see `LIVE_PDF_README.md`.
|
| 212 |
+
|
| 213 |
+
## 🤝 Contributing
|
| 214 |
+
|
| 215 |
+
1. Fork the repository
|
| 216 |
+
2. Create a feature branch
|
| 217 |
+
3. Make your changes
|
| 218 |
+
4. Test thoroughly
|
| 219 |
+
5. Submit a pull request
|
| 220 |
+
|
| 221 |
+
## 📄 License
|
| 222 |
+
|
| 223 |
+
This project is licensed under the MIT License.
|
| 224 |
+
|
| 225 |
+
## 🆘 Support
|
| 226 |
+
|
| 227 |
+
If you encounter any issues:
|
| 228 |
+
|
| 229 |
+
1. Check the troubleshooting section above
|
| 230 |
+
2. Review the API documentation in `LIVE_PDF_README.md`
|
| 231 |
+
3. Check the server logs for error messages
|
| 232 |
+
4. Open an issue on GitHub with detailed error information
|
app/DocumentGeneration/__init__.py
CHANGED
|
@@ -17,7 +17,6 @@ from document_helpers import (
|
|
| 17 |
extract_business_name_from_content,
|
| 18 |
filter_empty_sections,
|
| 19 |
extract_subheadings,
|
| 20 |
-
enhance_subheadings_with_bold,
|
| 21 |
remove_duplicate_headings
|
| 22 |
)
|
| 23 |
from grouped import (
|
|
@@ -33,10 +32,6 @@ from markdown_renderer import (
|
|
| 33 |
render_markdown_to_docx_grouped,
|
| 34 |
clean_text_for_docx
|
| 35 |
)
|
| 36 |
-
from mixed import (
|
| 37 |
-
make_sentences_ending_with_colon_bold,
|
| 38 |
-
process_bold_text_for_docx
|
| 39 |
-
)
|
| 40 |
|
| 41 |
__all__ = [
|
| 42 |
'create_pdf_document',
|
|
@@ -51,14 +46,11 @@ __all__ = [
|
|
| 51 |
'parse_content_into_groups',
|
| 52 |
'extract_key_phrases',
|
| 53 |
'get_content_groups_info',
|
| 54 |
-
'enhance_subheadings_with_bold',
|
| 55 |
'remove_duplicate_headings',
|
| 56 |
'clean_text_for_pdf',
|
| 57 |
'render_markdown_to_pdf',
|
| 58 |
'render_markdown_to_pdf_grouped',
|
| 59 |
'render_markdown_to_docx',
|
| 60 |
'render_markdown_to_docx_grouped',
|
| 61 |
-
'
|
| 62 |
-
'clean_text_for_docx',
|
| 63 |
-
'make_sentences_ending_with_colon_bold'
|
| 64 |
]
|
|
|
|
| 17 |
extract_business_name_from_content,
|
| 18 |
filter_empty_sections,
|
| 19 |
extract_subheadings,
|
|
|
|
| 20 |
remove_duplicate_headings
|
| 21 |
)
|
| 22 |
from grouped import (
|
|
|
|
| 32 |
render_markdown_to_docx_grouped,
|
| 33 |
clean_text_for_docx
|
| 34 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
__all__ = [
|
| 37 |
'create_pdf_document',
|
|
|
|
| 46 |
'parse_content_into_groups',
|
| 47 |
'extract_key_phrases',
|
| 48 |
'get_content_groups_info',
|
|
|
|
| 49 |
'remove_duplicate_headings',
|
| 50 |
'clean_text_for_pdf',
|
| 51 |
'render_markdown_to_pdf',
|
| 52 |
'render_markdown_to_pdf_grouped',
|
| 53 |
'render_markdown_to_docx',
|
| 54 |
'render_markdown_to_docx_grouped',
|
| 55 |
+
'clean_text_for_docx'
|
|
|
|
|
|
|
| 56 |
]
|
app/DocumentGeneration/chart_generator.py
CHANGED
|
@@ -11,7 +11,7 @@ from typing import List, Dict, Any, Optional
|
|
| 11 |
|
| 12 |
# Add the parent directory to sys.path to import from models.py
|
| 13 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 14 |
-
from
|
| 15 |
|
| 16 |
def parse_embedded_chart_data(sections: List[ExportSection]) -> List[Dict[str, Any]]:
|
| 17 |
"""Parse chart data embedded in section content using CHARTDATA markers"""
|
|
|
|
| 11 |
|
| 12 |
# Add the parent directory to sys.path to import from models.py
|
| 13 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 14 |
+
from models import ExportSection
|
| 15 |
|
| 16 |
def parse_embedded_chart_data(sections: List[ExportSection]) -> List[Dict[str, Any]]:
|
| 17 |
"""Parse chart data embedded in section content using CHARTDATA markers"""
|
app/DocumentGeneration/document_helpers.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import List, Dict, Any
|
|
| 8 |
|
| 9 |
# Add the parent directory to sys.path to import from models.py
|
| 10 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 11 |
-
from
|
| 12 |
|
| 13 |
def extract_business_name_from_content(sections: List[ExportSection]) -> str:
|
| 14 |
"""Extract business name from markdown content"""
|
|
@@ -97,43 +97,6 @@ def filter_empty_sections(sections: List[ExportSection]) -> List[ExportSection]:
|
|
| 97 |
return filtered_sections
|
| 98 |
|
| 99 |
|
| 100 |
-
def enhance_subheadings_with_bold(content: str) -> str:
|
| 101 |
-
"""Enhance subheadings with bold formatting and ensure no duplicates"""
|
| 102 |
-
if not content:
|
| 103 |
-
return content
|
| 104 |
-
|
| 105 |
-
lines = content.split('\n')
|
| 106 |
-
enhanced_lines = []
|
| 107 |
-
seen_headings = set()
|
| 108 |
-
|
| 109 |
-
for line in lines:
|
| 110 |
-
line = line.strip()
|
| 111 |
-
if not line:
|
| 112 |
-
enhanced_lines.append('')
|
| 113 |
-
continue
|
| 114 |
-
|
| 115 |
-
# Check if this is a subheading (## or ###)
|
| 116 |
-
if line.startswith('##') and not line.startswith('###'):
|
| 117 |
-
# Extract the heading text
|
| 118 |
-
heading_text = re.sub(r'^##+\s*', '', line)
|
| 119 |
-
# heading_text = re.sub(r'[*_`~]', '', heading_text).strip() # Commented out to preserve ** symbols
|
| 120 |
-
heading_text = heading_text.strip()
|
| 121 |
-
|
| 122 |
-
# Check for duplicates
|
| 123 |
-
if heading_text.lower() in seen_headings:
|
| 124 |
-
continue
|
| 125 |
-
|
| 126 |
-
seen_headings.add(heading_text.lower())
|
| 127 |
-
|
| 128 |
-
# Make it bold and ensure proper formatting
|
| 129 |
-
enhanced_line = f"**{heading_text}**"
|
| 130 |
-
enhanced_lines.append(enhanced_line)
|
| 131 |
-
else:
|
| 132 |
-
enhanced_lines.append(line)
|
| 133 |
-
|
| 134 |
-
return '\n'.join(enhanced_lines)
|
| 135 |
-
|
| 136 |
-
|
| 137 |
def remove_duplicate_headings(content: str) -> str:
|
| 138 |
"""Remove duplicate headings from content"""
|
| 139 |
if not content:
|
|
|
|
| 8 |
|
| 9 |
# Add the parent directory to sys.path to import from models.py
|
| 10 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 11 |
+
from models import ExportSection
|
| 12 |
|
| 13 |
def extract_business_name_from_content(sections: List[ExportSection]) -> str:
|
| 14 |
"""Extract business name from markdown content"""
|
|
|
|
| 97 |
return filtered_sections
|
| 98 |
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def remove_duplicate_headings(content: str) -> str:
|
| 101 |
"""Remove duplicate headings from content"""
|
| 102 |
if not content:
|
app/DocumentGeneration/live_pdf_generator.py
CHANGED
|
@@ -13,7 +13,7 @@ import sys
|
|
| 13 |
import os
|
| 14 |
sys.path.append(os.path.dirname(__file__))
|
| 15 |
from pdf_generator import create_pdf_document
|
| 16 |
-
from
|
| 17 |
|
| 18 |
class LivePDFGenerator:
|
| 19 |
"""Generates and streams PDF updates in real-time"""
|
|
|
|
| 13 |
import os
|
| 14 |
sys.path.append(os.path.dirname(__file__))
|
| 15 |
from pdf_generator import create_pdf_document
|
| 16 |
+
from models import ExportRequest
|
| 17 |
|
| 18 |
class LivePDFGenerator:
|
| 19 |
"""Generates and streams PDF updates in real-time"""
|
app/DocumentGeneration/markdown_renderer.py
CHANGED
|
@@ -9,7 +9,6 @@ from typing import List, Dict, Any, Optional
|
|
| 9 |
|
| 10 |
# Import from the same directory (Document Generation)
|
| 11 |
from grouped import parse_content_into_groups, should_break_page_for_group
|
| 12 |
-
from mixed import _render_mixed_text_to_pdf, process_bold_text_for_docx
|
| 13 |
|
| 14 |
def clean_markdown_text(text: str) -> str:
|
| 15 |
"""Clean markdown formatting for PDF generation - PRESERVE HEADINGS"""
|
|
@@ -346,17 +345,21 @@ def render_markdown_to_pdf_grouped(pdf, content: str, primary_color, accent_colo
|
|
| 346 |
|
| 347 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 348 |
# Main heading style
|
|
|
|
|
|
|
| 349 |
pdf.set_xy(25, current_y)
|
| 350 |
-
|
| 351 |
-
current_y +=
|
| 352 |
pdf.set_xy(25, current_y)
|
| 353 |
pdf.ln(6)
|
| 354 |
current_y = pdf.get_y()
|
| 355 |
else:
|
| 356 |
# Regular bold text
|
|
|
|
|
|
|
| 357 |
pdf.set_xy(25, current_y)
|
| 358 |
-
|
| 359 |
-
current_y +=
|
| 360 |
pdf.set_xy(25, current_y)
|
| 361 |
current_y = pdf.get_y()
|
| 362 |
|
|
@@ -467,10 +470,12 @@ def render_markdown_to_docx_grouped(doc, content: str):
|
|
| 467 |
doc.add_heading(text, level=4)
|
| 468 |
elif subheading_type == 'bold':
|
| 469 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 470 |
-
paragraph
|
|
|
|
| 471 |
paragraph.style = doc.styles['Heading 1'] # Apply heading style
|
| 472 |
else:
|
| 473 |
-
|
|
|
|
| 474 |
elif subheading_type == 'numbered':
|
| 475 |
doc.add_heading(text, level=2)
|
| 476 |
|
|
|
|
| 9 |
|
| 10 |
# Import from the same directory (Document Generation)
|
| 11 |
from grouped import parse_content_into_groups, should_break_page_for_group
|
|
|
|
| 12 |
|
| 13 |
def clean_markdown_text(text: str) -> str:
|
| 14 |
"""Clean markdown formatting for PDF generation - PRESERVE HEADINGS"""
|
|
|
|
| 345 |
|
| 346 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 347 |
# Main heading style
|
| 348 |
+
pdf.set_font("Arial", "B", 12) # Bold, size 12
|
| 349 |
+
pdf.set_text_color(*primary_color) # Black
|
| 350 |
pdf.set_xy(25, current_y)
|
| 351 |
+
pdf.multi_cell(160, 8, text) # Multi-cell for text
|
| 352 |
+
current_y += 8 # Move down for next line
|
| 353 |
pdf.set_xy(25, current_y)
|
| 354 |
pdf.ln(6)
|
| 355 |
current_y = pdf.get_y()
|
| 356 |
else:
|
| 357 |
# Regular bold text
|
| 358 |
+
pdf.set_font("Arial", "B", 12) # Bold, size 12
|
| 359 |
+
pdf.set_text_color(*text_color) # Dark gray
|
| 360 |
pdf.set_xy(25, current_y)
|
| 361 |
+
pdf.multi_cell(160, 8, text) # Multi-cell for text
|
| 362 |
+
current_y += 8 # Move down for next line
|
| 363 |
pdf.set_xy(25, current_y)
|
| 364 |
current_y = pdf.get_y()
|
| 365 |
|
|
|
|
| 470 |
doc.add_heading(text, level=4)
|
| 471 |
elif subheading_type == 'bold':
|
| 472 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 473 |
+
# Simple paragraph with bold text
|
| 474 |
+
paragraph = doc.add_paragraph(text)
|
| 475 |
paragraph.style = doc.styles['Heading 1'] # Apply heading style
|
| 476 |
else:
|
| 477 |
+
# Regular paragraph
|
| 478 |
+
doc.add_paragraph(text)
|
| 479 |
elif subheading_type == 'numbered':
|
| 480 |
doc.add_heading(text, level=2)
|
| 481 |
|
app/DocumentGeneration/mixed.py
CHANGED
|
@@ -1,91 +1,6 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
"""
|
| 8 |
-
if not content:
|
| 9 |
-
return content
|
| 10 |
-
|
| 11 |
-
lines = content.split('\n')
|
| 12 |
-
processed_lines = []
|
| 13 |
-
|
| 14 |
-
for line in lines:
|
| 15 |
-
if not line.strip():
|
| 16 |
-
processed_lines.append(line)
|
| 17 |
-
continue
|
| 18 |
-
|
| 19 |
-
last_colon_index = line.rfind(':')
|
| 20 |
-
|
| 21 |
-
if last_colon_index != -1:
|
| 22 |
-
text_before_colon = line[:last_colon_index].strip()
|
| 23 |
-
text_after_colon = line[last_colon_index:].strip()
|
| 24 |
-
|
| 25 |
-
if not (text_before_colon.startswith('**') and text_before_colon.endswith('**')):
|
| 26 |
-
processed_lines.append(f"**{text_before_colon}**{text_after_colon}")
|
| 27 |
-
else:
|
| 28 |
-
processed_lines.append(line)
|
| 29 |
-
else:
|
| 30 |
-
processed_lines.append(line)
|
| 31 |
-
|
| 32 |
-
return '\n'.join(processed_lines)
|
| 33 |
-
|
| 34 |
-
def _render_mixed_text_to_pdf(pdf, line: str, current_x: float, current_y: float, line_height: float, text_color: tuple, bold_font_size: int = 12, normal_font_size: int = 12):
|
| 35 |
-
"""Helper to render a line with mixed bold and normal text using pdf.cell"""
|
| 36 |
-
parts = re.split(r'(\*\*.*?\*\*)', line)
|
| 37 |
-
|
| 38 |
-
for part in parts:
|
| 39 |
-
if part.startswith('**') and part.endswith('**'):
|
| 40 |
-
bold_text = part[2:-2] # Remove ** markers
|
| 41 |
-
pdf.set_font("Arial", "B", bold_font_size)
|
| 42 |
-
pdf.set_text_color(*text_color)
|
| 43 |
-
pdf.set_xy(current_x, current_y)
|
| 44 |
-
text_width = pdf.get_string_width(bold_text)
|
| 45 |
-
pdf.cell(text_width, line_height, bold_text)
|
| 46 |
-
current_x += text_width
|
| 47 |
-
else:
|
| 48 |
-
if part:
|
| 49 |
-
pdf.set_font("Arial", "", normal_font_size)
|
| 50 |
-
pdf.set_text_color(*text_color)
|
| 51 |
-
pdf.set_xy(current_x, current_y)
|
| 52 |
-
text_width = pdf.get_string_width(part)
|
| 53 |
-
pdf.cell(text_width, line_height, part)
|
| 54 |
-
current_x += text_width
|
| 55 |
-
return current_x
|
| 56 |
-
|
| 57 |
-
def clean_bold_text_for_pdf(text: str) -> str:
|
| 58 |
-
"""Clean bold text for PDF rendering by removing literal asterisks and converting to proper format"""
|
| 59 |
-
# Handle cases where asterisks are literal characters instead of markdown
|
| 60 |
-
# Convert patterns like "*Business Name:**" to "**Business Name:**"
|
| 61 |
-
|
| 62 |
-
# First, handle the specific pattern we're seeing: "*Business Name:**" -> "**Business Name:**"
|
| 63 |
-
text = re.sub(r'\*([^*]+)\*\*:', r'**\1**:', text)
|
| 64 |
-
|
| 65 |
-
# Handle other similar patterns
|
| 66 |
-
text = re.sub(r'\*([^*]+)\*\*', r'**\1**', text)
|
| 67 |
-
|
| 68 |
-
# Clean up any remaining single asterisks that shouldn't be there
|
| 69 |
-
text = re.sub(r'(?<!\*)\*(?!\*)', '', text) # Remove single asterisks not part of **
|
| 70 |
-
|
| 71 |
-
return text
|
| 72 |
-
|
| 73 |
-
def process_bold_text_for_docx(text: str, doc):
|
| 74 |
-
"""Process bold text markers (**text**) in content for Word documents"""
|
| 75 |
-
# For Word documents, we can actually render bold text properly
|
| 76 |
-
# Split text by ** markers and create runs with appropriate formatting
|
| 77 |
-
parts = re.split(r'(\*\*.*?\*\*)', text)
|
| 78 |
-
paragraph = doc.add_paragraph()
|
| 79 |
-
|
| 80 |
-
for part in parts:
|
| 81 |
-
if part.startswith('**') and part.endswith('**'):
|
| 82 |
-
# This is bold text
|
| 83 |
-
bold_text = part # Keep ** markers
|
| 84 |
-
run = paragraph.add_run(bold_text)
|
| 85 |
-
run.bold = True
|
| 86 |
-
else:
|
| 87 |
-
# Regular text
|
| 88 |
-
if part: # Only add non-empty parts
|
| 89 |
-
paragraph.add_run(part)
|
| 90 |
-
|
| 91 |
-
return paragraph
|
|
|
|
| 1 |
+
# This file has been removed as all its functions are not present in maintest.txt
|
| 2 |
+
# Functions removed:
|
| 3 |
+
# - make_sentences_ending_with_colon_bold()
|
| 4 |
+
# - _render_mixed_text_to_pdf()
|
| 5 |
+
# - clean_bold_text_for_pdf()
|
| 6 |
+
# - process_bold_text_for_docx()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/DocumentGeneration/pdf_generator.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import List, Dict, Any
|
|
| 10 |
|
| 11 |
# Add the parent directory to sys.path to import from models.py
|
| 12 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 13 |
-
from
|
| 14 |
|
| 15 |
# Import from the same directory (Document Generation)
|
| 16 |
from document_helpers import (
|
|
@@ -24,16 +24,15 @@ from markdown_renderer import (
|
|
| 24 |
clean_text_for_pdf,
|
| 25 |
render_markdown_to_pdf_grouped
|
| 26 |
)
|
| 27 |
-
from mixed import make_sentences_ending_with_colon_bold
|
| 28 |
|
| 29 |
# Add Currency Mapping path
|
| 30 |
currency_mapping_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Currency Mapping')
|
| 31 |
if currency_mapping_path not in sys.path:
|
| 32 |
sys.path.append(currency_mapping_path)
|
| 33 |
-
from
|
| 34 |
|
| 35 |
# Import sort_sections_by_order from utils module
|
| 36 |
-
from
|
| 37 |
|
| 38 |
def create_pdf_document(request: ExportRequest):
|
| 39 |
"""Create a PROFESSIONAL PDF document from the business plan data using FPDF2
|
|
@@ -301,9 +300,6 @@ def create_pdf_document(request: ExportRequest):
|
|
| 301 |
# content_text = enhance_subheadings_with_bold(content_text)
|
| 302 |
content_text = remove_duplicate_headings(content_text)
|
| 303 |
|
| 304 |
-
# Make text before colons bold
|
| 305 |
-
content_text = make_sentences_ending_with_colon_bold(content_text)
|
| 306 |
-
|
| 307 |
# Add content with proper markdown rendering using grouped approach
|
| 308 |
if content_text and content_text.strip():
|
| 309 |
render_markdown_to_pdf_grouped(pdf, content_text, primary_color, accent_color, text_color)
|
|
|
|
| 10 |
|
| 11 |
# Add the parent directory to sys.path to import from models.py
|
| 12 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 13 |
+
from models import ExportRequest
|
| 14 |
|
| 15 |
# Import from the same directory (Document Generation)
|
| 16 |
from document_helpers import (
|
|
|
|
| 24 |
clean_text_for_pdf,
|
| 25 |
render_markdown_to_pdf_grouped
|
| 26 |
)
|
|
|
|
| 27 |
|
| 28 |
# Add Currency Mapping path
|
| 29 |
currency_mapping_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Currency Mapping')
|
| 30 |
if currency_mapping_path not in sys.path:
|
| 31 |
sys.path.append(currency_mapping_path)
|
| 32 |
+
from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
|
| 33 |
|
| 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
|
|
|
|
| 300 |
# content_text = enhance_subheadings_with_bold(content_text)
|
| 301 |
content_text = remove_duplicate_headings(content_text)
|
| 302 |
|
|
|
|
|
|
|
|
|
|
| 303 |
# Add content with proper markdown rendering using grouped approach
|
| 304 |
if content_text and content_text.strip():
|
| 305 |
render_markdown_to_pdf_grouped(pdf, content_text, primary_color, accent_color, text_color)
|
app/DocumentGeneration/table_of_content.py
CHANGED
|
@@ -8,8 +8,8 @@ from typing import List, Dict, Any
|
|
| 8 |
|
| 9 |
# Add the parent directory to sys.path to import from models.py and constants.py
|
| 10 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 11 |
-
from
|
| 12 |
-
from
|
| 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"""
|
|
|
|
| 8 |
|
| 9 |
# Add the parent directory to sys.path to import from models.py and constants.py
|
| 10 |
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"""
|
app/DocumentGeneration/word_generator.py
CHANGED
|
@@ -11,23 +11,22 @@ from typing import List, Dict, Any
|
|
| 11 |
|
| 12 |
# Add the parent directory to sys.path to import from models.py
|
| 13 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 14 |
-
from
|
| 15 |
|
| 16 |
# Import from the same directory (Document Generation)
|
| 17 |
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
|
| 25 |
)
|
| 26 |
-
from mixed import process_bold_text_for_docx
|
| 27 |
|
| 28 |
# Add Currency Mapping path
|
| 29 |
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Currency Mapping'))
|
| 30 |
-
from currency_mapping import convert_currency_symbols_to_iso
|
| 31 |
|
| 32 |
def create_word_document(request: ExportRequest):
|
| 33 |
"""Create a Word document from the business plan data with front page, TOC, and proper page breaks"""
|
|
@@ -245,9 +244,6 @@ def create_word_document(request: ExportRequest):
|
|
| 245 |
filtered_lines.append(line)
|
| 246 |
clean_content = '\n'.join(filtered_lines)
|
| 247 |
|
| 248 |
-
# Make text before colons bold
|
| 249 |
-
clean_content = make_sentences_ending_with_colon_bold(clean_content)
|
| 250 |
-
|
| 251 |
# Render markdown content with proper heading styling using grouped approach
|
| 252 |
render_markdown_to_docx_grouped(doc, clean_content)
|
| 253 |
|
|
|
|
| 11 |
|
| 12 |
# Add the parent directory to sys.path to import from models.py
|
| 13 |
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
| 14 |
+
from models import ExportRequest
|
| 15 |
|
| 16 |
# Import from the same directory (Document Generation)
|
| 17 |
from document_helpers import (
|
| 18 |
filter_empty_sections,
|
| 19 |
extract_business_name_from_content
|
| 20 |
)
|
| 21 |
+
from DocumentGeneration.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
|
| 25 |
)
|
|
|
|
| 26 |
|
| 27 |
# Add Currency Mapping path
|
| 28 |
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Currency Mapping'))
|
| 29 |
+
from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
|
| 30 |
|
| 31 |
def create_word_document(request: ExportRequest):
|
| 32 |
"""Create a Word document from the business plan data with front page, TOC, and proper page breaks"""
|
|
|
|
| 244 |
filtered_lines.append(line)
|
| 245 |
clean_content = '\n'.join(filtered_lines)
|
| 246 |
|
|
|
|
|
|
|
|
|
|
| 247 |
# Render markdown content with proper heading styling using grouped approach
|
| 248 |
render_markdown_to_docx_grouped(doc, clean_content)
|
| 249 |
|
app/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# App Package
|
| 2 |
+
# This file makes the app directory a proper Python package
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
|
| 7 |
+
# Add the current directory to sys.path
|
| 8 |
+
current_dir = os.path.dirname(__file__)
|
| 9 |
+
if current_dir not in sys.path:
|
| 10 |
+
sys.path.insert(0, current_dir)
|
| 11 |
+
|
| 12 |
+
# Import main modules for easy access
|
| 13 |
+
from models import ExportRequest, ExportSection, ExportResponse, SectionJob, BatchGenerateRequest, GenerateRequest, CreatePlanRequest
|
| 14 |
+
from constants import SECTION_ORDER, SECTION_MAP, SECTION_COLUMNS
|
| 15 |
+
from utils import sort_sections_by_order
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
'ExportRequest',
|
| 19 |
+
'ExportSection',
|
| 20 |
+
'ExportResponse',
|
| 21 |
+
'SectionJob',
|
| 22 |
+
'BatchGenerateRequest',
|
| 23 |
+
'GenerateRequest',
|
| 24 |
+
'CreatePlanRequest',
|
| 25 |
+
'SECTION_ORDER',
|
| 26 |
+
'SECTION_MAP',
|
| 27 |
+
'SECTION_COLUMNS',
|
| 28 |
+
'sort_sections_by_order'
|
| 29 |
+
]
|
app/__pycache__/constants.cpython-311.pyc
DELETED
|
Binary file (1.41 kB)
|
|
|
app/__pycache__/main.cpython-311.pyc
DELETED
|
Binary file (80.7 kB)
|
|
|
app/__pycache__/models.cpython-311.pyc
DELETED
|
Binary file (3.98 kB)
|
|
|
app/__pycache__/utils.cpython-311.pyc
DELETED
|
Binary file (2.42 kB)
|
|
|
app/main.py
CHANGED
|
@@ -94,40 +94,39 @@ import sys
|
|
| 94 |
import os
|
| 95 |
|
| 96 |
# Add the Document Generation and Currency Mapping paths to sys.path
|
| 97 |
-
sys.path.append(os.path.join(os.path.dirname(__file__), '
|
| 98 |
-
sys.path.append(os.path.join(os.path.dirname(__file__), '
|
| 99 |
|
| 100 |
# Import models
|
| 101 |
-
from
|
| 102 |
|
| 103 |
# Import constants
|
| 104 |
-
from
|
| 105 |
|
| 106 |
# Import utilities
|
| 107 |
-
from
|
| 108 |
|
| 109 |
# Now import the modules
|
| 110 |
import sys
|
| 111 |
import os
|
| 112 |
|
| 113 |
# Add the Document Generation and Currency Mapping paths to sys.path
|
| 114 |
-
sys.path.append(os.path.join(os.path.dirname(__file__), '
|
| 115 |
-
sys.path.append(os.path.join(os.path.dirname(__file__), '
|
| 116 |
-
|
| 117 |
-
from
|
| 118 |
-
from
|
| 119 |
-
from
|
| 120 |
-
from
|
| 121 |
-
from
|
| 122 |
-
from
|
| 123 |
extract_business_name_from_content,
|
| 124 |
filter_empty_sections,
|
| 125 |
extract_subheadings,
|
| 126 |
-
enhance_subheadings_with_bold,
|
| 127 |
remove_duplicate_headings
|
| 128 |
)
|
| 129 |
-
from
|
| 130 |
-
from
|
| 131 |
clean_text_for_pdf,
|
| 132 |
render_markdown_to_pdf,
|
| 133 |
render_markdown_to_pdf_grouped,
|
|
@@ -135,8 +134,7 @@ from app.DocumentGeneration.markdown_renderer import (
|
|
| 135 |
render_markdown_to_docx_grouped,
|
| 136 |
clean_text_for_docx
|
| 137 |
)
|
| 138 |
-
from
|
| 139 |
-
from app.CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
|
| 140 |
|
| 141 |
# ──────────────────────────────────────────────────────────────────────────────
|
| 142 |
# Logging
|
|
|
|
| 94 |
import os
|
| 95 |
|
| 96 |
# Add the Document Generation and Currency Mapping paths to sys.path
|
| 97 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'DocumentGeneration'))
|
| 98 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'CurrencyMapping'))
|
| 99 |
|
| 100 |
# Import models
|
| 101 |
+
from models import ExportRequest, ExportSection, ExportResponse, SectionJob, BatchGenerateRequest, GenerateRequest, CreatePlanRequest
|
| 102 |
|
| 103 |
# Import constants
|
| 104 |
+
from constants import SECTION_ORDER, SECTION_MAP, SECTION_COLUMNS
|
| 105 |
|
| 106 |
# Import utilities
|
| 107 |
+
from utils import sort_sections_by_order
|
| 108 |
|
| 109 |
# Now import the modules
|
| 110 |
import sys
|
| 111 |
import os
|
| 112 |
|
| 113 |
# Add the Document Generation and Currency Mapping paths to sys.path
|
| 114 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'DocumentGeneration'))
|
| 115 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'CurrencyMapping'))
|
| 116 |
+
|
| 117 |
+
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,
|
| 125 |
extract_subheadings,
|
|
|
|
| 126 |
remove_duplicate_headings
|
| 127 |
)
|
| 128 |
+
from DocumentGeneration.grouped import parse_content_into_groups, extract_key_phrases, get_content_groups_info
|
| 129 |
+
from DocumentGeneration.markdown_renderer import (
|
| 130 |
clean_text_for_pdf,
|
| 131 |
render_markdown_to_pdf,
|
| 132 |
render_markdown_to_pdf_grouped,
|
|
|
|
| 134 |
render_markdown_to_docx_grouped,
|
| 135 |
clean_text_for_docx
|
| 136 |
)
|
| 137 |
+
from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
|
|
|
|
| 138 |
|
| 139 |
# ──────────────────────────────────────────────────────────────────────────────
|
| 140 |
# Logging
|
app/utils.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
| 2 |
# This file helps break circular imports between main.py and pdf_generator.py
|
| 3 |
|
| 4 |
from typing import List
|
| 5 |
-
from
|
| 6 |
-
from
|
| 7 |
|
| 8 |
def sort_sections_by_order(sections: List[ExportSection]) -> List[ExportSection]:
|
| 9 |
"""Sort sections according to the predefined SECTION_ORDER to ensure correct numerical ordering"""
|
|
|
|
| 2 |
# This file helps break circular imports between main.py and pdf_generator.py
|
| 3 |
|
| 4 |
from typing import List
|
| 5 |
+
from models import ExportSection
|
| 6 |
+
from constants import SECTION_ORDER
|
| 7 |
|
| 8 |
def sort_sections_by_order(sections: List[ExportSection]) -> List[ExportSection]:
|
| 9 |
"""Sort sections according to the predefined SECTION_ORDER to ensure correct numerical ordering"""
|
bp _template.txt
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Executive Summary
|
| 2 |
+
|
| 3 |
+
### 1.1 Business Name, Tagline & Logo Reference
|
| 4 |
+
- **Business Name:** Desert Innovate
|
| 5 |
+
- **Tagline:** Empowering UAE Growth Through Innovation
|
| 6 |
+
- **Logo Concept:** A sleek, modern falcon silhouette in gold and deep green, symbolizing strength and prosperity, set against a minimalist desert dune backdrop to reflect the UAE’s heritage and forward-looking vision.
|
| 7 |
+
|
| 8 |
+
### 1.2 Business Concept Overview
|
| 9 |
+
Desert Innovate provides tailored business solutions and training workshops designed for startups and SMEs across the United Arab Emirates. Our core offering includes interactive demonstrations and hands-on workshops that help local entrepreneurs and businesses adopt innovative tools and strategies to enhance their competitiveness in the dynamic UAE market. We focus on practical, culturally relevant approaches that align with the country’s rapid economic diversification and digital transformation goals.
|
| 10 |
+
|
| 11 |
+
### 1.3 Problem Statement
|
| 12 |
+
Many UAE businesses face challenges in adopting the latest technologies and innovative business practices due to a lack of accessible, culturally relevant training and demonstration opportunities. Existing solutions are often generic or imported without adaptation, leaving a gap in effective knowledge transfer and practical application tailored to the unique UAE business environment.
|
| 13 |
+
|
| 14 |
+
### 1.4 Solution Summary
|
| 15 |
+
Desert Innovate addresses these challenges by offering free, engaging workshops and demonstrations that are customized to the needs and cultural context of UAE businesses.
|
| 16 |
+
- Hands-on learning experiences that build confidence and competence
|
| 17 |
+
- Culturally aligned content that resonates with local business practices
|
| 18 |
+
- Direct engagement to foster lasting partnerships and customer loyalty
|
| 19 |
+
|
| 20 |
+
### 1.5 Vision and Mission Statement
|
| 21 |
+
**Vision:** To be the leading catalyst for innovation-driven business growth across the UAE, empowering enterprises to thrive in a competitive global economy.
|
| 22 |
+
**Mission:** Desert Innovate is committed to providing accessible and culturally tailored innovation workshops and solutions that enable UAE businesses to harness new technologies and strategies, supporting the nation’s vision for economic diversification and sustainable development.
|
| 23 |
+
|
| 24 |
+
### 1.6 Financial Snapshot and Funding Requirement
|
| 25 |
+
Projected revenues are expected to range between د.إ 500,000 and د.إ 750,000 in the first year, driven primarily by workshop fees and consulting engagements. Major cost drivers include marketing, content development, and facilitator salaries, with an anticipated profit margin of approximately 25% as operational efficiencies improve.
|
| 26 |
+
We seek د.إ 200,000 in funding to accelerate development, expand marketing efforts, and scale workshop delivery across key UAE cities. The investment will be strategically allocated to customer acquisition, content localization, and technology infrastructure.
|
| 27 |
+
|
| 28 |
+
### 1.7 Key Milestones and Traction to Date
|
| 29 |
+
- Developed initial workshop curriculum tailored for UAE SMEs (Q1 2024)
|
| 30 |
+
- Conducted pilot workshops with 50+ participants, receiving positive feedback (Q2 2024)
|
| 31 |
+
- Established partnerships with two local business councils for outreach support (Q2 2024)
|
| 32 |
+
- Secured د.إ 50,000 in initial seed funding from angel investors (Q2 2024)
|
| 33 |
+
|
| 34 |
+
## Company Profile
|
| 35 |
+
|
| 36 |
+
### 2.1 Company Overview (Founding, Location)
|
| 37 |
+
The company is planned to be founded in the second quarter of 2024 by a team of experienced professionals specialized in marketing and customer engagement. The founding team includes a marketing strategist, a business development manager, and a technical consultant. The headquarters will be located in Dubai, United Arab Emirates, chosen for its status as a leading business hub with excellent connectivity, infrastructure, and access to a diverse customer base across the Middle East.
|
| 38 |
+
|
| 39 |
+
### 2.2 Legal Structure and Ownership
|
| 40 |
+
The company will be registered as a Limited Liability Company (LLC) under UAE law, providing flexibility and protection to its owners. Ownership is divided among three principal shareholders: the marketing strategist holding 40%, the business development manager with 35%, and the technical consultant owning 25%. This structure allows for clear roles in operations and decision-making.
|
| 41 |
+
|
| 42 |
+
### 2.3 Business Objectives (Short-Term and Long-Term)
|
| 43 |
+
**Short-Term (next 12 months):**
|
| 44 |
+
- Acquire 500 paying customers through targeted marketing and free workshops.
|
| 45 |
+
- Generate د.إ 200,000 in revenue by the end of the first operational year.
|
| 46 |
+
- Conduct at least 20 free demonstrations or workshops to build brand awareness and engagement.
|
| 47 |
+
|
| 48 |
+
**Long-Term (2–5 years):**
|
| 49 |
+
- Expand operations to at least two other emirates within the UAE.
|
| 50 |
+
- Achieve profitability with a 25% net margin through scaling and operational efficiency.
|
| 51 |
+
- Establish strategic partnerships with local businesses to enhance service distribution and customer reach.
|
| 52 |
+
|
| 53 |
+
### 2.4 Company History or Origin Story
|
| 54 |
+
The business idea originated from observing a gap in the local market for effective customer engagement solutions tailored to UAE businesses. The founders recognized that many companies struggled to attract and retain customers despite having quality products and services. Leveraging their combined expertise in marketing and technology, they envisioned a service offering free demonstrations and workshops to bridge this gap and foster stronger client relationships.
|
| 55 |
+
|
| 56 |
+
### 2.5 Business Type and Operating Model
|
| 57 |
+
This is a B2B service-oriented business focused on providing customer engagement solutions to other companies in the UAE. Revenue is primarily generated through subscription fees and service packages following initial free demonstrations or workshops. The company plans to leverage partnerships with local business councils and chambers of commerce to reach target customers effectively and build credibility within the regional market.
|
| 58 |
+
|
| 59 |
+
## Market Analysis
|
| 60 |
+
|
| 61 |
+
### 3.1 Target Market Segmentation
|
| 62 |
+
- **Young Professionals in Urban Areas:** This segment includes individuals aged 25-40 living primarily in Dubai and Abu Dhabi. They are tech-savvy, career-focused, and seek convenient, innovative solutions to improve their lifestyle. The estimated size is approximately 1.5 million people, representing a significant consumer base with disposable income in the UAE.
|
| 63 |
+
- **Small and Medium Enterprises (SMEs):** SMEs across various sectors such as retail, hospitality, and services form this segment. These businesses typically have 10-50 employees and annual revenues ranging from د.إ 1 million to د.إ 10 million. There are over 300,000 registered SMEs in the UAE, many of which are looking for cost-effective tools and workshops to improve their operations and customer engagement.
|
| 64 |
+
|
| 65 |
+
### 3.2 Market Size and Growth Potential
|
| 66 |
+
- The total addressable market for our offerings in the UAE is estimated to be worth د.إ 2 billion, considering demand from both individual consumers and SMEs.
|
| 67 |
+
- Key growth drivers include the rapid digital transformation fueled by government initiatives such as the UAE Vision 2021, and the increasing number of startups and SMEs seeking innovative solutions to stay competitive.
|
| 68 |
+
|
| 69 |
+
### 3.3 Industry Trends and Market Drivers
|
| 70 |
+
- Rising E-commerce Adoption: Online retail sales in the UAE increased by 23% in 2024, highlighting growing consumer comfort with digital platforms.
|
| 71 |
+
- Government Support for Innovation: The UAE government launched multiple incentive programs in 2023, aimed at fostering entrepreneurship and digital skills development.
|
| 72 |
+
- Increasing Mobile Penetration: Mobile internet usage reached 98% of the population in 2024, enabling easier access to digital services.
|
| 73 |
+
- Demand for Experiential Marketing: Businesses are focusing on interactive customer experiences to stand out, with 65% reporting increased investment in workshops and demos.
|
| 74 |
+
|
| 75 |
+
### 3.4 Customer Needs and Pain Points
|
| 76 |
+
- Need for Hands-on Experience: Customers in the UAE prefer engaging directly with products or services before committing, making free demonstrations essential.
|
| 77 |
+
- Language and Cultural Relevance: Many consumers seek offerings tailored to Arabic and English speakers, respecting local cultural nuances.
|
| 78 |
+
- Time Efficiency: Busy professionals and SME owners require quick, effective solutions that fit into their schedules without causing disruption.
|
| 79 |
+
- Trust and Reliability Concerns: Given the competitive market, customers often hesitate without clear proof of value and credibility.
|
| 80 |
+
|
| 81 |
+
### 3.5 Competitive Landscape Overview
|
| 82 |
+
- **Dubai Business Solutions:** Provides digital marketing workshops and consultancy with an estimated 15% market share in the SME segment. Their strong network and tailored content are key strengths. Our business differentiates by offering hands-on demonstrations and more personalized engagement.
|
| 83 |
+
- **Innovate UAE:** Focuses on tech-driven training programs for startups with a market presence primarily in Abu Dhabi. They emphasize cutting-edge technology but lack localized cultural customization, which our business prioritizes.
|
| 84 |
+
- **NextGen Workshops:** Offers a range of professional development sessions across the UAE, known for their comprehensive curriculum but limited free offerings. We stand out by providing no-cost demos that lower the entry barrier for potential clients.
|
| 85 |
+
|
| 86 |
+
### 3.6 SWOT Analysis
|
| 87 |
+
|
| 88 |
+
#### Strengths
|
| 89 |
+
- Deep understanding of the UAE market and cultural nuances.
|
| 90 |
+
- Ability to offer free, interactive demonstrations that build trust and engagement.
|
| 91 |
+
- Strong potential for partnership with government and private sector initiatives.
|
| 92 |
+
|
| 93 |
+
#### Weaknesses
|
| 94 |
+
- Limited brand recognition as a new entrant in a competitive market.
|
| 95 |
+
- Initial resource constraints may limit geographic reach and marketing efforts.
|
| 96 |
+
- Dependence on in-person workshops may be challenged by shifting preferences toward virtual formats.
|
| 97 |
+
|
| 98 |
+
#### Opportunities
|
| 99 |
+
- Expansion of digital infrastructure and government incentives supporting SMEs.
|
| 100 |
+
- Growing interest in experiential marketing and interactive learning formats.
|
| 101 |
+
- Potential to collaborate with established business hubs and accelerators in the UAE.
|
| 102 |
+
|
| 103 |
+
#### Threats
|
| 104 |
+
- Presence of well-established competitors with loyal customer bases.
|
| 105 |
+
- Economic fluctuations or regulatory changes impacting SME budgets.
|
| 106 |
+
- Currency volatility affecting pricing and operational costs.
|
| 107 |
+
|
| 108 |
+
## Product or Service
|
| 109 |
+
|
| 110 |
+
### 4.1 Product or Service Description
|
| 111 |
+
Our offering consists of an innovative solution tailored to meet the dynamic needs of customers in the United Arab Emirates. It integrates advanced technology with user-friendly design to provide a seamless and engaging experience. The service emphasizes convenience, reliability, and customization, which are highly valued by AE consumers. End users will benefit from personalized support and features that reflect local cultural preferences and business practices, ensuring relevance and ease of adoption.
|
| 112 |
+
|
| 113 |
+
### 4.2 Core Features and Benefits
|
| 114 |
+
- **Feature**: Multilingual interface including Arabic and English – **Benefit**: Ensures accessibility and ease of use across diverse AE demographics.
|
| 115 |
+
- **Feature**: Mobile-first platform optimized for smartphones – **Benefit**: Aligns with the UAE’s high mobile penetration and on-the-go lifestyle.
|
| 116 |
+
- **Feature**: Localized payment options including popular AE digital wallets – **Benefit**: Facilitates smooth transactions preferred by the regional market.
|
| 117 |
+
- **Feature**: Integration with UAE government services – **Benefit**: Streamlines user processes and builds trust through official partnerships.
|
| 118 |
+
|
| 119 |
+
### 4.3 Unique Selling Proposition (USP)
|
| 120 |
+
Our product uniquely combines advanced technology with deep localization to deliver a service that resonates specifically with UAE consumers.
|
| 121 |
+
- It addresses local language and cultural nuances that many competitors overlook, enhancing user engagement.
|
| 122 |
+
- The solution’s integration with regional payment and government systems meets local regulatory requirements and customer expectations.
|
| 123 |
+
|
| 124 |
+
### 4.4 Development Stage (MVP, Beta, Market Launch)
|
| 125 |
+
Currently, the product is in the Market Launch phase, with a fully functional platform deployed for early adopters in the UAE. Initial user feedback has been positive, with over 100 active users onboarded in the first month. An investment of د.إ 10,000 has been dedicated to marketing and user acquisition activities to accelerate growth.
|
| 126 |
+
|
| 127 |
+
### 4.5 Intellectual Property and Innovation
|
| 128 |
+
- Trademark registered in the UAE for brand protection (Registration number pending).
|
| 129 |
+
- Proprietary algorithm for user personalization, currently patent-pending locally.
|
| 130 |
+
- Unique integration framework for UAE government service APIs, developed in-house.
|
| 131 |
+
This innovation ensures a strong competitive advantage by offering exclusive features tailored to the UAE market, fostering customer loyalty and barriers to entry for competitors.
|
| 132 |
+
|
| 133 |
+
## Business Model
|
| 134 |
+
|
| 135 |
+
### 5.1 Revenue Streams
|
| 136 |
+
- One-time sales: د.إ 150,000 annually. This stream is ideal for AE customers who prefer upfront purchases for tangible products or services without ongoing commitments.
|
| 137 |
+
- Service contracts: د.إ 100,000 annually. Recurring contracts provide steady income and appeal to businesses seeking reliable, ongoing support in the UAE market.
|
| 138 |
+
- Workshops and demonstrations: د.إ 50,000 annually. Offering free demonstrations helps convert interested prospects into paying customers, driving sales growth in a competitive environment.
|
| 139 |
+
|
| 140 |
+
### 5.2 Pricing Strategy
|
| 141 |
+
The business will offer two main pricing tiers: Basic at د.إ 1,000 per package and Premium at د.إ 2,500 per package. This tiered approach reflects the purchasing power of a diverse customer base in the UAE, providing options for smaller businesses as well as larger enterprises. Competitor pricing analysis shows these rates are competitive while allowing for premium value-added services.
|
| 142 |
+
|
| 143 |
+
### 5.3 Sales and Distribution Channels
|
| 144 |
+
- Direct sales: Engaging customers through in-person meetings and consultations, common in the UAE’s relationship-driven business culture.
|
| 145 |
+
- E-commerce marketplace: Utilizing popular UAE platforms supporting digital payments like credit cards and mobile wallets for convenience.
|
| 146 |
+
- Retail partnerships: Collaborating with local retailers to expand reach, leveraging their established distribution networks within UAE cities.
|
| 147 |
+
- Workshops and demonstrations: Hosting events to showcase products, attracting and educating potential clients in key UAE business hubs.
|
| 148 |
+
|
| 149 |
+
### 5.4 Customer Acquisition Strategy
|
| 150 |
+
- Social media ads: Targeted campaigns on platforms popular in the UAE such as Instagram and LinkedIn, aiming for a Cost Per Acquisition (CPA) of د.إ 100.
|
| 151 |
+
- Referral program: Incentivizing existing customers with rewards to introduce new clients, leveraging strong community connections typical in the UAE.
|
| 152 |
+
- Local events and exhibitions: Participating in UAE trade shows and business forums to build brand awareness and generate leads.
|
| 153 |
+
- Free workshops: Offering no-cost demonstrations to lower entry barriers and convert attendees into paying customers.
|
| 154 |
+
|
| 155 |
+
### 5.5 Customer Retention and Loyalty Plan
|
| 156 |
+
- Loyalty points: Customers earn points redeemable for discounts or services, encouraging repeat business and customer engagement within the UAE market.
|
| 157 |
+
- Exclusive content: Providing subscribers with access to advanced tutorials and industry insights, fostering a sense of community and value.
|
| 158 |
+
- Premium support: Offering dedicated customer service with quick response times, meeting the high service expectations of UAE clients.
|
| 159 |
+
- Regular follow-ups: Personalized communication to gather feedback and offer tailored solutions, boosting long-term satisfaction and retention.
|
| 160 |
+
|
| 161 |
+
<!--CHART_DATA_START-->
|
| 162 |
+
[["Business Model","DC","Revenue Streams Breakdown","Annual",[["One-time sales",150000],["Service contracts",100000],["Workshops and demonstrations",50000]]],["Business Model","BG","Pricing Tier Comparison","Annual",[["Basic",1000],["Premium",2500]]],["Business Model","DC","Sales Channel Distribution","Annual",[["Direct sales",1],["E-commerce marketplace",1],["Retail partnerships",1],["Workshops and demonstrations",1]]],["Business Model","DC","Customer Acquisition Strategy Metrics","Annual",[["Social media ads",100]]]]
|
| 163 |
+
<!--CHART_DATA_END-->
|
| 164 |
+
|
| 165 |
+
## Marketing and Growth Strategy
|
| 166 |
+
|
| 167 |
+
### 6.1 Marketing Strategy Overview
|
| 168 |
+
Our marketing approach focuses on leveraging digital platforms combined with in-person engagement to build strong customer relationships in the UAE market. We plan to allocate a budget of approximately د.إ 50,000 initially, prioritizing online advertising, social media, and free demonstrations to attract and engage our target audience. Messaging will be culturally sensitive, reflecting the UAE’s diverse population and emphasizing respect, trust, and innovation. Localized content in both Arabic and English will be used to maximize reach and relevance.
|
| 169 |
+
|
| 170 |
+
### 6.2 Brand Positioning
|
| 171 |
+
The business is the most customer-centric provider of high-quality [product/service] in the United Arab Emirates for discerning individuals and organizations seeking reliability and innovation.
|
| 172 |
+
|
| 173 |
+
- The tone is professional yet approachable, highlighting trustworthiness and a commitment to excellence tailored to the UAE’s multicultural environment.
|
| 174 |
+
- Visual style incorporates modern, clean design elements with culturally appropriate colors and motifs that resonate with both local and expatriate communities.
|
| 175 |
+
|
| 176 |
+
### 6.3 Advertising and Promotion Channels
|
| 177 |
+
- **Google Ads:** Targeted campaigns focused on UAE demographics, expected monthly spend د.إ 15,000, reaching thousands of potential customers.
|
| 178 |
+
- **Social Media Influencers:** Partner with regional influencers to boost brand awareness, estimated cost د.إ 10,000 per campaign with high engagement potential.
|
| 179 |
+
- **Local Radio Spots:** Affordable advertising on popular UAE radio stations, budgeted at د.إ 5,000 monthly, effective for broad reach.
|
| 180 |
+
- **Workshops and Demonstrations:** Hosting free events to showcase products/services, with minimal costs mainly for venue and materials, enhancing direct customer interaction.
|
| 181 |
+
|
| 182 |
+
### 6.4 Partnerships and Referral Strategies
|
| 183 |
+
- **Local Distributors:** Collaborate with distributors who have established networks, offering د.إ 100 credit per referred customer to incentivize referrals, leveraging their market knowledge.
|
| 184 |
+
- **Industry Associations:** Partner with relevant UAE business groups for co-marketing opportunities and referral incentives, fostering credibility and access to niche markets.
|
| 185 |
+
- **Corporate Clients:** Develop referral programs for existing clients, rewarding them with discounts or credits for each new customer introduced, promoting loyalty and word-of-mouth growth.
|
| 186 |
+
|
| 187 |
+
<!--CHART_DATA_START-->
|
| 188 |
+
[]
|
| 189 |
+
<!--CHART_DATA_END-->
|
| 190 |
+
|
| 191 |
+
## Operations Plan
|
| 192 |
+
|
| 193 |
+
### 7.1 Operational Workflow Overview
|
| 194 |
+
- Receive customer inquiries and schedule free demonstrations or workshops to showcase products or services, building engagement and trust.
|
| 195 |
+
- Confirm orders and process payments through secure channels, ensuring clarity on product specifications and delivery terms.
|
| 196 |
+
- Coordinate with local and international suppliers to source materials or products, considering typical delivery lead times within the UAE and neighboring regional hubs.
|
| 197 |
+
- Prepare, package, and dispatch orders through reliable logistics partners, aiming for prompt delivery within 3–5 business days across the UAE.
|
| 198 |
+
- Follow up with customers post-delivery to gather feedback and provide after-sales support, strengthening customer relationships and encouraging repeat business.
|
| 199 |
+
|
| 200 |
+
### 7.2 Facility and Equipment Requirements
|
| 201 |
+
- Office space in Dubai or Abu Dhabi, with approximate monthly rent of د.إ 15,000, serving as the main administrative and customer engagement hub.
|
| 202 |
+
- Warehouse facility near a major logistics hub, renting for around د.إ 25,000 per month, to store inventory and manage dispatch.
|
| 203 |
+
- Computers and office equipment, including laptops and printers, estimated at د.إ 20,000 for initial setup to support daily operations.
|
| 204 |
+
- Workshop or demonstration area equipped with necessary presentation tools, costing approximately د.إ 10,000 to create an interactive customer experience.
|
| 205 |
+
|
| 206 |
+
### 7.3 Supply Chain and Vendor Management
|
| 207 |
+
- Local suppliers in the UAE for quick replenishment, typically offering 30-day net payment terms and delivery lead times of 2–4 days.
|
| 208 |
+
- International suppliers from regions such as Asia and Europe, with payment terms negotiated around 45-day net, and delivery lead times averaging 10–15 days.
|
| 209 |
+
- Use of regional distribution hubs in the UAE to expedite product flow and reduce shipping delays.
|
| 210 |
+
- Compliance with UAE import regulations, including customs clearance and adherence to product standards set by the Emirates Authority for Standardization and Metrology (ESMA).
|
| 211 |
+
|
| 212 |
+
### 7.4 Regulatory and Compliance Requirements
|
| 213 |
+
- Business registration with the Department of Economic Development (DED) in the relevant emirate is mandatory to legally operate.
|
| 214 |
+
- Industry-specific permits or approvals may be required depending on the product or service offered, such as health and safety certifications.
|
| 215 |
+
- Ongoing tax obligations include Value Added Tax (VAT) registration and periodic filing with the Federal Tax Authority (FTA).
|
| 216 |
+
- Compliance with UAE labor laws and occupational health and safety standards to ensure a safe and lawful working environment.
|
| 217 |
+
|
| 218 |
+
<!--CHART_DATA_START-->
|
| 219 |
+
[["Operations Plan","DC","Facility Cost Breakdown","Monthly",[["Office Rent",15000],["Warehouse Rent",25000],["Computers and Equipment",20000],["Workshop Setup",10000]]],["Operations Plan","BG","Supplier Payment Terms and Delivery Lead Times","N/A",[["Local Supplier Payment Terms (days)",30],["Local Supplier Delivery Lead Time (days)",4],["International Supplier Payment Terms (days)",45],["International Supplier Delivery Lead Time (days)",15]]]]
|
| 220 |
+
<!--CHART_DATA_END-->
|
| 221 |
+
|
| 222 |
+
## Management and Team
|
| 223 |
+
|
| 224 |
+
### 8.1 Core Roles and Functional Responsibilities
|
| 225 |
+
**- Chief Executive Officer (CEO):** Leads the overall strategic direction of the AE business, ensuring compliance with UAE commercial laws and fostering partnerships across the region. Oversees business growth initiatives including free demonstrations and workshops to attract customers.
|
| 226 |
+
**- Chief Operating Officer (COO):** Manages daily operations with a focus on efficiency and adherence to UAE-specific labor and safety regulations. Coordinates between departments to deliver seamless service experiences aligned with local market expectations.
|
| 227 |
+
**- Marketing Manager:** Develops and executes marketing strategies tailored to the UAE market, including organizing interactive workshops and demonstrations to engage potential clients. Ensures culturally relevant messaging and digital presence.
|
| 228 |
+
**- Compliance Officer:** Ensures all activities comply with UAE AE industry regulations and licensing requirements. Monitors ongoing legal changes and implements necessary adjustments to company policies and procedures.
|
| 229 |
+
**- Customer Relations Manager:** Oversees client engagement and retention strategies, including managing feedback from free demonstrations to improve service offerings and customer satisfaction within the UAE context.
|
| 230 |
+
|
| 231 |
+
### 8.2 Key Hires or Specialized Team Requirements
|
| 232 |
+
**- AE Technical Specialist:** Critical for delivering high-quality demonstrations and workshops, this role requires deep knowledge of AE products and services tailored to the UAE market.
|
| 233 |
+
**- Local Legal Advisor:** Essential to navigate UAE-specific regulatory frameworks and ensure all business practices meet local compliance standards.
|
| 234 |
+
**- Bilingual Customer Support Representative:** Provides support in both Arabic and English, enhancing communication and customer experience in the culturally diverse UAE environment.
|
| 235 |
+
**- Digital Marketing Expert:** Focused on leveraging UAE digital platforms and social media channels to maximize workshop attendance and brand awareness.
|
| 236 |
+
|
| 237 |
+
### 8.3 Advisory Board and Strategic Mentors
|
| 238 |
+
**- Industry Expert (Regional AE Markets):** Provides insights on regional market trends and competitive landscape to position the business effectively within the UAE.
|
| 239 |
+
**- Legal and Compliance Consultant:** Advises on evolving UAE regulations affecting AE operations, helping to mitigate risks and maintain adherence to local laws.
|
| 240 |
+
**- Business Development Mentor:** Supports strategic partnerships and growth opportunities in the UAE, especially around customer engagement initiatives such as free workshops.
|
| 241 |
+
|
| 242 |
+
### 8.4 Organizational Structure and Reporting Lines
|
| 243 |
+
- **CEO** -> Board of Directors -> Oversees all company operations and strategic growth
|
| 244 |
+
- **COO** -> CEO -> Manages daily operations and ensures regulatory compliance
|
| 245 |
+
- **Marketing Manager** -> COO -> Develops and implements marketing campaigns and customer engagement events
|
| 246 |
+
- **Compliance Officer** -> COO -> Monitors regulatory adherence and manages legal risk
|
| 247 |
+
- **Customer Relations Manager** -> COO -> Handles client communication and feedback from demonstrations and workshops
|
| 248 |
+
|
| 249 |
+
## Financial Plan and Funding
|
| 250 |
+
|
| 251 |
+
### 9.1 Startup and Operating Costs
|
| 252 |
+
|
| 253 |
+
- **Startup Costs:**
|
| 254 |
+
- Equipment and software licenses estimated at د.إ 100,000 to ensure the business is fully operational with up-to-date technology.
|
| 255 |
+
- Business registration and licensing fees approximately د.إ 15,000 to comply with UAE regulations.
|
| 256 |
+
- Initial marketing campaigns including free workshops setup costing around د.إ 30,000 to attract early customers.
|
| 257 |
+
- Office setup and furnishing expected to be د.إ 25,000 for a professional environment.
|
| 258 |
+
|
| 259 |
+
- **Operating Costs (Monthly or Annual):**
|
| 260 |
+
- Monthly office rent approximately د.إ 20,000 in a prime location within the UAE.
|
| 261 |
+
- Staff salaries estimated at د.إ 60,000 monthly to attract skilled professionals.
|
| 262 |
+
- Utilities, internet, and maintenance estimated at د.إ 5,000 per month.
|
| 263 |
+
- Marketing and promotional activities budgeted at د.إ 15,000 monthly to sustain customer engagement.
|
| 264 |
+
|
| 265 |
+
### 9.2 Revenue Forecast and Growth Projections (1–3 Years)
|
| 266 |
+
|
| 267 |
+
- **Year 1 Projected Revenue:** د.إ 1,200,000, based on initial client acquisition through free demonstrations and workshops converting to paid services.
|
| 268 |
+
- **Year 2 Projected Revenue:** د.إ 1,800,000, assuming a 50% increase due to expanding brand presence and customer base.
|
| 269 |
+
- **Year 3 Projected Revenue:** د.إ 2,700,000, reflecting a 50% growth supported by market expansion within the UAE and introduction of new service offerings.
|
| 270 |
+
|
| 271 |
+
### 9.3 Break-even Analysis (Optional)
|
| 272 |
+
|
| 273 |
+
The business is expected to break even by Month 15 with a required revenue of approximately د.إ 1,000,000. This projection assumes fixed costs such as rent and salaries remain constant, while variable costs scale with sales. The average sale price per service, combined with steady client growth, supports reaching profitability within this timeframe.
|
| 274 |
+
|
| 275 |
+
### 9.4 Funding Requirements and Use of Funds
|
| 276 |
+
|
| 277 |
+
A total funding requirement of د.إ 250,000 is needed to cover startup costs and initial operating expenses until the business reaches cash flow positivity. These funds will be allocated strategically to ensure smooth launch and early growth phases.
|
| 278 |
+
|
| 279 |
+
- د.إ 100,000 for equipment and software procurement.
|
| 280 |
+
- د.إ 50,000 dedicated to marketing efforts, including free demonstrations and digital campaigns.
|
| 281 |
+
- د.إ 70,000 allocated for staffing and training to build a competent team.
|
| 282 |
+
- د.إ 30,000 reserved as working capital for operational expenses during the first six months.
|
| 283 |
+
|
| 284 |
+
### 9.5 Financial Milestones and Investor ROI Expectations
|
| 285 |
+
|
| 286 |
+
- Reach د.إ 1,200,000 in revenue by the end of Year 1; projected ROI: 15% by Year 3.
|
| 287 |
+
- Achieve profitability by Month 15, reducing operating losses; projected ROI: 25% by Year 3.
|
| 288 |
+
- Expand customer base to over 500 clients by Year 2; projected ROI: 40% by Year 4.
|
| 289 |
+
- Increase revenue to د.إ 2,700,000 by Year 3 through market expansion; projected ROI: 60% by Year 5.
|
| 290 |
+
|
| 291 |
+
<!--CHART_DATA_START-->
|
| 292 |
+
[["Financial Plan and Funding","LC","Projected Revenue Over Time","Annual",[["Year 1",1200000],["Year 2",1800000],["Year 3",2700000]]],["Financial Plan and Funding","DC","Startup Cost Breakdown","Annual",[["Equipment and software licenses",100000],["Marketing and digital campaigns",30000],["Business registration and licensing",15000],["Office setup and furnishing",25000]]],["Financial Plan and Funding","DC","Operating Cost Breakdown","Monthly",[["Rent",20000],["Salaries",60000],["Utilities and maintenance",5000],["Marketing and promotions",15000]]],["Financial Plan and Funding","DC","Funding Allocation","Annual",[["Equipment and software procurement",100000],["Marketing efforts",50000],["Staffing and training",70000],["Working capital",30000]]]]
|
| 293 |
+
<!--CHART_DATA_END-->
|
maintest.txt
CHANGED
|
@@ -1776,8 +1776,7 @@ def create_pdf_document(request: ExportRequest):
|
|
| 1776 |
# content_text = enhance_subheadings_with_bold(content_text)
|
| 1777 |
content_text = remove_duplicate_headings(content_text)
|
| 1778 |
|
| 1779 |
-
|
| 1780 |
-
content_text = make_sentences_ending_with_colon_bold(content_text)
|
| 1781 |
|
| 1782 |
# Add content with proper markdown rendering using grouped approach
|
| 1783 |
if content_text and content_text.strip():
|
|
@@ -2216,8 +2215,7 @@ def create_word_document(request: ExportRequest):
|
|
| 2216 |
filtered_lines.append(line)
|
| 2217 |
clean_content = '\n'.join(filtered_lines)
|
| 2218 |
|
| 2219 |
-
|
| 2220 |
-
clean_content = make_sentences_ending_with_colon_bold(clean_content)
|
| 2221 |
|
| 2222 |
# Render markdown content with proper heading styling using grouped approach
|
| 2223 |
render_markdown_to_docx_grouped(doc, clean_content)
|
|
@@ -3064,41 +3062,7 @@ def filter_empty_sections(sections: List[ExportSection]) -> List[ExportSection]:
|
|
| 3064 |
# logging.info(f"Filtered sections: {len(filtered_sections)} out of {len(sections)} total")
|
| 3065 |
return filtered_sections
|
| 3066 |
|
| 3067 |
-
|
| 3068 |
-
"""Enhance subheadings with bold formatting and ensure no duplicates"""
|
| 3069 |
-
if not content:
|
| 3070 |
-
return content
|
| 3071 |
-
|
| 3072 |
-
lines = content.split('\n')
|
| 3073 |
-
enhanced_lines = []
|
| 3074 |
-
seen_headings = set()
|
| 3075 |
-
|
| 3076 |
-
for line in lines:
|
| 3077 |
-
line = line.strip()
|
| 3078 |
-
if not line:
|
| 3079 |
-
enhanced_lines.append('')
|
| 3080 |
-
continue
|
| 3081 |
-
|
| 3082 |
-
# Check if this is a subheading (## or ###)
|
| 3083 |
-
if line.startswith('##') and not line.startswith('###'):
|
| 3084 |
-
# Extract the heading text
|
| 3085 |
-
heading_text = re.sub(r'^##+\s*', '', line)
|
| 3086 |
-
# heading_text = re.sub(r'[*_`~]', '', heading_text).strip() # Commented out to preserve ** symbols
|
| 3087 |
-
heading_text = heading_text.strip()
|
| 3088 |
-
|
| 3089 |
-
# Check for duplicates
|
| 3090 |
-
if heading_text.lower() in seen_headings:
|
| 3091 |
-
continue
|
| 3092 |
-
|
| 3093 |
-
seen_headings.add(heading_text.lower())
|
| 3094 |
-
|
| 3095 |
-
# Make it bold and ensure proper formatting
|
| 3096 |
-
enhanced_line = f"**{heading_text}**"
|
| 3097 |
-
enhanced_lines.append(enhanced_line)
|
| 3098 |
-
else:
|
| 3099 |
-
enhanced_lines.append(line)
|
| 3100 |
-
|
| 3101 |
-
return '\n'.join(enhanced_lines)
|
| 3102 |
|
| 3103 |
def remove_duplicate_headings(content: str) -> str:
|
| 3104 |
"""Remove duplicate headings from content"""
|
|
@@ -3325,7 +3289,7 @@ def should_break_page_for_group(pdf, group: Dict[str, Any], current_y: int, page
|
|
| 3325 |
estimated_height = calculate_group_height(pdf, group)
|
| 3326 |
|
| 3327 |
# If the group is very large (more than half a page), always break
|
| 3328 |
-
if estimated_height > page_height * 0.
|
| 3329 |
return True
|
| 3330 |
|
| 3331 |
# If the group won't fit on current page, break
|
|
@@ -3338,28 +3302,7 @@ def should_break_page_for_group(pdf, group: Dict[str, Any], current_y: int, page
|
|
| 3338 |
|
| 3339 |
return False
|
| 3340 |
|
| 3341 |
-
|
| 3342 |
-
"""Helper to render a line with mixed bold and normal text using pdf.cell"""
|
| 3343 |
-
parts = re.split(r'(\*\*.*?\*\*)', line)
|
| 3344 |
-
|
| 3345 |
-
for part in parts:
|
| 3346 |
-
if part.startswith('**') and part.endswith('**'):
|
| 3347 |
-
bold_text = part[2:-2] # Remove ** markers
|
| 3348 |
-
pdf.set_font("Arial", "B", bold_font_size)
|
| 3349 |
-
pdf.set_text_color(*text_color)
|
| 3350 |
-
pdf.set_xy(current_x, current_y)
|
| 3351 |
-
text_width = pdf.get_string_width(bold_text)
|
| 3352 |
-
pdf.cell(text_width, line_height, bold_text)
|
| 3353 |
-
current_x += text_width
|
| 3354 |
-
else:
|
| 3355 |
-
if part:
|
| 3356 |
-
pdf.set_font("Arial", "", normal_font_size)
|
| 3357 |
-
pdf.set_text_color(*text_color)
|
| 3358 |
-
pdf.set_xy(current_x, current_y)
|
| 3359 |
-
text_width = pdf.get_string_width(part)
|
| 3360 |
-
pdf.cell(text_width, line_height, part)
|
| 3361 |
-
current_x += text_width
|
| 3362 |
-
return current_x
|
| 3363 |
|
| 3364 |
def render_markdown_to_pdf_grouped(pdf, content: str, primary_color, accent_color, text_color):
|
| 3365 |
"""Render markdown content to PDF with subheading-content grouping and smart page breaks"""
|
|
@@ -3455,18 +3398,19 @@ def render_markdown_to_pdf_grouped(pdf, content: str, primary_color, accent_colo
|
|
| 3455 |
|
| 3456 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 3457 |
# Main heading style
|
|
|
|
|
|
|
| 3458 |
pdf.set_xy(25, current_y)
|
| 3459 |
-
|
| 3460 |
-
current_y
|
| 3461 |
-
pdf.set_xy(25, current_y)
|
| 3462 |
pdf.ln(6)
|
| 3463 |
current_y = pdf.get_y()
|
| 3464 |
else:
|
| 3465 |
# Regular bold text
|
|
|
|
|
|
|
| 3466 |
pdf.set_xy(25, current_y)
|
| 3467 |
-
|
| 3468 |
-
current_y += line_height_text
|
| 3469 |
-
pdf.set_xy(25, current_y)
|
| 3470 |
current_y = pdf.get_y()
|
| 3471 |
|
| 3472 |
elif subheading_type == 'numbered':
|
|
@@ -3491,19 +3435,12 @@ def render_markdown_to_pdf_grouped(pdf, content: str, primary_color, accent_colo
|
|
| 3491 |
content_lines = group_content.split('\n')
|
| 3492 |
for line in content_lines:
|
| 3493 |
if line.strip():
|
| 3494 |
-
#
|
| 3495 |
-
|
| 3496 |
-
|
| 3497 |
-
|
| 3498 |
-
|
| 3499 |
-
|
| 3500 |
-
else:
|
| 3501 |
-
# No bold text - render normally
|
| 3502 |
-
pdf.set_font("Arial", "", 12)
|
| 3503 |
-
pdf.set_text_color(*text_color)
|
| 3504 |
-
pdf.set_xy(25, current_y)
|
| 3505 |
-
pdf.multi_cell(160, 8, line)
|
| 3506 |
-
current_y = pdf.get_y()
|
| 3507 |
else:
|
| 3508 |
# Empty line spacing (original)
|
| 3509 |
pdf.ln(3)
|
|
@@ -4119,10 +4056,10 @@ def render_markdown_to_docx_grouped(doc, content: str):
|
|
| 4119 |
doc.add_heading(text, level=4)
|
| 4120 |
elif subheading_type == 'bold':
|
| 4121 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 4122 |
-
paragraph =
|
| 4123 |
paragraph.style = doc.styles['Heading 1'] # Apply heading style
|
| 4124 |
else:
|
| 4125 |
-
|
| 4126 |
elif subheading_type == 'numbered':
|
| 4127 |
doc.add_heading(text, level=2)
|
| 4128 |
|
|
@@ -4131,32 +4068,14 @@ def render_markdown_to_docx_grouped(doc, content: str):
|
|
| 4131 |
content_lines = group_content.split('\n')
|
| 4132 |
for line in content_lines:
|
| 4133 |
if line.strip():
|
| 4134 |
-
#
|
| 4135 |
-
|
| 4136 |
else:
|
| 4137 |
doc.add_paragraph() # Empty paragraph for spacing (original logic)
|
| 4138 |
|
| 4139 |
# Removed process_bold_text_in_content function - now handled inline with proper bold rendering
|
| 4140 |
|
| 4141 |
-
|
| 4142 |
-
"""Process bold text markers (**text**) in content for Word documents"""
|
| 4143 |
-
# For Word documents, we can actually render bold text properly
|
| 4144 |
-
# Split text by ** markers and create runs with appropriate formatting
|
| 4145 |
-
parts = re.split(r'(\*\*.*?\*\*)', text)
|
| 4146 |
-
paragraph = doc.add_paragraph()
|
| 4147 |
-
|
| 4148 |
-
for part in parts:
|
| 4149 |
-
if part.startswith('**') and part.endswith('**'):
|
| 4150 |
-
# This is bold text
|
| 4151 |
-
bold_text = part # Keep ** markers
|
| 4152 |
-
run = paragraph.add_run(bold_text)
|
| 4153 |
-
run.bold = True
|
| 4154 |
-
else:
|
| 4155 |
-
# Regular text
|
| 4156 |
-
if part: # Only add non-empty parts
|
| 4157 |
-
paragraph.add_run(part)
|
| 4158 |
-
|
| 4159 |
-
return paragraph
|
| 4160 |
|
| 4161 |
def clean_text_for_docx(text: str) -> str:
|
| 4162 |
# Replace problematic Unicode characters with ASCII equivalents
|
|
@@ -4184,33 +4103,3 @@ def clean_text_for_docx(text: str) -> str:
|
|
| 4184 |
|
| 4185 |
return text
|
| 4186 |
|
| 4187 |
-
def make_sentences_ending_with_colon_bold(content: str) -> str:
|
| 4188 |
-
"""
|
| 4189 |
-
Make text before a colon (:) bold by wrapping it in ** markers.
|
| 4190 |
-
This handles cases like "Short-Term (next 12 months):" to make only the part before the colon bold.
|
| 4191 |
-
"""
|
| 4192 |
-
if not content:
|
| 4193 |
-
return content
|
| 4194 |
-
|
| 4195 |
-
lines = content.split('\n')
|
| 4196 |
-
processed_lines = []
|
| 4197 |
-
|
| 4198 |
-
for line in lines:
|
| 4199 |
-
if not line.strip():
|
| 4200 |
-
processed_lines.append(line)
|
| 4201 |
-
continue
|
| 4202 |
-
|
| 4203 |
-
last_colon_index = line.rfind(':')
|
| 4204 |
-
|
| 4205 |
-
if last_colon_index != -1:
|
| 4206 |
-
text_before_colon = line[:last_colon_index].strip()
|
| 4207 |
-
text_after_colon = line[last_colon_index:].strip()
|
| 4208 |
-
|
| 4209 |
-
if not (text_before_colon.startswith('**') and text_before_colon.endswith('**')):
|
| 4210 |
-
processed_lines.append(f"**{text_before_colon}**{text_after_colon}")
|
| 4211 |
-
else:
|
| 4212 |
-
processed_lines.append(line)
|
| 4213 |
-
else:
|
| 4214 |
-
processed_lines.append(line)
|
| 4215 |
-
|
| 4216 |
-
return '\n'.join(processed_lines)
|
|
|
|
| 1776 |
# content_text = enhance_subheadings_with_bold(content_text)
|
| 1777 |
content_text = remove_duplicate_headings(content_text)
|
| 1778 |
|
| 1779 |
+
|
|
|
|
| 1780 |
|
| 1781 |
# Add content with proper markdown rendering using grouped approach
|
| 1782 |
if content_text and content_text.strip():
|
|
|
|
| 2215 |
filtered_lines.append(line)
|
| 2216 |
clean_content = '\n'.join(filtered_lines)
|
| 2217 |
|
| 2218 |
+
|
|
|
|
| 2219 |
|
| 2220 |
# Render markdown content with proper heading styling using grouped approach
|
| 2221 |
render_markdown_to_docx_grouped(doc, clean_content)
|
|
|
|
| 3062 |
# logging.info(f"Filtered sections: {len(filtered_sections)} out of {len(sections)} total")
|
| 3063 |
return filtered_sections
|
| 3064 |
|
| 3065 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3066 |
|
| 3067 |
def remove_duplicate_headings(content: str) -> str:
|
| 3068 |
"""Remove duplicate headings from content"""
|
|
|
|
| 3289 |
estimated_height = calculate_group_height(pdf, group)
|
| 3290 |
|
| 3291 |
# If the group is very large (more than half a page), always break
|
| 3292 |
+
if estimated_height > page_height * 0.5:
|
| 3293 |
return True
|
| 3294 |
|
| 3295 |
# If the group won't fit on current page, break
|
|
|
|
| 3302 |
|
| 3303 |
return False
|
| 3304 |
|
| 3305 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3306 |
|
| 3307 |
def render_markdown_to_pdf_grouped(pdf, content: str, primary_color, accent_color, text_color):
|
| 3308 |
"""Render markdown content to PDF with subheading-content grouping and smart page breaks"""
|
|
|
|
| 3398 |
|
| 3399 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 3400 |
# Main heading style
|
| 3401 |
+
pdf.set_font("Arial", "B", 18)
|
| 3402 |
+
pdf.set_text_color(*primary_color)
|
| 3403 |
pdf.set_xy(25, current_y)
|
| 3404 |
+
pdf.multi_cell(160, line_height_heading, text)
|
| 3405 |
+
current_y = pdf.get_y()
|
|
|
|
| 3406 |
pdf.ln(6)
|
| 3407 |
current_y = pdf.get_y()
|
| 3408 |
else:
|
| 3409 |
# Regular bold text
|
| 3410 |
+
pdf.set_font("Arial", "B", 12)
|
| 3411 |
+
pdf.set_text_color(*text_color)
|
| 3412 |
pdf.set_xy(25, current_y)
|
| 3413 |
+
pdf.multi_cell(160, line_height_text, text)
|
|
|
|
|
|
|
| 3414 |
current_y = pdf.get_y()
|
| 3415 |
|
| 3416 |
elif subheading_type == 'numbered':
|
|
|
|
| 3435 |
content_lines = group_content.split('\n')
|
| 3436 |
for line in content_lines:
|
| 3437 |
if line.strip():
|
| 3438 |
+
# Render text normally (removed mixed text support)
|
| 3439 |
+
pdf.set_font("Arial", "", 12)
|
| 3440 |
+
pdf.set_text_color(*text_color)
|
| 3441 |
+
pdf.set_xy(25, current_y)
|
| 3442 |
+
pdf.multi_cell(160, 8, line)
|
| 3443 |
+
current_y = pdf.get_y()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3444 |
else:
|
| 3445 |
# Empty line spacing (original)
|
| 3446 |
pdf.ln(3)
|
|
|
|
| 4056 |
doc.add_heading(text, level=4)
|
| 4057 |
elif subheading_type == 'bold':
|
| 4058 |
if not re.match(r'^\d+\.', text) and len(text) < 80:
|
| 4059 |
+
paragraph = doc.add_paragraph(text)
|
| 4060 |
paragraph.style = doc.styles['Heading 1'] # Apply heading style
|
| 4061 |
else:
|
| 4062 |
+
doc.add_paragraph(text) # Use simple paragraph
|
| 4063 |
elif subheading_type == 'numbered':
|
| 4064 |
doc.add_heading(text, level=2)
|
| 4065 |
|
|
|
|
| 4068 |
content_lines = group_content.split('\n')
|
| 4069 |
for line in content_lines:
|
| 4070 |
if line.strip():
|
| 4071 |
+
# Simple text rendering (removed bold processing)
|
| 4072 |
+
doc.add_paragraph(line)
|
| 4073 |
else:
|
| 4074 |
doc.add_paragraph() # Empty paragraph for spacing (original logic)
|
| 4075 |
|
| 4076 |
# Removed process_bold_text_in_content function - now handled inline with proper bold rendering
|
| 4077 |
|
| 4078 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4079 |
|
| 4080 |
def clean_text_for_docx(text: str) -> str:
|
| 4081 |
# Replace problematic Unicode characters with ASCII equivalents
|
|
|
|
| 4103 |
|
| 4104 |
return text
|
| 4105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|