From c1470e8b999ab3d684270a2e614d357cd3b27835 Mon Sep 17 00:00:00 2001 From: Tim Repke <repke@mcc-berlin.net> Date: Thu, 8 Feb 2024 20:00:03 +0100 Subject: [PATCH] simplify resolutions --- requirements.txt | 8 ++--- requirements_dev.txt | 8 ++--- server/api/routes/annotations.py | 60 +++++++++++++++----------------- server/api/routes/evaluation.py | 6 ++-- server/api/routes/users.py | 11 +++++- 5 files changed, 50 insertions(+), 43 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2a1d562..122fe03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,10 +2,10 @@ fastapi==0.108.0 hypercorn==0.16.0 toml==0.10.2 email-validator==2.1.0.post1 -python-dotenv==1.0.0 +python-dotenv==1.0.1 passlib[bcrypt]==1.7.4 pymitter==0.5.0 -uvicorn==0.25.0 -python-multipart==0.0.6 +uvicorn==0.27.0.post1 +python-multipart==0.0.7 aiosmtplib==3.0.1 -nacsos_data[scripts,server,utils] @ git+ssh://git@gitlab.pik-potsdam.de/mcc-apsis/nacsos/nacsos-data.git@v0.12.15 +nacsos_data[scripts,server,utils] @ git+ssh://git@gitlab.pik-potsdam.de/mcc-apsis/nacsos/nacsos-data.git@v0.13.0 diff --git a/requirements_dev.txt b/requirements_dev.txt index a0b8d3e..c40c697 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,7 +1,7 @@ -flake8==6.1.0 -tox==4.11.4 -pytest==7.4.3 +flake8==7.0.0 +tox==4.12.1 +pytest==8.0.0 pytest-cov==4.1.0 -mypy==1.7.1 +mypy==1.8.0 types-toml==0.10.8.7 types-PyYAML==6.0.12.12 \ No newline at end of file diff --git a/server/api/routes/annotations.py b/server/api/routes/annotations.py index f438593..40a7216 100644 --- a/server/api/routes/annotations.py +++ b/server/api/routes/annotations.py @@ -61,7 +61,6 @@ from nacsos_data.db.crud.annotations import ( read_resolved_bot_annotation_meta, read_resolved_bot_annotations_for_meta ) -from nacsos_data.util.annotations import AnnotationFilterObject from nacsos_data.util.annotations.resolve import ( get_resolved_item_annotations, read_annotation_scheme @@ -73,7 +72,6 @@ from nacsos_data.util.annotations.validation import ( ) from nacsos_data.util.annotations.assignments.random import random_assignments from nacsos_data.util.annotations.assignments.random_exclusion import random_assignments_with_exclusion -from nacsos_data.util.annotations.assignments.random_nql import random_assignments_with_nql from server.api.errors import ( SaveFailedError, @@ -390,17 +388,6 @@ async def make_assignments(payload: MakeAssignmentsRequestModel, except ValueError as e: raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail=str(e)) - elif payload.config.config_type == 'random_nql': - try: - assignments = await random_assignments_with_nql( - assignment_scope_id=payload.scope_id, - annotation_scheme_id=payload.annotation_scheme_id, - project_id=permissions.permissions.project_id, - config=payload.config, # type: ignore[arg-type] # FIXME - engine=db_engine) - except ValueError as e: - raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, - detail=str(e)) else: raise HTTPException(status_code=http_status.HTTP_501_NOT_IMPLEMENTED, detail=f'Method "{payload.config.config_type}" is unknown.') @@ -435,12 +422,13 @@ async def get_annotators_for_scheme(scheme_id: str, .where(Annotation.annotation_scheme_id == scheme_id))).scalars().all()] -@router.post('/config/resolve/', response_model=ResolutionProposal) +@router.post('/config/resolve', response_model=ResolutionProposal) async def get_resolved_annotations(settings: BotMetaResolveBase, - include_empty: bool | None = Query(default=False), - existing_resolution: str | None = Query(default=None), - include_new: bool | None = Query(default=False), - update_existing: bool | None = Query(default=False), + assignment_scope_id: str | None = None, + bot_annotation_metadat_id: str | None = None, + include_empty: bool = False, + include_new: bool = False, + update_existing: bool = False, permissions=Depends(UserPermissionChecker('annotations_edit'))) \ -> ResolutionProposal: """ @@ -448,28 +436,30 @@ async def get_resolved_annotations(settings: BotMetaResolveBase, :param include_new: :param update_existing: - :param existing_resolution: + :param assignment_scope_id: + :param bot_annotation_metadat_id: :param include_empty: :param settings :param permissions: :return: """ if include_empty is None: - include_empty = True + include_empty = True # type: ignore[unreachable] if include_new is None: - include_new = False + include_new = False # type: ignore[unreachable] if update_existing is None: - update_existing = False + update_existing = False # type: ignore[unreachable] - if existing_resolution is not None: + if bot_annotation_metadat_id is not None: return await read_resolved_bot_annotations(db_engine=db_engine, - existing_resolution=existing_resolution, + existing_resolution=bot_annotation_metadat_id, include_new=include_new, include_empty=include_empty, update_existing=update_existing) - filters = AnnotationFilterObject.model_validate(settings.filters.model_dump()) + if assignment_scope_id is None: + raise ValueError('Missing assignment scope') return await get_resolved_item_annotations(strategy=settings.algorithm, - filters=filters, + assignment_scope_id=assignment_scope_id, ignore_repeat=settings.ignore_repeat, ignore_hierarchy=settings.ignore_hierarchy, include_new=include_new, @@ -502,12 +492,15 @@ async def get_saved_resolved_annotations(bot_annotation_metadata_id: str, async def save_resolved_annotations(settings: BotMetaResolveBase, matrix: ResolutionMatrix, name: str, + assignment_scope_id: str, + annotation_scheme_id: str, permissions=Depends(UserPermissionChecker('annotations_edit'))): meta_id = await store_resolved_bot_annotations(db_engine=db_engine, project_id=permissions.permissions.project_id, + assignment_scope_id=assignment_scope_id, + annotation_scheme_id=annotation_scheme_id, name=name, algorithm=settings.algorithm, - filters=settings.filters, ignore_hierarchy=settings.ignore_hierarchy, ignore_repeat=settings.ignore_repeat, matrix=matrix) @@ -525,9 +518,10 @@ async def update_resolved_annotations(bot_annotation_metadata_id: str, @router.get('/config/resolved-list/', response_model=list[BotAnnotationMetaDataBaseModel]) -async def list_saved_resolved_annotations(permissions=Depends(UserPermissionChecker('annotations_read'))): +async def list_saved_resolved_annotations(annotation_scheme_id: str | None = None, + permissions=Depends(UserPermissionChecker('annotations_read'))): async with db_engine.session() as session: # type: AsyncSession - exports = (await session.execute( + stmt = ( select(BotAnnotationMetaData) .where(BotAnnotationMetaData.project_id == permissions.permissions.project_id, BotAnnotationMetaData.kind == BotKind.RESOLVE) @@ -539,9 +533,11 @@ async def list_saved_resolved_annotations(permissions=Depends(UserPermissionChec BotAnnotationMetaData.name, BotAnnotationMetaData.kind, BotAnnotationMetaData.time_updated, - BotAnnotationMetaData.time_created)))) \ - .scalars().all() - + BotAnnotationMetaData.time_created)) + ) + if annotation_scheme_id is not None: + stmt = stmt.where(BotAnnotationMetaData.annotation_scheme_id == annotation_scheme_id) + exports = (await session.execute(stmt)).scalars().all() return [BotAnnotationMetaDataBaseModel.model_validate(e.__dict__) for e in exports] diff --git a/server/api/routes/evaluation.py b/server/api/routes/evaluation.py index b34be08..d7ab7ad 100644 --- a/server/api/routes/evaluation.py +++ b/server/api/routes/evaluation.py @@ -161,7 +161,7 @@ async def bg_populate_tracker(tracker_id: str, batch_size: int | None = None, la if batch_size is None: # Use scopes as batches - it = calculate_h0s_for_batches(labels=labels, + it = calculate_h0s_for_batches(labels=tracker.labels, recall_target=tracker.recall_target, n_docs=tracker.n_items_total) else: @@ -190,8 +190,10 @@ async def get_irr(assignment_scope_id: str, return [AnnotationQualityModel(**r.__dict__) for r in results] -@router.get('/quality/compute/{assignment_scope_id}', response_model=list[AnnotationQualityModel]) +@router.get('/quality/compute', response_model=list[AnnotationQualityModel]) async def recompute_irr(assignment_scope_id: str, + bot_annotation_metadata_id: str | None = None, + relevance_rule: str | None = None, permissions: UserPermissions = Depends(UserPermissionChecker('annotations_read'))) \ -> list[AnnotationQualityModel]: async with db_engine.session() as session: # type: AsyncSession diff --git a/server/api/routes/users.py b/server/api/routes/users.py index 9491235..ea0c590 100644 --- a/server/api/routes/users.py +++ b/server/api/routes/users.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Query from sqlalchemy import select, asc from nacsos_data.util.auth import UserPermissions -from nacsos_data.models.users import UserModel, UserInDBModel, UserBaseModel +from nacsos_data.models.users import UserModel, UserInDBModel, UserBaseModel, DehydratedUser from nacsos_data.db.schemas import User, AssignmentScope, AnnotationScheme, Assignment from nacsos_data.db.crud.users import ( read_users, @@ -35,6 +35,15 @@ async def get_all_users(current_user: UserModel = Depends(get_current_active_use return result +@router.get('/list/all/dehydrated', response_model=list[DehydratedUser]) +async def get_all_users_dehydrated(current_user: UserModel = Depends(get_current_active_user)) \ + -> list[UserInDBModel]: + result = await read_users(project_id=None, order_by_username=True, engine=db_engine) + if result is None: + return [] + return result + + @router.get('/list/project/annotators/{project_id}', response_model=dict[str, UserBaseModel]) async def get_project_annotator_users(project_id: str, permissions: UserPermissions = Depends(UserPermissionChecker())) \ -- GitLab