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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ htmlcov/
coverage.xml
coverage.json
!input/rodent_dataset.xlsx
input/*
input/*
phu_templates/*
!phu_templates/README.md
!phu_templates/.gitkeep
2 changes: 1 addition & 1 deletion AGENTS.MD
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

* **Orchestrator:** `pipeline/orchestrator.py` is the `viper` CLI entry point and coordinates 9 steps.
* **Steps:** Modules are organized by steps 1–9, not by functional themes.
* **Templates:** `templates/` contains `en_template.py`, `fr_template.py`. Import via `from templates import ...`. Typesetting is separate from orchestration.
* **Templates:** `templates/` contains default language templates (`en_template.py`, `fr_template.py`), Typst config (`conf.typ`), and assets. `phu_templates/` is the container for PHU-specific template customizations (gitignored). Templates are loaded dynamically at runtime via `build_language_renderers()` in `generate_notices.py`. The `--template` CLI argument expects a template name within `phu_templates/` (e.g., `--template wdgph``phu_templates/wdgph/`). Template names cannot contain path separators (no nesting). Each template module must define a `render_notice()` function. Template directory isolation: templates, conf.typ, and assets stay together. Typesetting is separate from orchestration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really thinking that we may need to clarify template name to the phu acronym!


---

Expand Down
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,18 @@ The main pipeline orchestrator (`orchestrator.py`) automates the end-to-end work

**Usage Example:**
```bash
uv run viper <input_file> <language> [--output-dir PATH]
uv run viper <input_file> <language> [--output PATH]
```

**Required Arguments:**
- `<input_file>`: Name of the input file (e.g., `students.xlsx`)
- `<language>`: Language code (`en` or `fr`)

**Optional Arguments:**
- `--input-dir PATH`: Input directory (default: ../input)
- `--output-dir PATH`: Output directory (default: ../output)
- `--config-dir PATH`: Configuration directory (default: ../config)
- `--input PATH`: Input directory (default: ../input)
- `--output PATH`: Output directory (default: ../output)
- `--config PATH`: Configuration directory (default: ../config)
- `--template NAME`: PHU template name within `phu_templates/` (e.g., `wdgph`); defaults to built-in `templates/` when omitted

**Configuration:**
See the complete configuration reference and examples in `config/README.md`:
Expand All @@ -171,9 +172,33 @@ Direct link: [Configuration Reference](./config/README.md)
uv run viper students.xlsx en

# Override output directory
uv run viper students.xlsx en --output-dir /tmp/output
uv run viper students.xlsx en --output /tmp/output

# Use a PHU-specific template (from phu_templates/my_phu/)
uv run viper students.xlsx en --template my_phu
```

### Using PHU-Specific Templates

Public Health Units can create custom template directories for organization-specific branding and layouts. All PHU templates live under the `phu_templates/` directory and are gitignored by default.

```bash
# Create your PHU template directory by copying defaults
cp -r templates/ phu_templates/my_phu/

# Customize templates and assets as needed, then run with your PHU template
uv run viper students.xlsx en --template my_phu
```

The `--template` argument expects a template name within `phu_templates/` (flat names only; no `/` or `\`). For example, `--template my_phu` loads from `phu_templates/my_phu/`.

Each PHU template directory should contain:
- `conf.typ` - Typst configuration and helper functions (required)
- `{lang}_template.py` - Language modules with `render_notice()` for the languages you intend to generate (e.g., `en_template.py` for English, `fr_template.py` for French). Single-language templates are supported.
- `assets/` - Optional directory for images like logos or signatures if your templates reference them

Templates are loaded dynamically at runtime, enabling different organizations to maintain separate template sets without modifying core code. By default (when `--template` is not specified), the pipeline uses the built-in `templates/` directory. It's recommended to start by copying from `templates/` into `phu_templates/<your_name>/` and customizing from there.

> ℹ️ **Typst preview note:** The WDGPH code-server development environments render Typst files via Tinymist. The shared template at `templates/conf.typ` only defines helper functions, colour tokens, and table layouts that the generated notice `.typ` files import; it doesn't emit any pages on its own, so Tinymist has nothing to preview if attempted on this file. To examine the actual markup that uses these helpers, run the pipeline with `pipeline.keep_intermediate_files: true` in `config/parameters.yaml` so the generated notice `.typ` files stay in `output/artifacts/` for manual inspection.

**Outputs:**
Expand Down
Empty file added phu_templates/.gitkeep
Empty file.
103 changes: 103 additions & 0 deletions phu_templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# PHU Templates Directory

This directory contains Public Health Unit (PHU) specific template customizations.

## Usage

Each PHU should create a subdirectory here with their organization-specific templates:

```
phu_templates/
├── my_phu/
│ ├── en_template.py (required for English output)
│ ├── fr_template.py (required for French output)
│ ├── conf.typ (required)
│ └── assets/ (optional - only if templates reference assets)
│ ├── logo.png (optional)
│ └── signature.png (optional)
```

## Running with PHU Templates

To use a PHU-specific template, specify the template name with `--template`:

```bash
# Generate English notices
uv run viper students.xlsx en --template my_phu

# Generate French notices
uv run viper students.xlsx fr --template my_phu
```

This will load templates from `phu_templates/my_phu/`.

## Template File Requirements

### Core Requirements (Always Required)

- `conf.typ` - Typst configuration and utility functions

### Language-Specific Requirements (Based on Output Language)

- `en_template.py` - Required only if generating English notices (`--language en`)
- Must define `render_notice()` function
- Consulted only when `--language en` is specified

- `fr_template.py` - Required only if generating French notices (`--language fr`)
- Must define `render_notice()` function
- Consulted only when `--language fr` is specified

**Note:** A PHU may provide templates for only one language. If a user requests a language your template does not support, the pipeline will fail with a clear error message. If you only support one language, only include that template file (e.g., only `en_template.py`).

### Asset Requirements (Based on Template Implementation)

Assets in the `assets/` directory are **optional** and depend entirely on your template implementation:

- `assets/logo.png` - Only required if your `en_template.py` or `fr_template.py` references a logo
- `assets/signature.png` - Only required if your `en_template.py` or `fr_template.py` references a signature
- Other files - Any additional assets (e.g., `assets/header.png`, `assets/seal.pdf`) may be included and referenced in your templates

**Note:** If your template references an asset (e.g., `include "assets/logo.png"` in Typst), that asset **must** exist. The pipeline will fail with a clear error if a referenced asset is missing.

## Creating a PHU Template

If your PHU supports both English and French:

```bash
cp -r templates/ phu_templates/my_phu/
```

Then customize:
- Replace `assets/logo.png` with your PHU logo
- Replace `assets/signature.png` with your signature
- Modify `en_template.py` and `fr_template.py` as needed
- Adjust `conf.typ` for organization-specific styling

### Testing Your Template

```bash
# Test English generation
uv run viper students.xlsx en --template my_phu

# Test French generation (if you provided fr_template.py)
uv run viper students.xlsx fr --template my_phu
```

If a language template is missing:
```
FileNotFoundError: Template module not found: /path/to/phu_templates/my_phu/fr_template.py
Expected fr_template.py in /path/to/phu_templates/my_phu
```

If an asset referenced by your template is missing:
```
FileNotFoundError: Logo not found: /path/to/phu_templates/my_phu/assets/logo.png
```

## Git Considerations

**Important:** PHU-specific templates are excluded from version control via `.gitignore`.

- Templates in this directory will NOT be committed to the main repository
- Each PHU should maintain their templates in their own fork or separate repository
- The `README.md` file and `.gitkeep` are the only tracked files in this directory
31 changes: 26 additions & 5 deletions pipeline/compile_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def compile_file(
Optional path to directory containing custom fonts.
root_dir : Path
Root directory for relative path resolution in Typst compilation.
This should be the template directory containing conf.typ and assets/.
verbose : bool
If True, print compilation status message.
"""
Expand Down Expand Up @@ -124,6 +125,7 @@ def compile_typst_files(
Optional custom fonts directory.
root_dir : Path
Root directory for relative path resolution.
Should be the template directory containing conf.typ and assets/.
verbose : bool
If True, print per-file compilation status.

Expand Down Expand Up @@ -154,17 +156,24 @@ def compile_with_config(
artifact_dir: Path,
output_dir: Path,
config_path: Path | None = None,
template_dir: Path | None = None,
) -> int:
"""Compile Typst files using configuration from parameters.yaml.

Reads typst configuration (binary path, font path) from parameters.yaml
and compiles all Typst files in the artifact directory.

Parameters
----------
artifact_dir : Path
Directory containing Typst artifacts (.typ files).
Directory containing Typst template files
Copy link
Member Author

@jangevaare jangevaare Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was previous documentation was better? Here .typs are treated as client specific artifacts. These are by definition typst template files, but we use the term template in the repository in other ways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like use of word template better. Maybe a hybrid?

"Directory containing Typst template files (.typ)."

output_dir : Path
Directory where compiled PDFs will be written.
Directory where compiled PDFs will be written
config_path : Path, optional
Path to parameters.yaml. If not provided, uses default location.
template_dir : Path, optional
Template directory for dynamic template loading. Typst compilation always
uses PROJECT_ROOT as --root to find both templates and output artifacts.

Returns
-------
Expand All @@ -182,17 +191,26 @@ def compile_with_config(

font_path = Path(font_path_str) if font_path_str else None

# Typst compilation always uses PROJECT_ROOT to find both templates and output artifacts.
# template_dir parameter is for dynamic template loading in generate_notices, not for Typst --root.
root_dir = ROOT_DIR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary if a constant?


return compile_typst_files(
artifact_dir,
output_dir,
typst_bin=typst_bin,
font_path=font_path,
root_dir=ROOT_DIR,
root_dir=root_dir,
verbose=False,
)


def main(artifact_dir: Path, output_dir: Path, config_path: Path | None = None) -> int:
def main(
artifact_dir: Path,
output_dir: Path,
config_path: Path | None = None,
template_dir: Path | None = None,
) -> int:
"""Main entry point for Typst compilation.

Parameters
Expand All @@ -203,13 +221,16 @@ def main(artifact_dir: Path, output_dir: Path, config_path: Path | None = None)
Directory for output PDFs.
config_path : Path, optional
Path to parameters.yaml configuration file.
template_dir : Path, optional
Template directory containing conf.typ and assets/. Used as Typst --root.
If not provided, defaults to project root.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If not provided, defaults to project root.
If not provided, defaults to project root, runs in test mode


Returns
-------
int
Number of files compiled.
"""
compiled = compile_with_config(artifact_dir, output_dir, config_path)
compiled = compile_with_config(artifact_dir, output_dir, config_path, template_dir)
if compiled:
print(f"Compiled {compiled} Typst file(s) to PDFs in {output_dir}.")
return compiled
Expand Down
Loading