-
Notifications
You must be signed in to change notification settings - Fork 0
review branch #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
01e017e
review branch
EstebanMontandon f8e1ef4
Merge branch 'main' into review_d2d
EstebanMontandon ff86709
fix(): pyproject to root + readme + ruff format
EstebanMontandon 331609c
fix(): ruff crap again
EstebanMontandon 657fa53
fix(test): utils tests + move ruff rules to d2d_dev only
EstebanMontandon 55b88cc
fix(): ruff imports
EstebanMontandon 630aae4
feature(DHIS2Push): Summary delete & ignore + test + docs
EstebanMontandon d0e3b91
chores(DHIS2Extract): docs + Update readme
EstebanMontandon 460d978
feat(push): rejected datapoints + test
EstebanMontandon 3b5160f
fix(d2d): Yann review fixes
EstebanMontandon 4796dc1
fix(extract): future annotations
EstebanMontandon aa8279a
chore(docs): clarify beta status of dataset_completion and org_unit_a…
EstebanMontandon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| import json | ||
| from dataclasses import dataclass | ||
|
EstebanMontandon marked this conversation as resolved.
|
||
| from enum import Enum | ||
| from typing import NamedTuple | ||
|
|
||
| import pandas as pd | ||
|
|
||
|
|
||
| class DataType(Enum): | ||
| """Enumeration of supported DHIS2 data types for extraction.""" | ||
|
|
||
| DATA_ELEMENT = "DATA_ELEMENT" | ||
| REPORTING_RATE = "REPORTING_RATE" | ||
| INDICATOR = "INDICATOR" | ||
|
EstebanMontandon marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| @dataclass | ||
| class DataPointModel: | ||
| """Data model representing a DHIS2 data point. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| dataElement : str | ||
| The unique identifier for the data element. | ||
| period : str | ||
| The reporting period for the data point. | ||
| orgUnit : str | ||
| The organizational unit associated with the data point. | ||
| categoryOptionCombo : str | ||
| The category option combination identifier. | ||
| attributeOptionCombo : str | ||
| The attribute option combination identifier. | ||
| value : float | ||
| The value of the data point. | ||
| """ | ||
|
|
||
| dataElement: str # noqa: N815 | ||
| period: str | ||
| orgUnit: str # noqa: N815 | ||
| categoryOptionCombo: str # noqa: N815 | ||
| attributeOptionCombo: str # noqa: N815 | ||
| value: str | ||
|
|
||
| def to_json(self) -> dict: | ||
| """Return a dictionary representation of the data point suitable for DHIS2 JSON format. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict | ||
| A dictionary with keys corresponding to DHIS2 data value fields. | ||
| """ | ||
| if self.value is None or (isinstance(self.value, str) and not self.value.strip()): | ||
| return { | ||
| "dataElement": self.dataElement, | ||
| "period": self.period, | ||
| "orgUnit": self.orgUnit, | ||
| "categoryOptionCombo": self.categoryOptionCombo, | ||
| "attributeOptionCombo": self.attributeOptionCombo, | ||
| "value": "", | ||
| "comment": "deleted value", | ||
| } | ||
|
|
||
| return { | ||
| "dataElement": self.dataElement, | ||
| "period": self.period, | ||
| "orgUnit": self.orgUnit, | ||
| "categoryOptionCombo": self.categoryOptionCombo, | ||
| "attributeOptionCombo": self.attributeOptionCombo, | ||
| "value": self.value, | ||
| } | ||
|
|
||
| def __str__(self) -> str: | ||
| return ( | ||
| f"DataPointModel(" | ||
| f"dataElement={self.dataElement}, " | ||
| f"period={self.period}, " | ||
| f"orgUnit={self.orgUnit}, " | ||
| f"categoryOptionCombo={self.categoryOptionCombo}, " | ||
| f"attributeOptionCombo={self.attributeOptionCombo}, " | ||
| f"value={self.value})" | ||
| ) | ||
|
EstebanMontandon marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| @dataclass | ||
| class OrgUnitModel: | ||
| """Helper object definition to represent an organizational unit.""" | ||
|
|
||
| id: str | ||
| name: str | ||
| shortName: str # noqa: N815 | ||
| openingDate: str # noqa: N815 | ||
| closedDate: str # noqa: N815 | ||
| parent: dict | ||
| level: int | ||
| path: str | ||
| geometry: str | ||
|
|
||
|
|
||
| class OrgUnitRow(NamedTuple): | ||
| """Helper object definition to represent an organizational unit.""" | ||
|
|
||
| id: str | ||
| name: str | ||
| shortName: str # noqa: N815 | ||
| openingDate: str # noqa: N815 | ||
| closedDate: str | None # noqa: N815 | ||
| parent: dict | None | ||
| level: int | ||
| path: str | ||
| geometry: str | dict | None | ||
|
|
||
|
|
||
| class OrgUnitObj: # noqa: PLW1641 (no hashing) | ||
| """Helper class definition to store/create the correct OrgUnit JSON format.""" | ||
|
|
||
| def __init__(self, org_unit: OrgUnitRow | pd.Series | tuple): | ||
| """Create a new org unit instance. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| org_unit : OrgUnitRow | pd.Series | ||
| The organizational unit data. | ||
| Expects columns with names : | ||
| ['id', 'name', 'shortName', 'openingDate', 'closedDate', 'parent','level', 'path', 'geometry'] | ||
| """ | ||
| if isinstance(org_unit, pd.Series): | ||
| # Convert Series to OrgUnitRow | ||
| org_unit = OrgUnitRow( | ||
| id=org_unit["id"], | ||
| name=org_unit["name"], | ||
| shortName=org_unit["shortName"], | ||
| openingDate=org_unit["openingDate"], | ||
| closedDate=org_unit["closedDate"], | ||
| parent=org_unit["parent"], | ||
| level=org_unit["level"], | ||
| path=org_unit["path"], | ||
| geometry=org_unit["geometry"], | ||
| ) | ||
| elif isinstance(org_unit, tuple) and hasattr(org_unit, "_fields"): | ||
| org_unit = OrgUnitRow(**org_unit._asdict()) | ||
| elif not isinstance(org_unit, OrgUnitRow): | ||
| raise TypeError(f"Expected OrgUnitRow, pd.Series, or tuple, got {type(org_unit)}") | ||
|
|
||
| self.initialize_from(org_unit_tuple=org_unit) | ||
|
|
||
| def initialize_from(self, org_unit_tuple: OrgUnitRow): | ||
| """Initialize the OrgUnitObj instance from an OrgUnitRow tuple. | ||
|
|
||
| This object should represent a DHIS2 organizational unit with the same attribute naming. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| org_unit_tuple : tuple | ||
| A tuple containing organizational unit attributes. | ||
| """ | ||
| # Keep names consistent | ||
| self.id = org_unit_tuple.id | ||
| self.name = org_unit_tuple.name | ||
| self.shortName = org_unit_tuple.shortName | ||
| self.openingDate = org_unit_tuple.openingDate | ||
| self.closedDate = org_unit_tuple.closedDate | ||
| self.parent = org_unit_tuple.parent | ||
| # Parse geometry safely | ||
| geometry = org_unit_tuple.geometry | ||
| if pd.notna(geometry): | ||
| if isinstance(geometry, str): | ||
| try: | ||
| self.geometry = json.loads(geometry) | ||
| except json.JSONDecodeError: | ||
| self.geometry = None | ||
| else: | ||
| self.geometry = geometry | ||
| else: | ||
| self.geometry = None | ||
|
|
||
| def to_json(self) -> dict: | ||
| """Return a dictionary representation of the organizational unit suitable for DHIS2 API. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict | ||
| Dictionary containing the organizational unit's attributes formatted for DHIS2. | ||
| """ | ||
| json_dict = { | ||
| "id": self.id, | ||
| "name": self.name, | ||
| "shortName": self.shortName, | ||
| "openingDate": self.openingDate, | ||
| } | ||
|
|
||
| if pd.notna(self.closedDate): | ||
| json_dict["closedDate"] = self.closedDate | ||
|
|
||
| if self.parent and self.parent.get("id") and pd.notna(self.parent.get("id")): | ||
| json_dict["parent"] = {"id": self.parent.get("id")} | ||
|
|
||
| if self.geometry and pd.notna(self.geometry): | ||
| json_dict["geometry"] = { | ||
| "type": self.geometry["type"], | ||
| "coordinates": self.geometry["coordinates"], | ||
| } | ||
| return json_dict | ||
|
|
||
| def is_valid(self) -> bool: | ||
| """Check if the OrgUnitObj instance has all required attributes set. | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if all required attributes are not None, False otherwise. | ||
| """ | ||
| return pd.notna(self.id) and pd.notna(self.name) and pd.notna(self.shortName) and pd.notna(self.openingDate) | ||
|
|
||
| def __str__(self) -> str: | ||
| return f"OrgUnitObj({self.id}, {self.name})" | ||
|
|
||
| def __eq__(self, other: object) -> bool: | ||
| if not isinstance(other, OrgUnitObj): | ||
| return NotImplemented | ||
| return ( | ||
| self.id == other.id | ||
| and self.name == other.name | ||
| and self.shortName == other.shortName | ||
| and self.openingDate == other.openingDate | ||
| and self.closedDate == other.closedDate | ||
| and self.parent == other.parent | ||
| and self.geometry == other.geometry | ||
| ) | ||
|
EstebanMontandon marked this conversation as resolved.
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.