grantforge-api / backend /core /document_builder.py
GrantForge Bot
Deploy to Hugging Face
afd56bc
import logging
from typing import Dict, List
from datetime import datetime
try:
from core.sensitive_data_guard import anonymizer
except ImportError:
try:
from backend.core.sensitive_data_guard import anonymizer
except ImportError:
anonymizer = None
logger = logging.getLogger(__name__)
class DocumentBuilder:
"""
Formatyzer sk\u0142adaj\u0105cy cz\u0119\u015bci dokumentu z GeneratorAgent
w sp\u00f3jny format Markdown gotowy do eksportu docx/pdf.
Przywraca PII stosuj\u0105c Deanonimizator.
"""
@staticmethod
def build_markdown(
sections_plan: List[Dict[str, str] | str],
generated_sections: Dict[str, str],
document_type: str,
project_title: str = "",
company_name: str = "",
traceability_data: Dict[str, List[dict]] = None,
) -> str:
"""
Scala sekcje wed\u0142ug ich oryginalnej kolejno\u015bci ze stanu
i tworzy reprezentacj\u0119 Markdown.
"""
logger.info(f"Scalanie dokumentu: {document_type}")
title = project_title or document_type
md_lines = [f"# {title}", ""]
if company_name:
md_lines += [f"**Wnioskodawca:** {company_name}", ""]
md_lines += [
f"**Typ dokumentu:** {document_type}",
f"**Data wygenerowania:** {datetime.now().strftime('%d.%m.%Y %H:%M')}",
"",
"---",
"",
]
for section_def in sections_plan:
section = (
section_def["title"] if isinstance(section_def, dict) else section_def
)
content = generated_sections.get(
section, "*(Sekcja nie zosta艂a wygenerowana)*"
)
md_lines.append(f"## {section}")
md_lines.append(content)
md_lines.append("") # odst臋p
# Za艂膮cznik: 殴r贸d艂a i dokumenty (Traceability)
if traceability_data:
md_lines.append("## Za艂膮cznik: 殴r贸d艂a i dokumenty")
md_lines.append("Poni偶ej znajduje si臋 lista dokument贸w (regulamin贸w, wytycznych), na podstawie kt贸rych sztuczna inteligencja wygenerowa艂a poszczeg贸lne sekcje. Ka偶dy dokument posiada unikalny skr贸t (Hash SHA-256) chroni膮cy przed niezauwa偶alnymi zmianami w przysz艂o艣ci.")
md_lines.append("")
for sec_name, traces in traceability_data.items():
if traces:
md_lines.append(f"### Sekcja: {sec_name}")
for t in traces:
md_lines.append(f"- **殴r贸d艂o:** {t.get('source', 'Brak')}")
ver_str = t.get('version_id')
vf_str = t.get('valid_from')
vt_str = t.get('valid_to')
if ver_str or vf_str or vt_str:
md_lines.append(f" - **Wersja dokumentu:** {ver_str or 'Nieznana'} (Obowi膮zuje od {vf_str or '-'} do {vt_str or '-'})")
md_lines.append(f" - **Link:** {t.get('url', 'Brak')}")
md_lines.append(f" - **Data pozyskania:** {t.get('date', 'Brak')}")
md_lines.append(f" - **Hash (SHA-256):** `{t.get('hash', 'Brak')}`")
md_lines.append("")
# Stopka AI
md_lines += [
"---",
"",
"cz\u0119\u015bciowo przy u\u017cyciu modeli j\u0119zykowych (AI). Tre\u015b\u0107 powinna zosta\u0107 "
"zweryfikowana przez uprawnionego doradc\u0119 przed z\u0142o\u017ceniem wniosku. "
"Wydawca nie ponosi odpowiedzialno\u015bci za b\u0142\u0119dy merytoryczne wygenerowanego tekstu.",
"",
]
full_text = "\n".join(md_lines)
# Deanonimizacja PII (NIP, know-how, etc.)
if anonymizer:
try:
full_text = anonymizer.deanonymize_text(full_text)
except Exception as e:
logger.warning(f"Deanonimizacja nie powiod\u0142a si\u0119: {e}")
return full_text