grantforge-api / backend /core /projects /section_seeder.py
GrantForge Bot
Deploy sha-9a5957fcdef15b7e2623f8b147cda6026475aee0 — source build (no GHCR)
3a3734f
Raw
History Blame Contribute Delete
6.01 kB
"""Seed sekcji wniosku z regulaminu (z fallback na szablony DB)."""
from __future__ import annotations
import re
import unicodedata
import uuid
from typing import Any
from core.projects.models import ProjectSection, ProjectSectionTemplate
_PROGRAM_TYPE_ALIASES: dict[str, str] = {
"PARP": "PARP",
"SMART": "SMART",
"FENG": "SMART",
"NCBR": "SMART",
"ARIMR": "ARIMR",
"ZUS": "ZUS_BHP",
"ZUS_BHP": "ZUS_BHP",
"BGK": "SMART",
"NFOSIGW": "SMART",
"NFOŚIGW": "SMART",
"REGIONAL": "REGIONAL",
}
def normalize_program_type(program_type: str) -> str:
"""Mapuje operator / typ programu na identyfikator szablonu sekcji."""
key = (program_type or "SMART").strip().upper()
if key in _PROGRAM_TYPE_ALIASES:
return _PROGRAM_TYPE_ALIASES[key]
for alias, target in _PROGRAM_TYPE_ALIASES.items():
if alias in key:
return target
return key if key else "SMART"
def section_title_to_slug(title: str) -> str:
"""Konwertuje tytuł sekcji z regulaminu na slug section_type."""
text = unicodedata.normalize("NFKD", title or "")
text = "".join(c for c in text if not unicodedata.combining(c))
text = text.lower().strip()
text = re.sub(r"[^a-z0-9]+", "_", text)
text = re.sub(r"_+", "_", text).strip("_")
return text[:60] or "section"
def seed_sections_from_regulation(
db,
project_id: str,
required_sections: list[str],
program_type: str,
) -> tuple[list[ProjectSection], dict[str, str]]:
"""
Tworzy ProjectSection z listy required_sections regulaminu.
Zwraca (utworzone_sekcje, mapa slug→tytuł).
"""
titles_map: dict[str, str] = {}
created: list[ProjectSection] = []
used_slugs: set[str] = set()
for idx, title in enumerate(required_sections or [], start=1):
title = (title or "").strip()
if not title:
continue
slug = section_title_to_slug(title)
base_slug = slug
n = 2
while slug in used_slugs:
slug = f"{base_slug}_{n}"
n += 1
used_slugs.add(slug)
titles_map[slug] = title
sec = ProjectSection(
id=str(uuid.uuid4()),
project_id=project_id,
section_type=slug,
order=idx,
content="",
is_approved=False,
generated_by_ai=False,
)
db.add(sec)
created.append(sec)
return created, titles_map
def seed_sections_from_templates(db, project_id: str, program_type: str) -> list[ProjectSection]:
"""Fallback: sekcje z ProjectSectionTemplate (SMART/ARIMR/ZUS_BHP)."""
norm_type = normalize_program_type(program_type)
templates = (
db.query(ProjectSectionTemplate)
.filter(ProjectSectionTemplate.program_type == norm_type)
.order_by(ProjectSectionTemplate.order.asc())
.all()
)
if not templates and norm_type != "SMART":
templates = (
db.query(ProjectSectionTemplate)
.filter(ProjectSectionTemplate.program_type == "SMART")
.order_by(ProjectSectionTemplate.order.asc())
.all()
)
if not templates:
from scripts.seed_section_templates import TEMPLATES
dicts = [t for t in TEMPLATES if t.get("program_type") == norm_type]
if not dicts:
dicts = [t for t in TEMPLATES if t.get("program_type") == "SMART"]
created = []
for tmpl in dicts:
sec = ProjectSection(
id=str(uuid.uuid4()),
project_id=project_id,
section_type=tmpl.get("section_type"),
order=tmpl.get("order"),
content="",
is_approved=False,
generated_by_ai=False,
)
db.add(sec)
created.append(sec)
return created
created = []
for tmpl in templates:
sec = ProjectSection(
id=str(uuid.uuid4()),
project_id=project_id,
section_type=tmpl.section_type,
order=tmpl.order,
content="",
is_approved=False,
generated_by_ai=False,
)
db.add(sec)
created.append(sec)
return created
def reseed_empty_sections_from_regulation(
db,
project_id: str,
required_sections: list[str],
) -> dict[str, str]:
"""Dodaje brakujące sekcje z regulaminu — nie nadpisuje wypełnionych."""
existing = (
db.query(ProjectSection)
.filter(ProjectSection.project_id == project_id)
.order_by(ProjectSection.order.asc())
.all()
)
existing_types = {s.section_type for s in existing}
existing_titles_lower = {
(s.section_type or "").replace("_", " ").lower() for s in existing
}
max_order = max((s.order for s in existing), default=0)
titles_map: dict[str, str] = {}
order = max_order
for title in required_sections or []:
title = (title or "").strip()
if not title:
continue
slug = section_title_to_slug(title)
title_lower = title.lower()
if slug in existing_types:
continue
if title_lower in existing_titles_lower:
continue
# Pomiń sekcje z treścią (fuzzy: ten sam slug lub podobny tytuł)
fuzzy_hit = any(
(s.content or "").strip() and (
s.section_type == slug
or title_lower in (s.section_type or "").replace("_", " ").lower()
)
for s in existing
)
if fuzzy_hit:
continue
order += 1
sec = ProjectSection(
id=str(uuid.uuid4()),
project_id=project_id,
section_type=slug,
order=order,
content="",
is_approved=False,
generated_by_ai=False,
)
db.add(sec)
titles_map[slug] = title
existing_types.add(slug)
return titles_map