First pass at new reporting designs
This commit lays out the genral structure and provides necessary data for the new reporting page designs. Some of the data generated by the report domain classes (including the mock CSP reporting class) was modified to fit new designs. This also included removing data that was no longer necessary. Part of the newly mocked data includes the idea of "expended" data per CLIN or task order. This was was mocked simply by using a 75% of the obligated funds fo a given object. Tests were also written for these new/ modifed reporting functions. As for the front end, this commit only focuses on the high-level markup layout. This includes splitting the large reporting index page into smaller component templates for each of the major sections of the report.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from itertools import groupby
|
||||
from collections import OrderedDict
|
||||
from atst.utils.localization import translate
|
||||
import pendulum
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class ReportingInterface:
|
||||
@@ -35,14 +36,16 @@ def generate_sample_dates(_max=8):
|
||||
current = pendulum.now()
|
||||
sample_dates = []
|
||||
for _i in range(_max):
|
||||
current = current.subtract(months=1)
|
||||
sample_dates.append(current.strftime("%m/%Y"))
|
||||
current = current.subtract(months=1)
|
||||
|
||||
reversed(sample_dates)
|
||||
return sample_dates
|
||||
|
||||
|
||||
class MockReportingProvider(ReportingInterface):
|
||||
MOCK_PERCENT_EXPENDED_FUNDS = 0.75
|
||||
|
||||
FIXTURE_MONTHS = generate_sample_dates()
|
||||
|
||||
MONTHLY_SPEND_BY_ENVIRONMENT = {
|
||||
@@ -163,25 +166,8 @@ class MockReportingProvider(ReportingInterface):
|
||||
"FM_Prod": {FIXTURE_MONTHS[0]: 5686},
|
||||
}
|
||||
|
||||
CUMULATIVE_BUDGET_A_WING = {
|
||||
FIXTURE_MONTHS[7]: {"spend": 9857, "cumulative": 9857},
|
||||
FIXTURE_MONTHS[6]: {"spend": 7881, "cumulative": 17738},
|
||||
FIXTURE_MONTHS[5]: {"spend": 14010, "cumulative": 31748},
|
||||
FIXTURE_MONTHS[4]: {"spend": 43510, "cumulative": 75259},
|
||||
FIXTURE_MONTHS[3]: {"spend": 41725, "cumulative": 116_984},
|
||||
FIXTURE_MONTHS[2]: {"spend": 41328, "cumulative": 158_312},
|
||||
FIXTURE_MONTHS[1]: {"spend": 47491, "cumulative": 205_803},
|
||||
FIXTURE_MONTHS[0]: {"spend": 36028, "cumulative": 241_831},
|
||||
}
|
||||
|
||||
CUMULATIVE_BUDGET_B_WING = {
|
||||
FIXTURE_MONTHS[1]: {"spend": 4838, "cumulative": 4838},
|
||||
FIXTURE_MONTHS[0]: {"spend": 14500, "cumulative": 19338},
|
||||
}
|
||||
|
||||
REPORT_FIXTURE_MAP = {
|
||||
"A-Wing": {
|
||||
"cumulative": CUMULATIVE_BUDGET_A_WING,
|
||||
"applications": [
|
||||
MockApplication("LC04", ["Integ", "PreProd", "Prod"]),
|
||||
MockApplication("SF18", ["Integ", "PreProd", "Prod"]),
|
||||
@@ -202,7 +188,6 @@ class MockReportingProvider(ReportingInterface):
|
||||
"budget": 500_000,
|
||||
},
|
||||
"B-Wing": {
|
||||
"cumulative": CUMULATIVE_BUDGET_B_WING,
|
||||
"applications": [
|
||||
MockApplication("NP02", ["Integ", "PreProd", "Prod"]),
|
||||
MockApplication("FM", ["Integ", "Prod"]),
|
||||
@@ -211,28 +196,6 @@ class MockReportingProvider(ReportingInterface):
|
||||
},
|
||||
}
|
||||
|
||||
def _sum_monthly_spend(self, data):
|
||||
return sum(
|
||||
[
|
||||
spend
|
||||
for application in data
|
||||
for env in application.environments
|
||||
for spend in self.MONTHLY_SPEND_BY_ENVIRONMENT[env.id].values()
|
||||
]
|
||||
)
|
||||
|
||||
def get_budget(self, portfolio):
|
||||
if portfolio.name in self.REPORT_FIXTURE_MAP:
|
||||
return self.REPORT_FIXTURE_MAP[portfolio.name]["budget"]
|
||||
return 0
|
||||
|
||||
def get_total_spending(self, portfolio):
|
||||
if portfolio.name in self.REPORT_FIXTURE_MAP:
|
||||
return self._sum_monthly_spend(
|
||||
self.REPORT_FIXTURE_MAP[portfolio.name]["applications"]
|
||||
)
|
||||
return 0
|
||||
|
||||
def _rollup_application_totals(self, data):
|
||||
application_totals = {}
|
||||
for application, environments in data.items():
|
||||
@@ -270,7 +233,14 @@ class MockReportingProvider(ReportingInterface):
|
||||
{ "01/2018": 79.85, "02/2018": 86.54 }
|
||||
|
||||
"""
|
||||
return self.MONTHLY_SPEND_BY_ENVIRONMENT.get(environment_id, {})
|
||||
environment_monthly_totals = self.MONTHLY_SPEND_BY_ENVIRONMENT.get(
|
||||
environment_id, {}
|
||||
).copy()
|
||||
|
||||
environment_monthly_totals["total_spend_to_date"] = sum(
|
||||
monthly_total for monthly_total in environment_monthly_totals.values()
|
||||
)
|
||||
return environment_monthly_totals
|
||||
|
||||
def monthly_totals(self, portfolio):
|
||||
"""Return month totals rolled up by environment, application, and portfolio.
|
||||
@@ -309,19 +279,46 @@ class MockReportingProvider(ReportingInterface):
|
||||
"portfolio": portfolio_totals,
|
||||
}
|
||||
|
||||
def cumulative_budget(self, portfolio):
|
||||
def get_obligated_funds_by_JEDI_clin(self, portfolio):
|
||||
"""
|
||||
Returns a dictionary of obligated funds and spending per JEDI CLIN
|
||||
{
|
||||
JEDI_CLIN: {
|
||||
obligated_funds,
|
||||
expended_funds
|
||||
}
|
||||
}
|
||||
"""
|
||||
if portfolio.name in self.REPORT_FIXTURE_MAP:
|
||||
budget_months = self.REPORT_FIXTURE_MAP[portfolio.name]["cumulative"]
|
||||
else:
|
||||
budget_months = {}
|
||||
return_dict = {}
|
||||
for jedi_clin, clins in groupby(
|
||||
portfolio.active_clins, lambda clin: clin.jedi_clin_type
|
||||
):
|
||||
obligated_funds = sum(clin.obligated_amount for clin in clins)
|
||||
return_dict[translate(f"JEDICLINType.{jedi_clin.value}")] = {
|
||||
"obligated_funds": obligated_funds,
|
||||
"expended_funds": (
|
||||
obligated_funds * Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
|
||||
),
|
||||
}
|
||||
return return_dict
|
||||
return {}
|
||||
|
||||
end = pendulum.now()
|
||||
start = end.subtract(months=12)
|
||||
period = pendulum.period(start, end)
|
||||
|
||||
all_months = OrderedDict()
|
||||
for t in period.range("months"):
|
||||
month_str = "{month:02d}/{year}".format(month=t.month, year=t.year)
|
||||
all_months[month_str] = budget_months.get(month_str, None)
|
||||
|
||||
return {"months": all_months}
|
||||
def get_expired_task_orders(self, portfolio):
|
||||
return [
|
||||
{
|
||||
"id": task_order.id,
|
||||
"number": task_order.number,
|
||||
"period_of_performance": {
|
||||
"start_date": task_order.start_date,
|
||||
"end_date": task_order.end_date,
|
||||
},
|
||||
"total_obligated_funds": task_order.total_obligated_funds,
|
||||
"expended_funds": (
|
||||
task_order.total_obligated_funds
|
||||
* Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
|
||||
),
|
||||
}
|
||||
for task_order in portfolio.task_orders
|
||||
if task_order.is_expired
|
||||
]
|
||||
|
||||
@@ -2,16 +2,14 @@ from flask import current_app
|
||||
|
||||
|
||||
class Reports:
|
||||
@classmethod
|
||||
def portfolio_totals(cls, portfolio):
|
||||
budget = current_app.csp.reports.get_budget(portfolio)
|
||||
spent = current_app.csp.reports.get_total_spending(portfolio)
|
||||
return {"budget": budget, "spent": spent}
|
||||
|
||||
@classmethod
|
||||
def monthly_totals(cls, portfolio):
|
||||
return current_app.csp.reports.monthly_totals(portfolio)
|
||||
|
||||
@classmethod
|
||||
def cumulative_budget(cls, portfolio):
|
||||
return current_app.csp.reports.cumulative_budget(portfolio)
|
||||
def expired_task_orders(cls, portfolio):
|
||||
return current_app.csp.reports.get_expired_task_orders(portfolio)
|
||||
|
||||
@classmethod
|
||||
def obligated_funds_by_JEDI_clin(cls, portfolio):
|
||||
return current_app.csp.reports.get_obligated_funds_by_JEDI_clin(portfolio)
|
||||
|
||||
@@ -114,7 +114,7 @@ class Portfolio(
|
||||
for task_order in self.task_orders
|
||||
if task_order.is_active
|
||||
),
|
||||
default=None,
|
||||
default=0,
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -46,34 +46,24 @@ def create_portfolio():
|
||||
def reports(portfolio_id):
|
||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||
today = date.today()
|
||||
month = http_request.args.get("month", today.month)
|
||||
year = http_request.args.get("year", today.year)
|
||||
current_month = date(int(year), int(month), 15)
|
||||
current_month = date(int(today.year), int(today.month), 15)
|
||||
prev_month = current_month - timedelta(days=28)
|
||||
two_months_ago = prev_month - timedelta(days=28)
|
||||
|
||||
task_order = next(
|
||||
(task_order for task_order in portfolio.task_orders if task_order.is_active),
|
||||
None,
|
||||
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
||||
total_portfolio_value = str(
|
||||
sum(
|
||||
task_order.total_obligated_funds
|
||||
for task_order in portfolio.active_task_orders
|
||||
)
|
||||
)
|
||||
expiration_date = task_order and task_order.end_date
|
||||
if expiration_date:
|
||||
remaining_difference = expiration_date - today
|
||||
remaining_days = remaining_difference.days
|
||||
else:
|
||||
remaining_days = None
|
||||
|
||||
return render_template(
|
||||
"portfolios/reports/index.html",
|
||||
cumulative_budget=Reports.cumulative_budget(portfolio),
|
||||
portfolio_totals=Reports.portfolio_totals(portfolio),
|
||||
portfolio=portfolio,
|
||||
total_portfolio_value=total_portfolio_value,
|
||||
current_obligated_funds=Reports.obligated_funds_by_JEDI_clin(portfolio),
|
||||
expired_task_orders=Reports.expired_task_orders(portfolio),
|
||||
monthly_totals=Reports.monthly_totals(portfolio),
|
||||
task_order=task_order,
|
||||
current_month=current_month,
|
||||
prev_month=prev_month,
|
||||
two_months_ago=two_months_ago,
|
||||
expiration_date=expiration_date,
|
||||
remaining_days=remaining_days,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user