Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.13'

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'

Expand All @@ -59,13 +59,13 @@ jobs:
cp -r viewer/dist/* docs/

- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v6

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
path: './docs'

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.11'

Expand Down
101 changes: 61 additions & 40 deletions grants_builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ def _old_strip_markdown_formatting(text):
return text.strip()


def process_sections(grant_path, base_path, sections, grant_id, grant_name, foundation):
def process_sections(
grant_path, base_path, sections, grant_id, grant_name, foundation
):
"""Process sections from a questions file."""
responses = {}
exports_dir = Path("docs/exports")
Expand Down Expand Up @@ -193,17 +195,20 @@ def process_grant(grant_id, grant_config):
app_sections,
grant_id,
grant_config["name"],
grant_config["foundation"]
grant_config["foundation"],
)

# Store application data separately
application_data = {
"metadata": app_metadata,
"responses": app_responses
"responses": app_responses,
}

for key, value in app_responses.items():
all_responses[f"app_{key}"] = {**value, "type": "application"}
all_responses[f"app_{key}"] = {
**value,
"type": "application",
}

# Process reports
if reports_path.exists():
Expand All @@ -213,8 +218,12 @@ def process_grant(grant_id, grant_config):
if report_questions_path.exists():
with open(report_questions_path) as f:
report_questions_data = yaml.safe_load(f)
report_sections = report_questions_data.get("sections", {})
report_metadata = report_questions_data.get("metadata", {})
report_sections = report_questions_data.get(
"sections", {}
)
report_metadata = report_questions_data.get(
"metadata", {}
)

# Handle both dict format and list format
if isinstance(report_sections, list):
Expand All @@ -230,22 +239,24 @@ def process_grant(grant_id, grant_config):
report_sections,
grant_id,
grant_config["name"],
grant_config["foundation"]
grant_config["foundation"],
)
report_name = report_dir.name

# Store report data separately
reports_data.append({
"period": report_name,
"metadata": report_metadata,
"responses": report_responses
})
reports_data.append(
{
"period": report_name,
"metadata": report_metadata,
"responses": report_responses,
}
)

for key, value in report_responses.items():
all_responses[f"report_{report_name}_{key}"] = {
**value,
"type": "report",
"report_period": report_name
"report_period": report_name,
}

responses = all_responses
Expand All @@ -259,33 +270,43 @@ def process_grant(grant_id, grant_config):
questions_path = nsf_config_path
else:
# Try old location (pritzker_questions.yaml)
questions_path = grant_path / f"{grant_id}_questions.yaml"

with open(questions_path) as f:
questions_data = yaml.safe_load(f)

# Process responses
sections = questions_data.get("sections", {})

# Handle both dict format (Pritzker) and list format (PBIF)
if isinstance(sections, list):
# Convert list to dict for uniform processing
sections = {
item.get("id", f"section_{i}"): item
for i, item in enumerate(sections)
if "file" in item
}
elif not isinstance(sections, dict):
sections = {}

responses = process_sections(
grant_path,
grant_path,
sections,
grant_id,
grant_config["name"],
grant_config["foundation"]
)
legacy_questions_path = (
grant_path / f"{grant_id}_questions.yaml"
)
questions_path = (
legacy_questions_path
if legacy_questions_path.exists()
else None
)

if questions_path is None:
responses = {}
else:
with open(questions_path) as f:
questions_data = yaml.safe_load(f)

# Process responses
sections = questions_data.get("sections", {})

# Handle both dict format (Pritzker) and list format (PBIF)
if isinstance(sections, list):
# Convert list to dict for uniform processing
sections = {
item.get("id", f"section_{i}"): item
for i, item in enumerate(sections)
if "file" in item
}
elif not isinstance(sections, dict):
sections = {}

responses = process_sections(
grant_path,
grant_path,
sections,
grant_id,
grant_config["name"],
grant_config["foundation"],
)

result = {
"id": grant_id,
Expand Down
69 changes: 51 additions & 18 deletions grants_builder/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import tempfile


def create_markdown_document(response_markdown, title, question, grant_name, foundation):
def create_markdown_document(
response_markdown, title, question, grant_name, foundation
):
"""Create a complete markdown document with header."""
doc = f"""# {grant_name}
**{foundation}**
Expand Down Expand Up @@ -33,21 +35,32 @@ def export_to_docx(
)

# Write to temp file
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as tmp:
with tempfile.NamedTemporaryFile(
mode="w", suffix=".md", delete=False
) as tmp:
tmp.write(full_markdown)
tmp_path = tmp.name

try:
# Use pandoc to convert to DOCX with Inter font and smaller size for tables
result = subprocess.run(
['pandoc', tmp_path, '-o', str(output_path),
'--from=markdown', '--to=docx',
'-V', 'mainfont=Inter',
'-V', 'fontsize=9pt',
'-V', 'geometry:margin=0.75in'],
[
"pandoc",
tmp_path,
"-o",
str(output_path),
"--from=markdown",
"--to=docx",
"-V",
"mainfont=Inter",
"-V",
"fontsize=9pt",
"-V",
"geometry:margin=0.75in",
],
check=True,
capture_output=True,
text=True
text=True,
)
except subprocess.CalledProcessError as e:
raise Exception(f"Pandoc DOCX conversion failed: {e.stderr}")
Expand All @@ -60,42 +73,62 @@ def export_to_pdf(
):
"""Export response to PDF via DOCX conversion (better rendering than LaTeX)."""
# First create DOCX
docx_temp = output_path.with_suffix('.temp.docx')
docx_temp = output_path.with_suffix(".temp.docx")
export_to_docx(
response_markdown, docx_temp, title, question, grant_name, foundation
)

try:
# Convert DOCX to PDF using soffice (LibreOffice)
result = subprocess.run(
['soffice', '--headless', '--convert-to', 'pdf', '--outdir',
str(output_path.parent), str(docx_temp)],
[
"soffice",
"--headless",
"--convert-to",
"pdf",
"--outdir",
str(output_path.parent),
str(docx_temp),
],
check=True,
capture_output=True,
text=True,
timeout=30
timeout=30,
)
# soffice outputs with the temp filename, rename it
soffice_output = output_path.parent / f"{docx_temp.stem}.pdf"
if soffice_output.exists():
soffice_output.rename(output_path)
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
except (
subprocess.CalledProcessError,
FileNotFoundError,
subprocess.TimeoutExpired,
) as e:
# Fallback to pandoc with better PDF settings
full_markdown = create_markdown_document(
response_markdown, title, question, grant_name, foundation
)
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as tmp:
with tempfile.NamedTemporaryFile(
mode="w", suffix=".md", delete=False
) as tmp:
tmp.write(full_markdown)
tmp_path = tmp.name

try:
subprocess.run(
['pandoc', tmp_path, '-o', str(output_path),
'--from=markdown', '--pdf-engine=xelatex',
'-V', 'geometry:margin=1in'],
[
"pandoc",
tmp_path,
"-o",
str(output_path),
"--from=markdown",
"--pdf-engine=xelatex",
"-V",
"geometry:margin=1in",
],
check=True,
capture_output=True,
text=True
text=True,
)
except subprocess.CalledProcessError as e:
raise Exception(f"PDF conversion failed: {e.stderr}")
Expand Down