diff --git a/epictrack-api/Makefile b/epictrack-api/Makefile index c1f714ee2..9e0fad32b 100644 --- a/epictrack-api/Makefile +++ b/epictrack-api/Makefile @@ -59,7 +59,7 @@ pylint: ## Linting with pylint . venv/bin/activate && pylint --rcfile=setup.cfg src/$(PROJECT_NAME) flake8: ## Linting with flake8 - . venv/bin/activate && flake8 --extend-ignore=Q000,D400,D401,I005,W503 src/$(PROJECT_NAME) tests + . venv/bin/activate && flake8 --extend-ignore=Q000,D400,D401,I005,W503,E261,E121 src/$(PROJECT_NAME) tests lint: pylint flake8 ## run all lint type scripts diff --git a/epictrack-api/src/api/__init__.py b/epictrack-api/src/api/__init__.py index 5319c143d..444d36128 100644 --- a/epictrack-api/src/api/__init__.py +++ b/epictrack-api/src/api/__init__.py @@ -43,7 +43,9 @@ def create_app(run_mode=os.getenv('FLASK_ENV', 'production')): """Return a configured Flask App using the Factory method.""" app = Flask(__name__) app.config.from_object(config.CONFIGURATION[run_mode]) - app.logger.setLevel(logging.INFO) # pylint: disable=no-member + log_level = os.getenv('LOG_LEVEL', 'INFO').upper() + app.logger.info(f'Current log level: {log_level}') + app.logger.setLevel(getattr(logging, log_level, logging.INFO)) # pylint: disable=no-member app.json_provider_class = CustomJSONEncoder db.init_app(app) diff --git a/epictrack-api/src/api/reports/anticipated_schedule_report.py b/epictrack-api/src/api/reports/anticipated_schedule_report.py index d4e707de6..15866dcf5 100644 --- a/epictrack-api/src/api/reports/anticipated_schedule_report.py +++ b/epictrack-api/src/api/reports/anticipated_schedule_report.py @@ -3,7 +3,7 @@ from flask import jsonify, current_app from pytz import timezone -from sqlalchemy import and_, func, select +from sqlalchemy import and_, func, select, or_ from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.orm import aliased @@ -39,6 +39,7 @@ class EAAnticipatedScheduleReport(ReportFactory): def __init__(self, filters, color_intensity): """Initialize the ReportFactory""" data_keys = [ + "work_id", "phase_name", "date_updated", "project_name", @@ -59,6 +60,8 @@ def __init__(self, filters, color_intensity): "next_pecp_title", "next_pecp_short_description", "milestone_type", + "category_type", + "event_name" ] group_by = "phase_name" template_name = "anticipated_schedule.docx" @@ -79,6 +82,8 @@ def _fetch_data(self, report_date): exclude_phase_names = [] if self.filters and "exclude" in self.filters: exclude_phase_names = self.filters["exclude"] + + current_app.logger.debug(f"Executing query for {self.report_title} report") results_qry = ( db.session.query(Work) .join(Event, Event.work_id == Work.id) @@ -122,15 +127,71 @@ def _fetch_data(self, report_date): ) # FILTER ENTRIES MATCHING MIN DATE FOR NEXT PECP OR NO WORK ENGAGEMENTS (FOR AMENDMENTS) .filter( - Work.is_active.is_(True), - Work.is_deleted.is_(False), - Work.work_state.in_( - [WorkStateEnum.IN_PROGRESS.value, WorkStateEnum.SUSPENDED.value] - ), - # Filter out specific WorkPhase names - ~WorkPhase.name.in_(exclude_phase_names) + Work.is_active.is_(True), + Event.anticipated_date.between(report_date - timedelta(days=7), report_date + timedelta(days=366)), + or_( + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_type_id == 5, # EA Referral + EventConfiguration.event_category_id == 1 # Milestone, + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.event_type_id == 14 # Minister + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.name != "IPD/EP Approval Decision (Day Zero)", + EventConfiguration.event_type_id == 15 # CEAO + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.name != "IPD/EP Approval Decision (Day Zero)", + EventConfiguration.name != "Revised EAC Application Acceptance Decision (Day Zero)", + EventConfiguration.event_type_id == 15 # CEAO + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.name != "Delegation of Amendment Decision", + or_( + EventConfiguration.event_type_id == 15, # CEAO + EventConfiguration.event_type_id == 16 # ADM + ) + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.name != "Delegation of SubStart Decision to Minister", + EventConfiguration.event_type_id == 15 # CEAO + ) + ), + Event.event_configuration_id.in_( + db.session.query(EventConfiguration.id).filter( + EventConfiguration.event_category_id == 4, # Decision + EventConfiguration.name != "Delegation of Transfer Decision to Minister", + or_( + EventConfiguration.event_type_id == 15, # CEAO + EventConfiguration.event_type_id == 16 # ADM + ) + ) + ) + ), + Work.is_deleted.is_(False), + Work.work_state.in_([WorkStateEnum.IN_PROGRESS.value, WorkStateEnum.SUSPENDED.value]), + # Filter out specific WorkPhase names + ~WorkPhase.name.in_(exclude_phase_names) ) .add_columns( + Work.id.label("work_id"), PhaseCode.name.label("phase_name"), latest_status_updates.c.posted_date.label("date_updated"), Project.name.label("project_name"), @@ -154,6 +215,8 @@ def _fetch_data(self, report_date): eac_decision_by.full_name.label("eac_decision_by"), decision_by.full_name.label("decision_by"), EventConfiguration.event_type_id.label("milestone_type"), + EventConfiguration.event_category_id.label("category_type"), + EventConfiguration.name.label("event_name"), func.coalesce(next_pecp_query.c.name, Event.name).label( "next_pecp_title" ), diff --git a/epictrack-api/src/api/resources/reports.py b/epictrack-api/src/api/resources/reports.py index a5fc74bf1..fbdedfdc3 100644 --- a/epictrack-api/src/api/resources/reports.py +++ b/epictrack-api/src/api/resources/reports.py @@ -16,7 +16,7 @@ from http import HTTPStatus from io import BytesIO -from flask import jsonify, send_file +from flask import jsonify, send_file, current_app from flask_restx import Namespace, Resource, cors from api.schemas.event_calendar import EventCalendarSchema @@ -58,6 +58,9 @@ class Report(Resource): @profiletime def post(report_type): """Generate report from given date.""" + current_app.logger.debug(f"Generating report of type: {report_type}") + current_app.logger.debug(f"Endpoint called: /reports/{report_type}") + current_app.logger.debug(f"Request payload: {API.payload}") report_date = datetime.strptime(API.payload["report_date"], "%Y-%m-%d") color_intensity = API.payload.get("color_intensity", None) filters = API.payload.get("filters", None)