def split_task()

in backend/services/grid/split_service.py [0:0]


    def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs:
        """
        Replaces a task square with 4 smaller tasks at the next OSM tile grid zoom level
        Validates that task is:
         - locked for mapping by current user
        :param split_task_dto:
        :return: new tasks in a DTO
        """
        # get the task to be split
        original_task = Task.get(split_task_dto.task_id, split_task_dto.project_id)
        if original_task is None:
            raise NotFound()

        original_geometry = shape.to_shape(original_task.geometry)

        # Fetch the task geometry in meters
        original_task_area_m = db.engine.execute(
            ST_Area(ST_GeogFromWKB(original_task.geometry))
        ).scalar()

        if (
            original_task.zoom and original_task.zoom >= 18
        ) or original_task_area_m < 25000:
            raise SplitServiceError("Task is too small to be split")

        # check its locked for mapping by the current user
        if TaskStatus(original_task.task_status) != TaskStatus.LOCKED_FOR_MAPPING:
            raise SplitServiceError("Status must be LOCKED_FOR_MAPPING to split")

        if original_task.locked_by != split_task_dto.user_id:
            raise SplitServiceError("Attempting to split a task owned by another user")

        # create new geometries from the task geometry
        try:
            new_tasks_geojson = SplitService._create_split_tasks(
                original_task.x, original_task.y, original_task.zoom, original_task
            )
        except Exception as e:
            raise SplitServiceError(f"Error splitting task{str(e)}")

        # create new tasks from the new geojson
        i = Task.get_max_task_id_for_project(split_task_dto.project_id)
        new_tasks = []
        new_tasks_dto = []
        for new_task_geojson in new_tasks_geojson:
            # Sanity check: ensure the new task geometry intersects the original task geometry
            new_geometry = shapely_shape(new_task_geojson.geometry)
            if not new_geometry.intersects(original_geometry):
                raise InvalidGeoJson("New split task does not intersect original task")

            # insert new tasks into database
            i = i + 1
            new_task = Task.from_geojson_feature(i, new_task_geojson)
            new_task.project_id = split_task_dto.project_id
            new_task.task_status = TaskStatus.READY.value
            new_task.create()
            new_task.task_history.extend(original_task.copy_task_history())
            if new_task.task_history:
                new_task.clear_task_lock()  # since we just copied the lock
            new_task.set_task_history(
                TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.SPLIT
            )
            new_task.set_task_history(
                TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.READY
            )
            new_task.task_status = TaskStatus.READY.value
            new_tasks.append(new_task)
            new_task.update()
            new_tasks_dto.append(
                new_task.as_dto_with_instructions(split_task_dto.preferred_locale)
            )

        # delete original task from the database
        try:
            original_task.delete()
        except Exception:
            db.session.rollback()
            # Ensure the new tasks are cleaned up
            for new_task in new_tasks:
                new_task.delete()
            db.session.commit()
            raise

        # update project task counts
        project = Project.query.get(split_task_dto.project_id)
        project.total_tasks = project.tasks.count()
        # update bad imagery because we may have split a bad imagery tile
        project.tasks_bad_imagery = project.tasks.filter(
            Task.task_status == TaskStatus.BADIMAGERY.value
        ).count()
        project.save()

        # return the new tasks in a DTO
        task_dtos = TaskDTOs()
        task_dtos.tasks = new_tasks_dto
        return task_dtos