Skip to content

Commit

Permalink
# This is a combination of 3 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

Remove system card mocks

# Conflicts:
#	amt/api/routes/project.py

# This is the commit message #2:

Remove system card mocks

# This is the commit message #3:

Remove system card mocks
  • Loading branch information
uittenbroekrobbert committed Oct 29, 2024
1 parent 994c9ab commit e4a8e51
Show file tree
Hide file tree
Showing 16 changed files with 1,719 additions and 76 deletions.
19 changes: 2 additions & 17 deletions amt/api/routes/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,6 @@ async def get_project_update(
return templates.TemplateResponse(request, "parts/view_cell.html.j2", context)


# !!!
# Implementation of this endpoint is for now independent of the project ID, meaning
# that the same system card is rendered for all project ID's. This is due to the fact
# that the logical process flow of a system card is not complete.
# !!!
@router.get("/{project_id}/details/system_card")
async def get_system_card(
request: Request,
Expand Down Expand Up @@ -350,11 +345,6 @@ async def get_project_inference(
return templates.TemplateResponse(request, "projects/details_inference.html.j2", context)


# !!!
# Implementation of this endpoint is for now independent of the project ID, meaning
# that the same system card is rendered for all project ID's. This is due to the fact
# that the logical process flow of a system card is not complete.
# !!!
@router.get("/{project_id}/details/system_card/requirements")
async def get_system_card_requirements(
request: Request,
Expand All @@ -364,7 +354,6 @@ async def get_system_card_requirements(
project = await get_project_or_error(project_id, projects_service, request)
instrument_state = get_instrument_state(project.system_card)
requirements_state = get_requirements_state(project.system_card)
# TODO: This tab is fairly slow, fix in later releases
tab_items = get_project_details_tabs(request)

breadcrumbs = resolve_base_navigation_items(
Expand All @@ -378,11 +367,7 @@ async def get_system_card_requirements(
request,
)

# TODO: This is only for the demo of 18 Oct. In reality one would load the requirements from the requirement
# field in the system card, but one would load the AI Act Profile and determine the requirements from
# the labels in this field.
system_card = project.system_card
requirements = fetch_requirements([requirement.urn for requirement in system_card.requirements])
requirements = fetch_requirements([requirement.urn for requirement in project.system_card.requirements])

# Get measures that correspond to the requirements and merge them with the measuretasks
requirements_and_measures = []
Expand All @@ -391,7 +376,7 @@ async def get_system_card_requirements(
linked_measures = fetch_measures(requirement.links)
extended_linked_measures: list[ExtendedMeasureTask] = []
for measure in linked_measures:
measure_task = find_measure_task(system_card, measure.urn)
measure_task = find_measure_task(project.system_card, measure.urn)
if measure_task:
ext_measure_task = ExtendedMeasureTask(
name=measure.name,
Expand Down
14 changes: 4 additions & 10 deletions amt/api/routes/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from amt.schema.localized_value_item import LocalizedValueItem
from amt.schema.project import ProjectNew
from amt.services.instruments import InstrumentsService
from amt.services.projects import ProjectsService
from amt.services.projects import ProjectsService, get_template_files

router = APIRouter()
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -104,12 +104,15 @@ async def get_new(

ai_act_profile = get_ai_act_profile_selector(request)

template_files = get_template_files()

context: dict[str, Any] = {
"instruments": instrument_service.fetch_instruments(),
"ai_act_profile": ai_act_profile,
"breadcrumbs": breadcrumbs,
"sub_menu_items": {}, # sub_menu_items disabled for now,
"lifecycles": get_localized_lifecycles(request),
"template_files": template_files
}

response = templates.TemplateResponse(request, "projects/new.html.j2", context)
Expand All @@ -122,15 +125,6 @@ async def post_new(
project_new: ProjectNew,
projects_service: Annotated[ProjectsService, Depends(ProjectsService)],
) -> HTMLResponse:
# TODO: FOR DEMO 18 OCT
# Override AI Act Profile for demo purposes to values:
project_new.type = "AI-systeem"
project_new.publication_category = "hoog-risico AI"
project_new.transparency_obligations = "geen transparantieverplichtingen"
project_new.role = "gebruiksverantwoordelijke"
project_new.systemic_risk = "geen systeemrisico"
project_new.open_source = "open-source"

project = await projects_service.create(project_new)
response = templates.Redirect(request, f"/algorithm-system/{project.id}/details/tasks")
return response
4 changes: 2 additions & 2 deletions amt/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ def system_card(self, value: SystemCard | None) -> None:
if value is None:
self._system_card = ProjectSystemCard(self)
else:
self._system_card = ProjectSystemCard(self, **value.model_dump(exclude_unset=True))
self._system_card = ProjectSystemCard(self, **value.model_dump(exclude_unset=True, by_alias=True))
self.sync_system_card()

def sync_system_card(self) -> None:
if self._system_card is not None:
self.system_card_json = self._system_card.model_dump(exclude_unset=True)
self.system_card_json = self._system_card.model_dump(exclude_unset=True, by_alias=True)


Project.__mapper_args__ = {"exclude_properties": ["_system_card"]}
13 changes: 5 additions & 8 deletions amt/schema/assessment_card.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from datetime import datetime

from pydantic import (
BaseModel,
Field, # pyright: ignore [reportUnknownMemberType]
)
from pydantic import Field # pyright: ignore [reportUnknownMemberType]

from amt.schema.shared import IterMixin
from amt.schema.shared import BaseModel


class AssessmentAuthor(BaseModel, IterMixin):
class AssessmentAuthor(BaseModel):
name: str = Field(default=None)


class AssessmentContent(BaseModel, IterMixin):
class AssessmentContent(BaseModel):
question: str = Field(default=None)
urn: str = Field(default=None)
answer: str = Field(default=None)
Expand All @@ -21,7 +18,7 @@ class AssessmentContent(BaseModel, IterMixin):
timestamp: datetime | None = Field(default=None)


class AssessmentCard(BaseModel, IterMixin):
class AssessmentCard(BaseModel):
name: str = Field(default=None)
urn: str = Field(default=None)
date: datetime = Field(default=None)
Expand Down
8 changes: 4 additions & 4 deletions amt/schema/instrument.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import Field

from amt.schema.shared import IterMixin
from amt.schema.shared import BaseModel


class Owner(BaseModel, IterMixin):
class Owner(BaseModel):
organization: str
name: str
email: str
Expand All @@ -17,7 +17,7 @@ class InstrumentTask(BaseModel):
lifecycle: list[str]


class InstrumentBase(BaseModel, IterMixin):
class InstrumentBase(BaseModel):
urn: str


Expand Down
6 changes: 3 additions & 3 deletions amt/schema/measure.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import Field

from amt.schema.shared import IterMixin
from amt.schema.shared import BaseModel


class MeasureBase(BaseModel, IterMixin):
class MeasureBase(BaseModel):
urn: str


Expand Down
4 changes: 3 additions & 1 deletion amt/schema/model_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

from datetime import datetime

from pydantic import BaseModel, Field
from pydantic import Field

from amt.schema.shared import BaseModel


class Provenance(BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions amt/schema/project.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

from pydantic import BaseModel, Field
from pydantic.functional_validators import field_validator

Expand All @@ -15,6 +16,7 @@ class ProjectNew(ProjectBase):
systemic_risk: str = Field(default=None)
transparency_obligations: str = Field(default=None)
role: list[str] | str = []
template_id: str = Field(default="")

@field_validator("instruments", "role")
def ensure_list(cls, v: list[str] | str) -> list[str]:
Expand Down
6 changes: 3 additions & 3 deletions amt/schema/requirement.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import Field

from amt.schema.shared import IterMixin
from amt.schema.shared import BaseModel


class RequirementBase(BaseModel, IterMixin):
class RequirementBase(BaseModel):
urn: str


Expand Down
6 changes: 6 additions & 0 deletions amt/schema/shared.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from pydantic import BaseModel as PydanticBaseModel


class IterMixin:
def __iter__(self):
for attr, value in self.__dict__.items():
yield attr, value

class BaseModel(PydanticBaseModel, IterMixin):
...
11 changes: 4 additions & 7 deletions amt/schema/system_card.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
from typing import Any

from pydantic import (
BaseModel,
Field, # pyright: ignore [reportUnknownMemberType]
)
from pydantic import Field

from amt.schema.ai_act_profile import AiActProfile
from amt.schema.assessment_card import AssessmentCard
from amt.schema.instrument import InstrumentBase
from amt.schema.measure import MeasureTask
from amt.schema.model_card import ModelCardSchema
from amt.schema.requirement import RequirementTask
from amt.schema.shared import IterMixin
from amt.schema.shared import BaseModel


class Reference(BaseModel, IterMixin):
class Reference(BaseModel):
name: str = Field(default=None)
link: str = Field(default=None)


class SystemCard(BaseModel, IterMixin):
class SystemCard(BaseModel):
schema_version: str = Field(default="0.1a10")
name: str = Field(default=None)
ai_act_profile: AiActProfile = Field(default=None)
Expand Down
33 changes: 33 additions & 0 deletions amt/services/projects.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import json
import logging
from os import listdir
from os.path import isfile, join
from pathlib import Path
from typing import Annotated

from fastapi import Depends

from amt.core.exceptions import AMTNotFound
from amt.models import Project
from amt.repositories.projects import ProjectsRepository
from amt.schema.instrument import InstrumentBase
Expand All @@ -14,6 +19,7 @@

logger = logging.getLogger(__name__)

template_path = "resources/system_card_templates"

class ProjectsService:
def __init__(
Expand All @@ -31,6 +37,17 @@ async def get(self, project_id: int) -> Project:
return project

async def create(self, project_new: ProjectNew) -> Project:

# TODO (Robbert): this is temporary so we can use templates (example projects) from disk
system_card_from_template = None
if project_new.template_id:
template_files = get_template_files()
if project_new.template_id in template_files:
with open(Path(template_path) / Path(template_files[project_new.template_id]["value"])) as f:
system_card_from_template = json.load(f)
else:
raise AMTNotFound()

instruments: list[InstrumentBase] = [
InstrumentBase(urn=instrument_urn) for instrument_urn in project_new.instruments
]
Expand All @@ -54,6 +71,18 @@ async def create(self, project_new: ProjectNew) -> Project:
measures=measures,
)

if system_card_from_template is not None:
system_card_dict = system_card.model_dump()
system_card_merged = {
k: (system_card_dict[k] if k in system_card_dict and system_card_dict.get(k) else system_card_from_template.get(k))
for k in set(system_card_dict) | set(system_card_from_template)
}
system_card_merged["ai_act_profile"] = {
k: (system_card_dict["ai_act_profile"][k] if k in system_card_dict["ai_act_profile"] and system_card_dict["ai_act_profile"].get(k) else system_card_from_template["ai_act_profile"].get(k))
for k in set(system_card_dict["ai_act_profile"]) | set(system_card_from_template["ai_act_profile"])
}
system_card = SystemCard.model_validate(system_card_merged)

project = Project(name=project_new.name, lifecycle=project_new.lifecycle, system_card=system_card)
project = await self.update(project)

Expand All @@ -74,3 +103,7 @@ async def update(self, project: Project) -> Project:
project.sync_system_card()
project = await self.repository.save(project)
return project

def get_template_files() -> dict[str, dict[str, str]]:
return {str(i): {"display_value": k.split(".")[0].replace("_", " "), "value": k} for i, k in enumerate(listdir(template_path)) if
isfile(join(template_path, k))}
20 changes: 4 additions & 16 deletions amt/services/task_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,10 @@ def get_requirements_and_measures(
list[RequirementTask],
list[MeasureTask],
]:
# TODO: This now loads measures & requirements from the example system card independent of the project ID.
# TODO: Refactor to call the task registry api here (based on the ai_act_profile) NOT the system card which is very
# artificial and has extra information like status and value.
# TODO: Refactor and merge both the InstrumentService and this TaskRegsitryService to make use of the TaskRegistry
measure_path = Path("example_system_card/measures/measures.yaml")
requirements_path = Path("example_system_card/requirements/requirements.yaml")
measures: Any = StorageFactory.init(
storage_type="file", location=measure_path.parent, filename=measure_path.name
).read()
requirements: Any = StorageFactory.init(
storage_type="file", location=requirements_path.parent, filename=requirements_path.name
).read()
measure_card = [MeasureTask(**measure) for measure in measures]
requirements_card = [RequirementTask(**requirement) for requirement in requirements]

# TODO: After calling the Task Registry API this should return a MeasureBase and RequirementBase
# TODO (Robbert): the body of this method will be added later (another ticket)
measure_card = []
requirements_card = []

return requirements_card, measure_card


Expand Down
6 changes: 3 additions & 3 deletions amt/site/templates/pages/system_card.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<span class="rvo-text">{% trans %}Last updated{% endtrans %}: {{ last_edited | time_ago(language) }}
{% trans %}ago{% endtrans %}</span>
<div class="rvo-layout-row rvo-layout-align-items-start rvo-layout-gap--xl">
{% if system_card.assessments is defined %}
{% if system_card.assessments is defined and system_card.assessments|length > 0 %}
<div>
<h4 class="utrecht-heading-4">{% trans %}Assessment cards {% endtrans %}</h4>
<ul class="rvo-ul rvo-ul--no-margin rvo-ul--none rvo-ul--no-padding">
Expand All @@ -22,9 +22,9 @@
</ul>
</div>
{% endif %}
{% if system_card.models is defined %}
{% if system_card.models is defined and system_card.models|length > 0 %}
<div>
<h4 class="utrecht-heading-4">Model cards</h4>
<h4 class="utrecht-heading-4">{% trans %}Model cards{% endtrans %}</h4>
<ul class="rvo-ul rvo-ul--no-margin rvo-ul--none rvo-ul--no-padding">
{% for model in system_card.models %}
<li>
Expand Down
Loading

0 comments on commit e4a8e51

Please sign in to comment.