diff --git a/.gitignore b/.gitignore index e08d13c0a6..265de0398e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ client/yarn.lock server/web/static/dist server/web/static/dist/ +# Ignore project files # +server/project-files + # Ignore local logs logs/ *.log @@ -43,3 +46,7 @@ ehthumbs.db Thumbs.db .elasticbeanstalk/ venv_aws/ + +# emacs files # +\#*\# +*~ \ No newline at end of file diff --git a/client/app/project/project-dashboard.html b/client/app/project/project-dashboard.html index f0745b9055..c91eefd2b3 100644 --- a/client/app/project/project-dashboard.html +++ b/client/app/project/project-dashboard.html @@ -75,14 +75,18 @@

{{ 'Contributions' | translate }}

{{ 'User' | translate }} {{ 'Mapped' | translate }} + {{ 'Validated' | translate }} + {{ item.username }} {{ item.mapped }} + {{ item.validated }} + @@ -97,13 +101,26 @@

{{ 'Contributions' | translate }}

{{ 'Stats' | translate }}

+

Task Completion

+ {{ ' Total Tasks' | translate }}: {{ projectDashboardCtrl.project.totalTasks }} | + {{ 'Tasks Mapped' | translate }}: {{ projectDashboardCtrl.project.tasksMapped }} | + {{ ' Tasks Validated' | translate }}: {{ projectDashboardCtrl.project.tasksValidated }} | + {{ ' Bad Imagery' | translate }}: {{ projectDashboardCtrl.project.tasksBadImagery }}
{{ projectDashboardCtrl.project.percentMapped }}{{ '% Mapped' | translate }} {{ projectDashboardCtrl.project.percentValidated }}{{ '% Validated' | translate }}
+

Area Completion

+
+ {{ projectDashboardCtrl.project.areaPercentMapped }}{{ '% Mapped' | translate }} + + {{ projectDashboardCtrl.project.areaPercentValidated }}{{ '% Validated' | translate }} + + {{ 'NOTE: This percentage may be greater than 100 if the tasks aren\'t clipped to the project AOI' | translate }} +
diff --git a/client/index.html b/client/index.html index 0833d3fa72..97bd8c9e83 100644 --- a/client/index.html +++ b/client/index.html @@ -265,6 +265,8 @@

