diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39f5579..2cb2ddd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,4 +4,10 @@ repos: hooks: - id: ruff-check args: [--fix] # Lint and auto-fix - - id: ruff-format # Format code like black \ No newline at end of file + - id: ruff-format # Format code like black + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: pretty-format-json + args: [--autofix] + files: \.json$ \ No newline at end of file diff --git a/config/disease_normalization.json b/config/disease_normalization.json index 135db27..53653a7 100644 --- a/config/disease_normalization.json +++ b/config/disease_normalization.json @@ -1,8 +1,8 @@ { "Haemophilus influenzae infection, invasive": "Hib", "Haemophilus influenzae infection,invasive": "Hib", - "Poliomyelitis": "Polio", "Human papilloma virus infection": "HPV", "Human papillomavirus infection": "HPV", + "Poliomyelitis": "Polio", "Varicella": "Varicella" } diff --git a/config/map_school.json b/config/map_school.json new file mode 100644 index 0000000..9bc21a7 --- /dev/null +++ b/config/map_school.json @@ -0,0 +1,34 @@ +{ + "BURROW_PUBLIC_SCHOOL": { + "phu_address": "Burrow Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca" + }, + "CHEESE_WHEEL_ACADEMY": { + "phu_address": "Cheese Wheel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234" + }, + "DEFAULT": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca" + }, + "DOWNTOWN_COLLEGIATE": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234" + }, + "MOUNTAIN_HEIGHTS_PUBLIC_SCHOOL": { + "phu_address": "Mountain Heights Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234" + }, + "RIVER_VALLEY_ELEMENTARY": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth@rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234" + } +} diff --git a/config/translations/en_diseases_chart.json b/config/translations/en_diseases_chart.json index 9663d4b..05d4fd1 100644 --- a/config/translations/en_diseases_chart.json +++ b/config/translations/en_diseases_chart.json @@ -6,12 +6,12 @@ "Measles": "Measles", "Meningococcal": "Meningococcal", "Mumps": "Mumps", + "Other": "Other", "Pertussis": "Pertussis", "Pneumococcal": "Pneumococcal", "Polio": "Polio", "Rotavirus": "Rotavirus", "Rubella": "Rubella", "Tetanus": "Tetanus", - "Varicella": "Varicella", - "Other": "Other" + "Varicella": "Varicella" } diff --git a/config/translations/fr_diseases_chart.json b/config/translations/fr_diseases_chart.json index e09a1c1..4c71be1 100644 --- a/config/translations/fr_diseases_chart.json +++ b/config/translations/fr_diseases_chart.json @@ -1,17 +1,17 @@ { - "Diphtheria": "Diphtérie", + "Diphtheria": "Dipht\u00e9rie", "HPV": "VPH", - "Hepatitis B": "Hépatite B", + "Hepatitis B": "H\u00e9patite B", "Hib": "Hib", "Measles": "Rougeole", - "Meningococcal": "Méningocoque", + "Meningococcal": "M\u00e9ningocoque", "Mumps": "Oreillons", + "Other": "Autre", "Pertussis": "Coqueluche", "Pneumococcal": "Pneumocoque", - "Polio": "Poliomyélite", + "Polio": "Poliomy\u00e9lite", "Rotavirus": "Rotavirus", - "Rubella": "Rubéole", - "Tetanus": "Tétanos", - "Varicella": "Varicelle", - "Other": "Autre" + "Rubella": "Rub\u00e9ole", + "Tetanus": "T\u00e9tanos", + "Varicella": "Varicelle" } diff --git a/config/translations/fr_diseases_overdue.json b/config/translations/fr_diseases_overdue.json index 1cbeee8..70507ba 100644 --- a/config/translations/fr_diseases_overdue.json +++ b/config/translations/fr_diseases_overdue.json @@ -1,16 +1,16 @@ { - "Diphtheria": "Diphtérie", + "Diphtheria": "Dipht\u00e9rie", "HPV": "VPH", - "Hepatitis B": "Hépatite B", + "Hepatitis B": "H\u00e9patite B", "Hib": "Hib", "Measles": "Rougeole", - "Meningococcal": "Méningocoque", + "Meningococcal": "M\u00e9ningocoque", "Mumps": "Oreillons", "Pertussis": "Coqueluche", "Pneumococcal": "Pneumocoque", - "Polio": "Poliomyélite", + "Polio": "Poliomy\u00e9lite", "Rotavirus": "Rotavirus", - "Rubella": "Rubéole", - "Tetanus": "Tétanos", + "Rubella": "Rub\u00e9ole", + "Tetanus": "T\u00e9tanos", "Varicella": "Varicelle" -} \ No newline at end of file +} diff --git a/config/vaccine_reference.json b/config/vaccine_reference.json index 35864de..3727f83 100644 --- a/config/vaccine_reference.json +++ b/config/vaccine_reference.json @@ -1,370 +1,370 @@ { - "aP": [ - "Pertussis" - ], - "ap-unspecified": [ - "Pertussis" - ], - "BAT": [ - "Other" - ], - "BCG vaccine": [ - "Other" - ], - "Chol": [ - "Other" - ], - "Chol-Ecol-O": [ - "Other" - ], - "D": [ - "Diphtheria" - ], - "DPT": [ - "Diphtheria", - "Tetanus", - "Pertussis" - ], - "DPT-HB": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Hepatitis B" - ], - "DPT-HB-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Hepatitis B", - "Hib" - ], - "DPT-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Hib" - ], - "DPT-IPV": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio" - ], - "DPTP": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio" - ], - "DPTP-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio", - "Hib" - ], - "DT": [ - "Diphtheria", - "Tetanus" - ], - "DTaP": [ - "Diphtheria", - "Tetanus", - "Pertussis" - ], - "DTaP-HB-IPV": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio", - "Hepatitis B" - ], - "DTaP-HB-IPV-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio", - "Hepatitis B", - "Hib" - ], - "DTaP-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Hib" - ], - "DTaP-IPV": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio" - ], - "DTaP-IPV-Hib": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio", - "Hib" - ], - "DT-IPV": [ - "Diphtheria", - "Tetanus", - "Polio" - ], - "DTwP-HB": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Hepatitis B" - ], - "d-unspecified": [ - "Diphtheria" - ], - "H1N1": [ - "Other" - ], - "HA": [ - "Other" - ], - "HAHB": [ - "Hepatitis B", - "Other" - ], - "HAHB-pediatric": [ - "Hepatitis B", - "Other" - ], - "HAHB-unspecified": [ - "Hepatitis B", - "Other" - ], - "HA-pediatric": [ - "Other" - ], - "HA-Typh-I": [ - "Other" - ], - "HA-unspecified": [ - "Other" - ], - "HB": [ - "Hepatitis B" - ], - "HB-dialysis": [ - "Hepatitis B" - ], - "HB-pediatric": [ - "Hepatitis B" - ], - "HB-unspecified": [ - "Hepatitis B" - ], - "Hib": [ - "Hib" - ], - "Hib-HB": [ - "Hepatitis B", - "Hib" - ], - "HPV-2": [ - "HPV" - ], - "HPV-4": [ - "HPV" - ], - "HPV-9": [ - "HPV" - ], - "hpv-unspecified": [ - "HPV" - ], - "Inf (QIV)": [ - "Other" - ], - "Inf (TIV)": [ - "Other" - ], - "Inf High Dose (TIV)": [ - "Other" - ], - "inf-unspecified": [ - "Other" - ], - "IPV": [ - "Polio" - ], - "JE": [ - "Other" - ], - "LAIV": [ - "Other" - ], - "M": [ - "Measles" - ], - "men-AC unspecified": [ - "Meningococcal" - ], - "Men-ACYW-135-unspecified": [ - "Meningococcal" - ], - "Men-B": [ - "Meningococcal" - ], - "Men-C-A": [ - "Meningococcal" - ], - "Men-C-AC": [ - "Meningococcal" - ], - "Men-C-ACYW-135": [ - "Meningococcal" - ], - "Men-C-C": [ - "Meningococcal" - ], - "Men-C-CY-Hib": [ - "Meningococcal", - "Hib" - ], - "men-c-unspecified": [ - "Meningococcal" - ], - "men-p-AC unspecified": [ - "Meningococcal" - ], - "Men-P-ACYW-135": [ - "Meningococcal" - ], - "men-p-A unspecified": [ - "Meningococcal" - ], - "men-p-unspecified": [ - "Meningococcal" - ], - "men-unspecified": [ - "Meningococcal" - ], - "MMR": [ - "Measles", - "Mumps", - "Rubella" - ], - "MMR-Var": [ - "Measles", - "Mumps", - "Rubella", - "Varicella" - ], - "MR": [ - "Measles", - "Rubella" - ], - "Mu": [ - "Mumps" - ], - "OPV": [ - "Polio" - ], - "pertussis-unspecified": [ - "Pertussis" - ], - "Pneu-C-10": [ - "Pneumococcal" - ], - "Pneu-C-13": [ - "Pneumococcal" - ], - "Pneu-C-15": [ - "Pneumococcal" - ], - "Pneu-C-20": [ - "Pneumococcal" - ], - "Pneu-C-7": [ - "Pneumococcal" - ], - "pneu-c-unspecified": [ - "Pneumococcal" - ], - "Pneu-P-23": [ - "Pneumococcal" - ], - "pneu-p-unspecified": [ - "Pneumococcal" - ], - "pneu-unspecified": [ - "Pneumococcal" - ], - "p-unspecified": [ - "Polio" - ], - "R": [ - "Rubella" - ], - "Rab": [ - "Other" - ], - "Rota-1": [ - "Rotavirus" - ], - "Rota-5": [ - "Rotavirus" - ], - "rota-unspecified": [ - "Rotavirus" - ], - "RSV": [ - "Other" - ], - "Sma": [ - "Other" - ], - "T": [ - "Tetanus" - ], - "TBE": [ - "Other" - ], - "Td": [ - "Diphtheria", - "Tetanus" - ], - "Tdap": [ - "Diphtheria", - "Tetanus", - "Pertussis" - ], - "Tdap-IPV": [ - "Diphtheria", - "Tetanus", - "Pertussis", - "Polio" - ], - "Td-IPV": [ - "Diphtheria", - "Tetanus", - "Polio" - ], - "Typh-I": [ - "Other" - ], - "Typh-O": [ - "Other" - ], - "typh-unspecified": [ - "Other" - ], - "Var": [ - "Varicella" - ], - "YF": [ - "Other" - ], - "Zos": [ - "Other" - ], - "Zos-unspecified": [ - "Other" - ] -} \ No newline at end of file + "BAT": [ + "Other" + ], + "BCG vaccine": [ + "Other" + ], + "Chol": [ + "Other" + ], + "Chol-Ecol-O": [ + "Other" + ], + "D": [ + "Diphtheria" + ], + "DPT": [ + "Diphtheria", + "Tetanus", + "Pertussis" + ], + "DPT-HB": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Hepatitis B" + ], + "DPT-HB-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Hepatitis B", + "Hib" + ], + "DPT-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Hib" + ], + "DPT-IPV": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio" + ], + "DPTP": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio" + ], + "DPTP-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio", + "Hib" + ], + "DT": [ + "Diphtheria", + "Tetanus" + ], + "DT-IPV": [ + "Diphtheria", + "Tetanus", + "Polio" + ], + "DTaP": [ + "Diphtheria", + "Tetanus", + "Pertussis" + ], + "DTaP-HB-IPV": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio", + "Hepatitis B" + ], + "DTaP-HB-IPV-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio", + "Hepatitis B", + "Hib" + ], + "DTaP-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Hib" + ], + "DTaP-IPV": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio" + ], + "DTaP-IPV-Hib": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio", + "Hib" + ], + "DTwP-HB": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Hepatitis B" + ], + "H1N1": [ + "Other" + ], + "HA": [ + "Other" + ], + "HA-Typh-I": [ + "Other" + ], + "HA-pediatric": [ + "Other" + ], + "HA-unspecified": [ + "Other" + ], + "HAHB": [ + "Hepatitis B", + "Other" + ], + "HAHB-pediatric": [ + "Hepatitis B", + "Other" + ], + "HAHB-unspecified": [ + "Hepatitis B", + "Other" + ], + "HB": [ + "Hepatitis B" + ], + "HB-dialysis": [ + "Hepatitis B" + ], + "HB-pediatric": [ + "Hepatitis B" + ], + "HB-unspecified": [ + "Hepatitis B" + ], + "HPV-2": [ + "HPV" + ], + "HPV-4": [ + "HPV" + ], + "HPV-9": [ + "HPV" + ], + "Hib": [ + "Hib" + ], + "Hib-HB": [ + "Hepatitis B", + "Hib" + ], + "IPV": [ + "Polio" + ], + "Inf (QIV)": [ + "Other" + ], + "Inf (TIV)": [ + "Other" + ], + "Inf High Dose (TIV)": [ + "Other" + ], + "JE": [ + "Other" + ], + "LAIV": [ + "Other" + ], + "M": [ + "Measles" + ], + "MMR": [ + "Measles", + "Mumps", + "Rubella" + ], + "MMR-Var": [ + "Measles", + "Mumps", + "Rubella", + "Varicella" + ], + "MR": [ + "Measles", + "Rubella" + ], + "Men-ACYW-135-unspecified": [ + "Meningococcal" + ], + "Men-B": [ + "Meningococcal" + ], + "Men-C-A": [ + "Meningococcal" + ], + "Men-C-AC": [ + "Meningococcal" + ], + "Men-C-ACYW-135": [ + "Meningococcal" + ], + "Men-C-C": [ + "Meningococcal" + ], + "Men-C-CY-Hib": [ + "Meningococcal", + "Hib" + ], + "Men-P-ACYW-135": [ + "Meningococcal" + ], + "Mu": [ + "Mumps" + ], + "OPV": [ + "Polio" + ], + "Pneu-C-10": [ + "Pneumococcal" + ], + "Pneu-C-13": [ + "Pneumococcal" + ], + "Pneu-C-15": [ + "Pneumococcal" + ], + "Pneu-C-20": [ + "Pneumococcal" + ], + "Pneu-C-7": [ + "Pneumococcal" + ], + "Pneu-P-23": [ + "Pneumococcal" + ], + "R": [ + "Rubella" + ], + "RSV": [ + "Other" + ], + "Rab": [ + "Other" + ], + "Rota-1": [ + "Rotavirus" + ], + "Rota-5": [ + "Rotavirus" + ], + "Sma": [ + "Other" + ], + "T": [ + "Tetanus" + ], + "TBE": [ + "Other" + ], + "Td": [ + "Diphtheria", + "Tetanus" + ], + "Td-IPV": [ + "Diphtheria", + "Tetanus", + "Polio" + ], + "Tdap": [ + "Diphtheria", + "Tetanus", + "Pertussis" + ], + "Tdap-IPV": [ + "Diphtheria", + "Tetanus", + "Pertussis", + "Polio" + ], + "Typh-I": [ + "Other" + ], + "Typh-O": [ + "Other" + ], + "Var": [ + "Varicella" + ], + "YF": [ + "Other" + ], + "Zos": [ + "Other" + ], + "Zos-unspecified": [ + "Other" + ], + "aP": [ + "Pertussis" + ], + "ap-unspecified": [ + "Pertussis" + ], + "d-unspecified": [ + "Diphtheria" + ], + "hpv-unspecified": [ + "HPV" + ], + "inf-unspecified": [ + "Other" + ], + "men-AC unspecified": [ + "Meningococcal" + ], + "men-c-unspecified": [ + "Meningococcal" + ], + "men-p-A unspecified": [ + "Meningococcal" + ], + "men-p-AC unspecified": [ + "Meningococcal" + ], + "men-p-unspecified": [ + "Meningococcal" + ], + "men-unspecified": [ + "Meningococcal" + ], + "p-unspecified": [ + "Polio" + ], + "pertussis-unspecified": [ + "Pertussis" + ], + "pneu-c-unspecified": [ + "Pneumococcal" + ], + "pneu-p-unspecified": [ + "Pneumococcal" + ], + "pneu-unspecified": [ + "Pneumococcal" + ], + "rota-unspecified": [ + "Rotavirus" + ], + "typh-unspecified": [ + "Other" + ] +} diff --git a/input/test_dataset.xlsx b/input/test_dataset.xlsx new file mode 100644 index 0000000..1fc3d55 Binary files /dev/null and b/input/test_dataset.xlsx differ diff --git a/pipeline/config_loader.py b/pipeline/config_loader.py index a57d20b..5a1347f 100644 --- a/pipeline/config_loader.py +++ b/pipeline/config_loader.py @@ -87,6 +87,7 @@ def validate_config(config: Dict[str, Any]) -> None: - All error messages are clear and actionable - Config is validated once at load time, not per-step """ + # Validate QR config qr_config = config.get("qr", {}) qr_enabled = qr_config.get("enabled", True) diff --git a/pipeline/generate_notices.py b/pipeline/generate_notices.py index d7353b1..70e402c 100644 --- a/pipeline/generate_notices.py +++ b/pipeline/generate_notices.py @@ -40,6 +40,7 @@ from __future__ import annotations import json +import re import logging from pathlib import Path from typing import Dict, List, Mapping, Sequence @@ -255,7 +256,10 @@ def load_and_translate_chart_diseases(language: str) -> List[str]: def build_template_context( - client: ClientRecord, qr_output_dir: Path | None = None + client: ClientRecord, + qr_output_dir: Path | None = None, + map_file: Path = ROOT_DIR / "config/map_school.json", + required_keys: Dict = {"phu_address", "phu_phone", "phu_email", "phu_website"}, ) -> Dict[str, str]: """Build template context from client data. @@ -270,7 +274,12 @@ def build_template_context( Client record with all required fields. qr_output_dir : Path, optional Directory containing QR code PNG files. - + map_file: Filepath, optional + File containing mapping of schools to specific info (e.g. satellite PHU office info) to populate template. + By default, will use config/map_school.json. + required_keys: Dict, optional + Dictionary containing the keys that should come from the mapping file. + Each of these keys should be present in the "DEFAULT" section of the mapping file. Returns ------- Dict[str, str] @@ -306,6 +315,59 @@ def build_template_context( if qr_path.exists(): client_data["qr_code"] = to_root_relative(qr_path) + # Check if mapping file exists + if not map_file.exists(): + raise FileNotFoundError( + f"Expected school mapping file at {map_file}, but file does not exist. Please provide mapping file at {map_file}." + ) + + # Load mapping file data + with open(map_file, "r") as f: + map_data = json.load(f) + + # Attempt to load default PHU data values from mapping file; this should also contain all required keys + try: + phu_data = map_data["DEFAULT"] + except KeyError as err: + raise ( + f"Loading default PHU info, error when attempting to access 'DEFAULT' from {map_file}: {err}" + ) + + if not phu_data: + raise ValueError( + "Default values for PHU info not provided. " + f'Please define DEFAULT values {required_keys} in under "DEFAULT" key in {map_file}.' + ) + else: + missing = [key for key in required_keys if key not in phu_data] + if missing: + missing_keys = ", ".join(missing) + raise KeyError(f"Missing phu_data keys in config: {missing_keys}") + + # Clean school name + client_school_key = re.sub(r"\s+", "_", client_data["school"]).upper() + + # Check if school has a mapping associated with it - otherwise, use default config values + if client_school_key in map_data.keys(): + if client_school_key != "DEFAULT": + LOG.info(f"School-specific information provided for: {client_school_key}") + + map_client_data = map_data[client_school_key] + + # Replace default values with values in map file. If any are missing, keep default values. + for key in phu_data.keys(): + if key in map_client_data.keys(): + phu_data[key] = map_client_data[key] + else: + LOG.info( + f"Mapping file for school {client_school_key} missing {key}. Using default value." + ) + + else: + LOG.info( + f"School {client_school_key} not in mapping file. Using default values." + ) + # Load and translate chart disease header chart_diseases_translated = load_and_translate_chart_diseases(client.language) @@ -346,6 +408,7 @@ def build_template_context( return { "client_row": to_typ_value([client.client_id]), "client_data": to_typ_value(client_data), + "phu_data": phu_data, "vaccines_due_str": to_typ_value(vaccines_due_str_translated), "vaccines_due_array": to_typ_value(vaccines_due_array_translated), "received": to_typ_value(received_translated), @@ -405,10 +468,7 @@ def render_notice( def generate_typst_files( - payload: ArtifactPayload, - output_dir: Path, - logo_path: Path, - signature_path: Path, + payload: ArtifactPayload, output_dir: Path, logo_path: Path, signature_path: Path ) -> List[Path]: output_dir.mkdir(parents=True, exist_ok=True) qr_output_dir = output_dir / "qr_codes" @@ -437,10 +497,7 @@ def generate_typst_files( def main( - artifact_path: Path, - output_dir: Path, - logo_path: Path, - signature_path: Path, + artifact_path: Path, output_dir: Path, logo_path: Path, signature_path: Path ) -> List[Path]: """Main entry point for Typst notice generation. @@ -461,12 +518,7 @@ def main( List of generated Typst file paths. """ payload = read_artifact(artifact_path) - generated = generate_typst_files( - payload, - output_dir, - logo_path, - signature_path, - ) + generated = generate_typst_files(payload, output_dir, logo_path, signature_path) print( f"Generated {len(generated)} Typst files in {output_dir} for language {payload.language}" ) diff --git a/pipeline/orchestrator.py b/pipeline/orchestrator.py index 21caaca..a59480a 100755 --- a/pipeline/orchestrator.py +++ b/pipeline/orchestrator.py @@ -261,10 +261,7 @@ def run_step_4_generate_notices( # Generate Typst files using main function generated = generate_notices.main( - artifact_path, - artifacts_dir, - logo_path, - signature_path, + artifact_path, artifacts_dir, logo_path, signature_path ) print(f"Generated {len(generated)} Typst files in {artifacts_dir}") @@ -509,10 +506,7 @@ def main() -> int: # Step 4: Generating Notices step_start = time.time() run_step_4_generate_notices( - output_dir, - run_id, - DEFAULT_TEMPLATES_ASSETS_DIR, - config_dir, + output_dir, run_id, DEFAULT_TEMPLATES_ASSETS_DIR, config_dir ) step_duration = time.time() - step_start step_times.append(("Template Generation", step_duration)) diff --git a/scripts/2025_mock_generate_template_english_map.sh b/scripts/2025_mock_generate_template_english_map.sh new file mode 100755 index 0000000..476ca52 --- /dev/null +++ b/scripts/2025_mock_generate_template_english_map.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +INDIR=${1} +FILENAME=${2} +LOGO=${3} +SIGNATURE=${4} +PARAMETERS=${5} +MAP_SCHOOL=${6} + +CLIENTIDFILE=${FILENAME}_client_ids.csv +JSONFILE=${FILENAME}.json +OUTFILE=${INDIR}/${FILENAME}_immunization_notice.typ + + +echo " +// --- CCEYA NOTICE TEMPLATE (TEST VERSION) --- // +// Description: A typst template that dynamically generates 2025 cceya templates for phsd. +// NOTE: All contact details are placeholders for testing purposes only. +// Author: Kassy Raymond +// Date Created: 2025-06-25 +// Date Last Updated: 2025-09-16 +// ----------------------------------------- // + +#import \"conf.typ\" + +// General document formatting +#set text(fill: black) +#set par(justify: false) +#set page(\"us-letter\") + +// Formatting links +#show link: underline + +// Font formatting +#set text( + font: \"FreeSans\", + size: 10pt +) + +// Read current date from yaml file +#let date(contents) = { + contents.date_today +} + +// Read diseases from yaml file +#let diseases_yaml(contents) = { + contents.chart_diseases_header +} + +#let diseases = diseases_yaml(yaml(\"${PARAMETERS}\")) +#let date = date(yaml(\"${PARAMETERS}\")) + +// Immunization Notice Section +#let immunization_notice(client, client_id, immunizations_due, date, font_size, school_address, school_phone) = block[ + +#v(0.2cm) + +#conf.header_info_cim(\"${LOGO}\") + +#v(0.2cm) + +#conf.client_info_tbl_en(equal_split: false, vline: false, client, client_id, font_size) + +#v(0.3cm) + +// Notice for immunizations +As of *#date* our files show that your child has not received the following immunization(s): + +#conf.client_immunization_list(immunizations_due) + +Please review the Immunization Record on page 2 and update your child's record by using one of the following options: + +1. By visiting #text(fill:conf.linkcolor)[#link(\"https://www.test-immunization.ca\")] +2. By emailing #text(fill:conf.linkcolor)[#link(\"records@test-immunization.ca\")] +3. By mailing a photocopy of your child’s immunization record to #school_address +4. By Phone: #school_phone + +Please update Public Health and your childcare centre every time your child receives a vaccine. By keeping your child's vaccinations up to date, you are not only protecting their health but also the health of other children and staff at the childcare centre. + +*If you are choosing not to immunize your child*, a valid medical exemption or statement of conscience or religious belief must be completed and submitted to Public Health. Links to these forms can be located at #text(fill:conf.wdgteal)[#link(\"https://www.test-immunization.ca/exemptions\")]. Please note this exemption is for childcare only and a new exemption will be required upon enrollment in elementary school. + +If there is an outbreak of a vaccine-preventable disease, Public Health may require that children who are not adequately immunized (including those with exemptions) be excluded from the childcare centre until the outbreak is over. + +If you have any questions about your child’s vaccines, please call 555-555-5555 ext. 1234 to speak with a Public Health Nurse. + + Sincerely, + +#conf.signature(\"${SIGNATURE}\", \"Dr. Jane Smith, MPH\", \"Associate Medical Officer of Health\") + +] + +#let vaccine_table_page(client_id) = block[ + + #v(0.5cm) + + #grid( + + columns: (50%,50%), + gutter: 5%, + [#image(\"${LOGO}\", width: 6cm)], + [#set align(center + bottom) + #text(size: 20.5pt, fill: black)[*Immunization Record*]] + +) + + #v(0.5cm) + + For your reference, the immunization(s) on file with Public Health are as follows: + +] + +#let end_of_immunization_notice() = [ + #set align(center) + End of immunization record ] + +#let client_ids = csv(\"${CLIENTIDFILE}\", delimiter: \",\", row-type: array) + +#for row in client_ids { + + let reset = <__reset> + let subtotal() = { + let loc = here() + let list = query(selector(reset).after(loc)) + if list.len() > 0 { + counter(page).at(list.first().location()).first() - 1 + } else { + counter(page).final().first() + } +} + + let page-numbers = context numbering( + \"1 / 1\", + ..counter(page).get(), + subtotal(), + ) + + set page(margin: (top: 1cm, bottom: 2cm, left: 1.75cm, right: 2cm), + footer: align(center, page-numbers)) + + let value = row.at(0) // Access the first (and only) element of the row + let data = json(\"${JSONFILE}\").at(value) + let received = data.received + + let school_address = \"Test Health, 123 Placeholder Street, Sample City, ON A1A 1A1\" + let school_phone = \"555-555-5555 ext. 1234\" + + if \"${MAP_SCHOOL}\" != \"false\" { + let school = upper(data.school.replace(regex(\"\\s+\"), \"_\")) + let school_data = json(\"${MAP_SCHOOL}\").at(school) + school_address = school_data.phu_address + school_phone = school_data.phu_phone + } + + let num_rows = received.len() + + // get vaccines due, split string into an array of sub strings + let vaccines_due = data.vaccines_due + + let vaccines_due_array = vaccines_due.split(\", \") + + let section(it) = { + [#metadata(none)#reset] + pagebreak(weak: true) + counter(page).update(1) // Reset page counter for this section + pagebreak(weak: true) + immunization_notice(data, row, vaccines_due_array, date, 11pt, school_address, school_phone) + pagebreak() + vaccine_table_page(value) + conf.immunization-table(5, num_rows, received, diseases, 11pt) + end_of_immunization_notice() + } + + section([] + page-numbers) + +} + + +" > "${OUTFILE}" \ No newline at end of file diff --git a/scripts/generate_notices_map.sh b/scripts/generate_notices_map.sh new file mode 100755 index 0000000..5240be9 --- /dev/null +++ b/scripts/generate_notices_map.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +OUTDIR="../output" +LANG=$1 +MAP_SCHOOL=$2 + +if [ "$MAP_SCHOOL" != "false" ]; then + MAP_SCHOOL="../../config/map_school.json" + echo "Using school mapping file: $MAP_SCHOOL" +fi + +echo "Generating templates..." + +for jsonfile in ${OUTDIR}/json_${LANG}/*.json; do + filename=$(basename "$jsonfile" .json) + echo "Processing $filename" + ./2025_mock_generate_template_${LANG}.sh "${OUTDIR}/json_${LANG}" "$filename" \ + "../../assets/logo.png" \ + "../../assets/signature.png" \ + "../../config/parameters.yaml" \ + $MAP_SCHOOL +done diff --git a/scripts/run_pipeline_map.sh b/scripts/run_pipeline_map.sh new file mode 100755 index 0000000..e96c8f6 --- /dev/null +++ b/scripts/run_pipeline_map.sh @@ -0,0 +1,167 @@ +#!/bin/bash +set -e + +if [ $# -lt 2 ]; then + echo "Usage: $0 [--no-cleanup]" + exit 1 +fi + +INFILE=$1 +LANG=$2 +SKIP_CLEANUP=false +MAP_SCHOOL=false + + +# Shift past the first two arguments +shift 2 + +# Define allowed flags +allowed_flags=("--no-cleanup" "--map-school") + +# Loop through remaining arguments +while [[ $# -gt 0 ]]; do + flag="$1" + shift + + # Check if flag is allowed + if [[ " ${allowed_flags[@]} " =~ " ${flag} " ]]; then + case $flag in + --no-cleanup) + SKIP_CLEANUP=true + ;; + --map-school) + MAP_SCHOOL=true + ;; + *) + echo "Unknown option: $flag" + echo "Usage: $0 [--no-cleanup] [--map-school]" + exit 1 + ;; + esac + + else + echo "Error: Unknown flag $flag" + exit 1 + fi +done + +INDIR="../input" +OUTDIR="../output" +BATCH_SIZE=100 + +if [ "$LANG" != "english" ] && [ "$LANG" != "french" ]; then + echo "Error: Language must be 'english' or 'french'" + exit 1 +fi + +echo "" +echo "🚀 Starting VIPER Pipeline" +echo "🗂️ Input File: ${INFILE}" +echo "" + +TOTAL_START=$(date +%s) + + +########################################## +# Step 1: Preprocessing +########################################## +STEP1_START=$(date +%s) +echo "" +echo "🔍 Step 1: Preprocessing started..." +python preprocess.py ${INDIR} ${INFILE} ${OUTDIR} ${LANG} ${BATCH_SIZE} +STEP1_END=$(date +%s) +STEP1_DURATION=$((STEP1_END - STEP1_START)) +echo "✅ Step 1: Preprocessing complete in ${STEP1_DURATION} seconds." + +########################################## +# Record count +########################################## +CSV_PATH="${INDIR}/${CSVFILE}" +if [ -f "$CSV_PATH" ]; then + TOTAL_RECORDS=$(tail -n +2 "$CSV_PATH" | wc -l) + echo "📊 Total records (excluding header): $TOTAL_RECORDS" +else + echo "⚠️ CSV not found for record count: $CSV_PATH" +fi + +########################################## +# Step 2: Generating Notices +########################################## +STEP2_START=$(date +%s) +echo "" +echo "📝 Step 2: Generating Typst templates..." +bash ./generate_notices.sh ${LANG} ${MAP_SCHOOL} +STEP2_END=$(date +%s) +STEP2_DURATION=$((STEP2_END - STEP2_START)) +echo "✅ Step 2: Template generation complete in ${STEP2_DURATION} seconds." + +########################################## +# Step 3: Compiling Notices +########################################## +STEP3_START=$(date +%s) + +# Check to see if the conf.typ file is in the json_ directory +if [ -e "${OUTDIR}/json_${LANG}/conf.typ" ]; then + echo "Found conf.typ in ${OUTDIR}/json_${LANG}/" +else + # Move conf.typ to the json_ directory + echo "Moving conf.typ to ${OUTDIR}/json_${LANG}/" + cp ./conf.typ "${OUTDIR}/json_${LANG}/conf.typ" +fi + +echo "" +echo "📄 Step 3: Compiling Typst templates..." +bash ./compile_notices.sh ${LANG} +STEP3_END=$(date +%s) +STEP3_DURATION=$((STEP3_END - STEP3_START)) +echo "✅ Step 3: Compilation complete in ${STEP3_DURATION} seconds." + +########################################## +# Step 4: Checking length of compiled files against expected length +########################################## + +echo "" +echo "📏 Step 4: Checking length of compiled files..." + +# Remove conf.pdf if it exists +if [ -e "${OUTDIR}/json_${LANG}/conf.pdf" ]; then + echo "Removing existing conf.pdf..." + rm "${OUTDIR}/json_${LANG}/conf.pdf" +fi + +for file in "${OUTDIR}/json_${LANG}/"*.pdf; do + python count_pdfs.py ${file} +done + +########################################## +# Step 5: Cleanup +########################################## + +echo "" +if [ "$SKIP_CLEANUP" = true ]; then + echo "🧹 Step 5: Cleanup skipped (--no-cleanup flag)." +else + echo "🧹 Step 5: Cleanup started..." + python cleanup.py ${OUTDIR} ${LANG} +fi + +########################################## +# Summary +########################################## +TOTAL_END=$(date +%s) +TOTAL_DURATION=$((TOTAL_END - TOTAL_START)) + +echo "" +echo "🎉 Pipeline completed successfully!" +echo "🕒 Time Summary:" +echo " - Preprocessing: ${STEP1_DURATION}s" +echo " - Template Generation: ${STEP2_DURATION}s" +echo " - Template Compilation: ${STEP3_DURATION}s" +echo " - -----------------------------" +echo " - Total Time: ${TOTAL_DURATION}s" +echo "" +echo "📦 Batch size: ${BATCH_SIZE}" +echo "📊 Total records: ${TOTAL_RECORDS}" +if [ "$SKIP_CLEANUP" = true ]; then + echo "🧹 Cleanup: Skipped" +fi \ No newline at end of file diff --git a/templates/en_template.py b/templates/en_template.py index 71fd88a..5e119d5 100644 --- a/templates/en_template.py +++ b/templates/en_template.py @@ -45,7 +45,7 @@ ) // Immunization Notice Section -#let immunization_notice(client, client_id, immunizations_due, date, font_size) = block[ +#let immunization_notice(client, client_id, immunizations_due, date, font_size, phu_address, phu_phone, phu_email, phu_website) = block[ #v(0.2cm) @@ -64,10 +64,10 @@ Please review the Immunization Record on page 2 and update your child's record by using one of the following options: -1. By visiting #text(fill:conf.linkcolor)[#link("https://www.test-immunization.ca")] -2. By emailing #text(fill:conf.linkcolor)[#link("records@test-immunization.ca")] -3. By mailing a photocopy of your child's immunization record to Test Health, 123 Placeholder Street, Sample City, ON A1A 1A1 -4. By Phone: 555-555-5555 ext. 1234 +1. By visiting #text(fill:conf.linkcolor)[#link(phu_website)] +2. By emailing #text(fill:conf.linkcolor)[#link(phu_email)] +3. By mailing a photocopy of your child's immunization record to #phu_address +4. By Phone: #phu_phone Please update Public Health and your childcare centre every time your child receives a vaccine. @@ -129,13 +129,17 @@ #let num_rows = __NUM_ROWS__ #let diseases = __CHART_DISEASES_TRANSLATED__ #let date = data.date_data_cutoff +#let phu_address = "__PHU_ADDRESS__" +#let phu_phone = "__PHU_PHONE__" +#let phu_email = "__PHU_EMAIL__" +#let phu_website = "__PHU_WEBSITE__" #set page( margin: (top: 1cm, bottom: 2cm, left: 1.75cm, right: 2cm), footer: align(center, context numbering("1 / " + str(counter(page).final().first()), counter(page).get().first())) ) -#immunization_notice(data, client_row, vaccines_due_array, date, 11pt) +#immunization_notice(data, client_row, vaccines_due_array, date, 11pt, phu_address, phu_phone, phu_email, phu_website) #pagebreak() #vaccine_table_page(client_row.at(0)) #conf.immunization-table(5, num_rows, received, diseases, 11pt) @@ -186,6 +190,7 @@ def render_notice( "received", "num_rows", "chart_diseases_translated", + "phu_data", ) missing = [key for key in required_keys if key not in context] if missing: @@ -204,5 +209,9 @@ def render_notice( .replace("__RECEIVED__", context["received"]) .replace("__NUM_ROWS__", context["num_rows"]) .replace("__CHART_DISEASES_TRANSLATED__", context["chart_diseases_translated"]) + .replace("__PHU_ADDRESS__", context["phu_data"]["phu_address"]) + .replace("__PHU_EMAIL__", context["phu_data"]["phu_email"]) + .replace("__PHU_PHONE__", context["phu_data"]["phu_phone"]) + .replace("__PHU_WEBSITE__", context["phu_data"]["phu_website"]) ) return prefix + dynamic diff --git a/templates/fr_template.py b/templates/fr_template.py index 5c3dcbd..8c2790b 100644 --- a/templates/fr_template.py +++ b/templates/fr_template.py @@ -46,7 +46,7 @@ ) // Immunization Notice Section -#let immunization_notice(client, client_id, immunizations_due, date, font_size) = block[ +#let immunization_notice(client, client_id, immunizations_due, date, font_size, phu_address, phu_phone, phu_email, phu_website) = block[ #v(0.2cm) @@ -65,10 +65,10 @@ Veuillez examiner le dossier d'immunisation à la page 2 et mettre à jour le dossier de votre enfant en utilisant l'une des options suivantes : -1. En visitant #text(fill:conf.linkcolor)[#link("https://www.test-immunization.ca")] -2. En envoyant un courriel à #text(fill:conf.linkcolor)[#link("records@test-immunization.ca")] -3. En envoyant par la poste une photocopie du dossier d'immunisation de votre enfant à Test Health, 123 Placeholder Street, Sample City, ON A1A 1A1 -4. Par téléphone : 555-555-5555 poste 1234 +1. En visitant #text(fill:conf.linkcolor)[#link(phu_website)] +2. En envoyant un courriel à #text(fill:conf.linkcolor)[#link(phu_email)] +3. En envoyant par la poste une photocopie du dossier d'immunisation de votre enfant à #phu_address +4. Par téléphone : #phu_phone Veuillez informer la Santé publique et votre centre de garde d'enfants chaque fois que votre enfant reçoit un vaccin. En gardant les vaccinations de votre enfant à jour, vous protégez non seulement sa santé, mais aussi la santé des autres enfants et du personnel du centre de garde d'enfants. @@ -130,13 +130,17 @@ #let num_rows = __NUM_ROWS__ #let diseases = __CHART_DISEASES_TRANSLATED__ #let date = data.date_data_cutoff +#let phu_address = "__PHU_ADDRESS__" +#let phu_phone = "__PHU_PHONE__" +#let phu_email = "__PHU_EMAIL__" +#let phu_website = "__PHU_WEBSITE__" #set page( margin: (top: 1cm, bottom: 2cm, left: 1.75cm, right: 2cm), footer: align(center, context numbering("1 / " + str(counter(page).final().first()), counter(page).get().first())) ) -#immunization_notice(data, client_row, vaccines_due_array, date, 11pt) +#immunization_notice(data, client_row, vaccines_due_array, date, 11pt, phu_address, phu_phone, phu_email, phu_website) #pagebreak() #vaccine_table_page(client_row.at(0)) #conf.immunization-table(5, num_rows, received, diseases, 11pt) @@ -187,6 +191,7 @@ def render_notice( "received", "num_rows", "chart_diseases_translated", + "phu_data", ) missing = [key for key in required_keys if key not in context] if missing: @@ -205,5 +210,9 @@ def render_notice( .replace("__RECEIVED__", context["received"]) .replace("__NUM_ROWS__", context["num_rows"]) .replace("__CHART_DISEASES_TRANSLATED__", context["chart_diseases_translated"]) + .replace("__PHU_ADDRESS__", context["phu_data"]["phu_address"]) + .replace("__PHU_EMAIL__", context["phu_data"]["phu_email"]) + .replace("__PHU_PHONE__", context["phu_data"]["phu_phone"]) + .replace("__PHU_WEBSITE__", context["phu_data"]["phu_website"]) ) return prefix + dynamic diff --git a/tests/unit/test_en_template.py b/tests/unit/test_en_template.py index 1c737d3..bc685a9 100644 --- a/tests/unit/test_en_template.py +++ b/tests/unit/test_en_template.py @@ -45,6 +45,12 @@ def test_render_notice_with_valid_context(self) -> None: "received": '(("MMR", "2020-05-15"), ("DPT", "2019-03-15"))', "num_rows": "2", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -73,6 +79,12 @@ def test_render_notice_missing_client_row_raises_error(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } with pytest.raises(KeyError, match="Missing context keys"): @@ -116,6 +128,12 @@ def test_render_notice_substitutes_logo_path(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } logo_path = "/custom/logo/path.png" @@ -142,6 +160,12 @@ def test_render_notice_substitutes_signature_path(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } signature_path = "/custom/signature.png" @@ -168,6 +192,12 @@ def test_render_notice_includes_template_prefix(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -194,6 +224,12 @@ def test_render_notice_includes_dynamic_block(self) -> None: "received": "()", "num_rows": "1", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -222,6 +258,12 @@ def test_render_notice_with_complex_client_data(self) -> None: "received": '(("Measles", "2020-05-01"), ("Mumps", "2020-05-01"))', "num_rows": "5", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -250,6 +292,12 @@ def test_render_notice_empty_vaccines_handled(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtheria", "Tetanus", "Pertussis")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( diff --git a/tests/unit/test_fr_template.py b/tests/unit/test_fr_template.py index 64aa7c0..5234f0d 100644 --- a/tests/unit/test_fr_template.py +++ b/tests/unit/test_fr_template.py @@ -63,6 +63,12 @@ def test_render_notice_with_valid_context(self) -> None: "received": '(("RRO", "2020-05-15"), ("DPT", "2019-03-15"))', "num_rows": "2", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -91,6 +97,12 @@ def test_render_notice_missing_client_row_raises_error(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } with pytest.raises(KeyError, match="Missing context keys"): @@ -134,6 +146,12 @@ def test_render_notice_substitutes_logo_path(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } logo_path = "/custom/logo/path.png" @@ -160,6 +178,12 @@ def test_render_notice_substitutes_signature_path(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } signature_path = "/custom/signature.png" @@ -186,6 +210,12 @@ def test_render_notice_includes_template_prefix(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -212,6 +242,12 @@ def test_render_notice_includes_dynamic_block(self) -> None: "received": "()", "num_rows": "1", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -240,6 +276,12 @@ def test_render_notice_with_complex_client_data(self) -> None: "received": '(("Rougeole", "2020-05-01"), ("Oreillons", "2020-05-01"))', "num_rows": "5", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -268,6 +310,12 @@ def test_render_notice_empty_vaccines_handled(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( @@ -295,6 +343,12 @@ def test_render_notice_french_content(self) -> None: "received": "()", "num_rows": "0", "chart_diseases_translated": '("Diphtérie", "Tétanos", "Coqueluche")', + "phu_data": { + "phu_address": "Tunnel Health, 123 Placeholder Street, Sample City, ON A1A 1A1", + "phu_email": "mainpublichealth#rodenthealth.ca", + "phu_phone": "555-555-5555 ext. 1234", + "phu_website": "https://www.test-immunization.ca", + }, } result = render_notice( diff --git a/tests/unit/test_generate_notices.py b/tests/unit/test_generate_notices.py index c3a2a3f..98e248d 100644 --- a/tests/unit/test_generate_notices.py +++ b/tests/unit/test_generate_notices.py @@ -293,6 +293,7 @@ def test_build_template_context_from_client(self) -> None: assert "vaccines_due_array" in context assert "received" in context assert "num_rows" in context + assert "phu_data" in context def test_build_template_context_includes_client_id(self) -> None: """Verify client_id is in context. @@ -372,6 +373,69 @@ def test_build_template_context_includes_formatted_date(self) -> None: or "date_data_cutoff" in client_data_str ) + def test_build_template_context_map_file(self, tmp_test_dir: Path) -> None: + """Verify handling of map file. + + Real-world significance: + - Map file must be present with expected values + - Must avoid missing or mixing up template values + """ + # Test + map_path = tmp_test_dir / "map.json" + map_path.write_text("not valid json {{{") + + client = sample_input.create_test_client_record( + school_name="Test School", + ) + + # Map file does not exist + with pytest.raises(Exception): + generate_notices.build_template_context( + client, map_file=tmp_test_dir / "fake_map.json" + ) + + # Map file exists, but can't read json + with pytest.raises(Exception): # json.JSONDecodeError or similar + generate_notices.build_template_context(client, map_file=map_path) + + # Missing "DEFAULT" in .json file + test_json = {"SCHOOL_1": {}} + json_string = json.dumps(test_json) + map_path.write_text(json_string) + with pytest.raises(Exception): + generate_notices.build_template_context(client, map_file=map_path) + + # Missing required keys in "DEFAULT" + required_keys = {"phu_phone", "phu_email"} + test_json = { + "DEFAULT": { + "phu_phone": "555-555-5555 ext. 1234", + } + } + json_string = json.dumps(test_json) + map_path.write_text(json_string) + with pytest.raises(Exception): + generate_notices.build_template_context( + client, map_file=map_path, required_keys=required_keys + ) + + # Check that finds school name and substitutes provided value and uses default values for keys not provided for school name + test_json = { + "DEFAULT": { + "phu_phone": "555-555-5555 ext. 1234", + "phu_email": "mainpublichealth@rodenthealth.ca", + }, + "TEST_SCHOOL": {"phu_phone": "555-555-5555 ext. 4321"}, + } + json_string = json.dumps(test_json) + map_path.write_text(json_string) + context = generate_notices.build_template_context( + client, map_file=map_path, required_keys=required_keys + ) + assert context["phu_data"] + assert context["phu_data"]["phu_email"] == "mainpublichealth@rodenthealth.ca" + assert context["phu_data"]["phu_phone"] == "555-555-5555 ext. 4321" + @pytest.mark.unit class TestLanguageSupport: diff --git a/tests/unit/test_run_pipeline.py b/tests/unit/test_run_pipeline.py index 9e4ab6b..75b6044 100644 --- a/tests/unit/test_run_pipeline.py +++ b/tests/unit/test_run_pipeline.py @@ -311,6 +311,7 @@ def test_pipeline_loads_parameters_yaml(self, config_file: Path) -> None: mock_load.return_value = { "pipeline": {"auto_remove_output": False}, "qr": {"enabled": True}, + "map_file": "map_school.json", } from pipeline.config_loader import load_config