Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ uv run pytest -m "not e2e"
- Use data extracts from [Panorama PEAR](https://accessonehealth.ca/)
- Place input files in the `input/` subfolder (not tracked by Git)
- Files must be `.xlsx` format with a **single worksheet** per file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a reason that we want .xlsx over .csv? .xlsx can do some weird things with dates/formatting we may like to avoid dealing with at all, and incurs some xlsx handling dependencies. Perhaps this is a PEAR thing though - if more rows can be exported at a time in xlsx format vs csv?

I believe with PEAR you can export the report template definitions/schema which we could include in this repo once revised (in addition for PHU users to access the template from our collaboration folder in PEAR)

- Column names must match the required schema exactly - see [Getting Started](docs/user_guide/getting_started.md#preparing-input-data) for the full column list

## Preprocessing

Expand Down
73 changes: 73 additions & 0 deletions config/input_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"$schema": "https://specs.frictionlessdata.io/schemas/table-schema.json",
"fields": [
{
"name": "School Type",
"type": "string"
},
{
"name": "School Name",
"type": "string"
},
{
"name": "Client Id",
"type": "string",
"constraints": {
"pattern": "^\\d{10}$"
}
},
{
"name": "First Name",
"type": "string"
},
{
"name": "Last Name",
"type": "string"
},
{
"name": "Age",
"type": "integer"
},
{
"name": "Date of Birth",
"type": "date"
},
{
"name": "Street Address Line 1",
"type": "string"
},
{
"name": "Street Address Line 2",
"type": "string"
},
{
"name": "City",
"type": "string"
},
{
"name": "Province/Territory",
"type": "string"
},
{
"name": "Postal Code",
"type": "string"
},
{
"name": "Overdue Disease",
"type": "string"
},
{
"name": "Overdue Agent",
"type": "string"
},
{
"name": "Imms Given",
"type": "string"
},
{
"name": "Birth Year",
"type": "string"
}
],
"missingValues": [""]
}
37 changes: 37 additions & 0 deletions config/input_schema.yaml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we snake_case-ify like standards in other pipelines?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Went with yaml because the internet suggested it was a little more human readable - so though it might be nicer for other PHUs looking to user our code; but can switch it to json to match our existing pipeline schemas!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, I thought frictionless tabular schema was officially only a JSON spec https://specs.frictionlessdata.io/table-schema/#language

I agree it's quite readable

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeahh you're right though, I'll change it - readability argument is weak since non-developers probably wouldn't be editing a schema file anyway, and using proper frictionless descriptor makes other tooling we decide to implement straightforward (thanks for the review btw!!)

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
fields:
- name: School Type
type: string
- name: School Name
type: string
- name: Client Id
type: string
constraints:
pattern: '^\d{10}$'
- name: First Name
type: string
- name: Last Name
type: string
- name: Age
type: integer
- name: Date of Birth
type: date
- name: Street Address Line 1
type: string
- name: Street Address Line 2
type: string
- name: City
type: string
- name: Province/Territory
type: string
- name: Postal Code
type: string
- name: Overdue Disease
type: string
- name: Overdue Agent
type: string
- name: Imms Given
type: string
- name: Birth Year
type: string
missingValues:
- ''
32 changes: 32 additions & 0 deletions docs/user_guide/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,38 @@ uv run pre-commit install

Input files must be `.xlsx` format with a single worksheet, extracted from [Panorama PEAR](https://accessonehealth.ca/).

The pipeline enforces a strict column schema — column names must match exactly (no fuzzy matching). The following columns are **required**:

| Column name | Notes |
|---|---|
| `School Type` | |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see in test fixtures this included "catholic". I would've expected this was secondary/elementary. Is it also possible however, that type is maintained in our look up table from #194 or perhaps isn't needed at all - and simply any dynamics around separate notices for elementary vs secondary is dealt with via the optional, more generic version code approach (see other comment on optional fields)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In current code, school type column is never actually used - templates pass hard-coded values to conf.typ for info box; in the interest of min required cols, this could be easily handled as template/version specific

| `School Name` | |
| `Client Id` | 10-digit numeric string |
| `First Name` | |
| `Last Name` | |
| `Age` | Integer |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should work to also refine and minimize this set... Age and Birth Year are best as derived in pipeline as needed rather than relied upon from input data.

| `Date of Birth` | ISO 8601 date (`YYYY-MM-DD`) |
| `Street Address Line 1` | |
| `Street Address Line 2` | May be blank |
| `City` | |
| `Province/Territory` | |
| `Postal Code` | |
| `Overdue Disease` | May be blank |
| `Overdue Agent` | May be blank |
| `Imms Given` | May be blank |
| `Birth Year` | |

The following columns are **optional** and will be used when present:

| Column name |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Support optional "version" identifier (e.g. for multi-language support, A/B tests, etc)

|---|
| `Board Name` |
| `Board Id` |
| `School Id` |
| `Unique Id` |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is different from client ID?

@TiaTuinstra TiaTuinstra Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In test sets contains 'SCHOOL NAME - PHIX ID' - perhaps a relic of earlier report formats. This is the structure we currently expect from SCHOOL_NAME column in new reports; will remove 'Unique Id' from expected list and test set to reflect new standards


The full schema is defined in `config/input_schema.yaml`. If the file is missing any required column, the pipeline will stop immediately with a clear error message listing the missing columns.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Another idea on this... I am imagining that it's going to come up a lot where we want to list a schema in a user friendly way in documentation, that is also coded in the frictionless JSON and that this will create an opportunity for drift between documentation and implementation. Should we consider (perhaps separate of this PR, or even this project), a tool that will enable us to have an approachable display method in mkdocs for our various schemas?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Current approach: uv run python docs/generate_schema_docs.py run before mkdocs build in docs.yml. Could add a more standardized generate_schema_docs.py file to our template repo to keep consistent across our projects


Place input files in the `input/` subdirectory (not tracked by Git):

```
Expand Down
Binary file modified input/rodent_dataset.xlsx
Binary file not shown.
7 changes: 6 additions & 1 deletion pipeline/generate_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ def read_artifact(path: Path) -> ArtifactPayload:
)


_TYP_IDENT_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_-]*$")


def escape_string(value: str) -> str:
"""Escape special characters in a string for Typst template output.

Expand Down Expand Up @@ -359,7 +362,9 @@ def to_typ_value(value) -> str:
inner = ", ".join(items)
return f"({inner})"
if isinstance(value, Mapping):
items = ", ".join(f"{key}: {to_typ_value(val)}" for key, val in value.items())
def _typ_key(k: str) -> str:
return k if _TYP_IDENT_RE.match(k) else f'"{escape_string(k)}"'
items = ", ".join(f"{_typ_key(key)}: {to_typ_value(val)}" for key, val in value.items())
return f"({items})"
raise TypeError(f"Unsupported value type for Typst conversion: {type(value)!r}")

Expand Down
5 changes: 2 additions & 3 deletions pipeline/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,8 @@ def run_step_2_preprocess(
# Load and process input data
input_path = input_dir / input_file
df_raw = preprocess.read_input(input_path)
mapped_df, column_mapping = preprocess.map_columns(df_raw)
df_filtered = preprocess.filter_columns(mapped_df)
df = preprocess.normalize_dataframe(df_filtered)
preprocess.validate_input(input_path)
df = preprocess.normalize_dataframe(preprocess.map_columns(df_raw))

# Check that addresses are complete, return only complete rows
df = preprocess.check_addresses_complete(df)
Expand Down
Loading