+ + diff --git a/server/models/dtos/project_dto.py b/server/models/dtos/project_dto.py index 0040ca5683..4898a50928 100644 --- a/server/models/dtos/project_dto.py +++ b/server/models/dtos/project_dto.py @@ -316,6 +316,11 @@ class ProjectStatsDTO(Model): percent_validated = IntType(serialized_name='percentValidated') percent_bad_imagery = IntType(serialized_name='percentBadImagery') aoi_centroid = BaseType(serialized_name='aoiCentroid') + tasks_mapped = IntType(serialized_name='tasksMapped') + tasks_validated = IntType(serialized_name='tasksValidated') + tasks_bad_imagery = IntType(serialized_name='tasksBadImagery') + area_percent_mapped = IntType(serialized_name='areaPercentMapped') + area_percent_validated = IntType(serialized_name='areaPercentValidated') class ProjectUserStatsDTO(Model): diff --git a/server/models/dtos/stats_dto.py b/server/models/dtos/stats_dto.py index f636e4101b..1bbab402ef 100644 --- a/server/models/dtos/stats_dto.py +++ b/server/models/dtos/stats_dto.py @@ -9,6 +9,8 @@ class UserContribution(Model): username = StringType() mapped = IntType() validated = IntType() + project_percent_mapped = IntType() + project_percent_validated = IntType() class ProjectContributionsDTO(Model): diff --git a/server/models/postgis/project.py b/server/models/postgis/project.py index c57c6cc3e2..59f3645951 100644 --- a/server/models/postgis/project.py +++ b/server/models/postgis/project.py @@ -394,6 +394,57 @@ def get_project_user_stats(self, user_id: int) -> ProjectUserStatsDTO: return stats_dto + def get_mapped_area(project_id: int): + """ Get all tasks that are mapped """ + sql = '''SELECT sum(ST_Area(geom)) + FROM ( + SELECT t.geometry :: geometry geom + FROM tasks t + WHERE t.project_id = :project_id AND t.task_status IN (2,4)) sub; + ''' + + result = db.engine.execute(text(sql), project_id=project_id) + if result.rowcount == 0: + raise NotFound() + + area = result.next()[0] + result.close() + return area or 0 + + def get_validated_area(project_id: int): + """ Get all tasks that are validated """ + sql = '''SELECT sum(ST_Area(geom)) + FROM ( + SELECT t.geometry :: geometry geom + FROM tasks t + WHERE t.project_id = :project_id AND t.task_status = 4) s; + ''' + + result = db.engine.execute(text(sql), project_id=project_id) + if result.rowcount == 0: + raise NotFound() + + area = result.next()[0] + result.close() + return area or 0 + + def get_bad_imagery_area(project_id: int): + """ Get all tasks that are marked as bad imagery """ + sql = '''SELECT sum(ST_Area(geom)) + FROM ( + SELECT t.geometry :: geometry geom + FROM tasks t + WHERE t.project_id = :project_id AND t.task_status = 6) sub; + ''' + + result = db.engine.execute(text(sql), project_id=project_id) + if result.rowcount == 0: + raise NotFound() + + area = result.next()[0] + result.close() + return area or 0 + def get_project_stats(self) -> ProjectStatsDTO: """ Create Project Summary model for postgis project object""" project_stats = ProjectStatsDTO() @@ -412,6 +463,9 @@ def get_project_stats(self) -> ProjectStatsDTO: project_stats.area = area project_stats.total_mappers = db.session.query(User).filter(User.projects_mapped.any(self.id)).count() project_stats.total_tasks = self.total_tasks + project_stats.tasks_mapped = self.tasks_mapped + self.tasks_validated + project_stats.tasks_validated = self.tasks_validated + project_stats.tasks_bad_imagery = self.tasks_bad_imagery project_stats.total_comments = db.session.query(ProjectChat).filter(ProjectChat.project_id == self.id).count() project_stats.percent_mapped = Project.calculate_tasks_percent('mapped', self.total_tasks, self.tasks_mapped, self.tasks_validated, @@ -464,6 +518,10 @@ def get_project_stats(self) -> ProjectStatsDTO: average_validation_time = total_validation_seconds/unique_validators project_stats.average_validation_time = average_validation_time + # Calculate area_percent_complete to get completion percentage by state according to area + project_stats.area_percent_mapped = int((Project.get_mapped_area(self.id) / (polygon.area - Project.get_bad_imagery_area(self.id))) * 100) + project_stats.area_percent_validated = int((Project.get_validated_area(self.id) / (polygon.area - Project.get_bad_imagery_area(self.id))) * 100) + return project_stats def get_project_summary(self, preferred_locale) -> ProjectSummary: diff --git a/server/services/stats_service.py b/server/services/stats_service.py index 9f7e2b2320..a750746ca1 100644 --- a/server/services/stats_service.py +++ b/server/services/stats_service.py @@ -140,6 +140,11 @@ def get_user_contributions(project_id: int) -> ProjectContributionsDTO: user_contrib.username = row[1] if row[1] else row[4] user_contrib.mapped = row[2] if row[2] else 0 user_contrib.validated = row[5] if row[5] else 0 + + # Calculate user's contribution percentages + project = Project.get(project_id) + user_contrib.project_percent_mapped = (user_contrib.mapped / (project.tasks_mapped or 1)) * 100 + user_contrib.project_percent_validated = (user_contrib.validated / (project.tasks_validated or 1)) * 100 contrib_dto.user_contributions.append(user_contrib) return contrib_dto