Commit 450563e2 authored by Powell, Eric's avatar Powell, Eric
Browse files

SQL scripts to generate schedule, spend plan and spend curve from Resolution data added

parent 0579ae2c
Loading
Loading
Loading
Loading
+0 −102
Original line number Diff line number Diff line
-- estimated_duration

let
    Source = PostgreSQL.Database("hgis-prj-mgmt:5438", "proj_status"),
    f_o_isg_vext_task_duration = Source{[Schema="f_o_isg",Item="vext_task_duration"]}[Data],
    JoinedTables = Table.NestedJoin(f_o_isg_vext_task_duration, {"title"}, NumericalData, {"Title"}, "title_matches", JoinKind.Inner),
       ExpandedTable = Table.ExpandTableColumn(JoinedTables, "title_matches", {"effort_wt"}, {"NumericalData.effort_wt"}),
     weighted_fte = Table.AddColumn(ExpandedTable, "weighted_fte", each [estimated_fte_months] * [NumericalData.effort_wt]),
    wt_dur_day = Table.AddColumn(weighted_fte, "weighted_duration_days", each [def_task_dur_days] *  [NumericalData.effort_wt]),
    wt_dur_mo = Table.AddColumn(wt_dur_day, "weighted_duration_months", each [def_task_dur_mo] *  [NumericalData.effort_wt]),
    #"Changed Type" = Table.TransformColumnTypes(wt_dur_mo,{{"weighted_duration_days", type number}, {"weighted_duration_months", type number}, {"weighted_fte", type number}})
       
in
    #"Changed Type"

-- estimatedcost

let
    Source = PostgreSQL.Database("hgis-prj-mgmt:5438", "proj_status"),
    f_o_isg_v_task_cost_estimate = Source{[Schema="f_o_isg",Item="vext_task_cost_estimate"]}[Data],
     JoinedTables = Table.NestedJoin(f_o_isg_v_task_cost_estimate, {"title"}, NumericalData, {"Title"}, "title_matches", JoinKind.Inner),
       ExpandedTable = Table.ExpandTableColumn(JoinedTables, "title_matches", {"effort_wt"}, {"NumericalData.effort_wt"}),
    weighted_cost = Table.AddColumn(ExpandedTable, "weighted_cost", each [estimated_cost] * [NumericalData.effort_wt]),
  #"Changed Type" = Table.TransformColumnTypes(weighted_cost,{"weighted_cost", type number})
       
in
    #"Changed Type"

-- projectscheduie

ProjectSchedule = 
VAR StartDate = TODAY() // A fixed start date for all parallel schedules

// This variable first joins the necessary data from all three tables.
// It iterates over the central 'metadata' table and uses RELATED()
// to pull in columns from the related 'v_ian_duration' and 'FeaturePrioritization' tables.
VAR TasksJoined =
    ADDCOLUMNS(
        'metadata',
        // "Platform", 'metadata'[Platform],
        "Duration", RELATED('estimated duration'[weighted_duration_months]),
        "Rank", RELATED('NumericalData'[mgt_weighted_rank])
    )

VAR TasksSorted =
    ADDCOLUMNS(
        TasksJoined,
        // A hidden column for stable sorting using the Rank
        "__SortValue", [Rank]
    )

// Step 1: Create a table with the TaskStartDate column
VAR TasksWithStartDates =
    ADDCOLUMNS(
        TasksSorted,
        // Calculate the cumulative duration of all preceding tasks
        // using SUMX and EARLIER. This is the core logic for sequencing.
        "TaskStartDate",
            VAR CumulativeDuration =
                SUMX(
                    // The FILTER has been modified to only sum durations for tasks
                    // that share the same 'Primary Resource'
                    FILTER(
                        TasksSorted,
                        // Group the calculation by Primary Resource
                        [Primary Resource] = EARLIER([Primary Resource]) &&
                        // Then, apply the sorting logic for the cumulative sum
                        (
                            [__SortValue] < EARLIER([__SortValue]) || (
                                [__SortValue] = EARLIER([__SortValue]) &&
                                [title] < EARLIER([title])
                            )
                        )
                    ),
                    [Duration]
                )
            RETURN
                // EDATE is used here to correctly add the cumulative months to the start date.
                EDATE(StartDate, CumulativeDuration)
    )

// Step 2: Create a new table that now has access to the TaskStartDate column
VAR TasksWithBothDates =
    ADDCOLUMNS(
        TasksWithStartDates,
        "TaskEndDate",
            // The end date is calculated by adding the task's duration in months to its start date.
            EDATE([TaskStartDate], [Duration])
    )

// Return the final table, selecting only the necessary columns
RETURN
    SELECTCOLUMNS(
        TasksWithBothDates,
        "Title", [title],
        "Primary Resource", [Primary Resource],
        "Rank", [Rank],
        "Duration", [Duration],
        "StartDate", [TaskStartDate],
        "EndDate", [TaskEndDate]
    )
+209 −0
Original line number Diff line number Diff line
/*CREATE OR REPLACE VIEW f_o_itsd_estimate.v_ian_duration
AS WITH wo_team_count AS (
         SELECT count(r_1.resource_id) AS team_count,
            p_1.project_team_id,
            w_1.workorder_id,
            w_1.title
           FROM f_o_itsd_estimate.workorders w_1
             JOIN f_o_itsd_estimate.project_team p_1 ON w_1.workorder_id = p_1.workorder_id
             JOIN f_o_itsd_estimate.project_team_members ptm_1 ON ptm_1.project_team_id = p_1.project_team_id
             JOIN f_o_itsd_estimate.resources r_1 ON r_1.resource_id = ptm_1.resource_id
          GROUP BY p_1.project_team_id, w_1.workorder_id
        )
 SELECT DISTINCT w.internal_order_number,
    w.title,
    rp.description,
    (wtc.team_count * rp.def_duration_mo)::numeric * p.percent_commit / 12::numeric AS estimated_fte_months,
    wtc.team_count::numeric * (100::numeric - rp.parallelism) / 100::numeric * rp.def_duration_mo::numeric AS task_duration
   FROM f_o_itsd_estimate.resource_pools rp
     JOIN f_o_itsd_estimate.resources r ON rp.resource_pool_id = r.resource_pool_id
     JOIN f_o_itsd_estimate.project_team_members ptm ON ptm.resource_id = r.resource_id
     JOIN f_o_itsd_estimate.project_team p ON ptm.project_team_id = p.project_team_id
     JOIN f_o_itsd_estimate.workorders w ON w.workorder_id = p.workorder_id
     JOIN wo_team_count wtc ON wtc.workorder_id = w.workorder_id AND wtc.project_team_id = p.project_team_id;

*/
/*CREATE OR REPLACE VIEW f_o_isg.vext_task_duration
AS SELECT vid.title,
    round(vid.estimated_fte_months, 1) AS estimated_fte_months,
    round(vid.estimated_fte_months * 160::numeric, 1) AS estimated_hours,
    vid.task_duration AS def_task_dur_mo,
    vid.task_duration * 20::numeric AS def_task_dur_days
   FROM f_o_isg.v_ian_duration vid;
*/

/* Functions   */
CREATE OR REPLACE FUNCTION fo_itsd_estimate.generate_mondays(start_date date, end_date date)
 RETURNS TABLE(monday_date date)
 LANGUAGE plpgsql
AS $function$
DECLARE
    the_date DATE;
BEGIN
    IF start_date IS NULL OR end_date IS NULL THEN
        RETURN;
    END IF;

    IF start_date > end_date THEN
        RAISE EXCEPTION 'Start date (%) cannot be after end date (%)', start_date, end_date;
    END IF;

    the_date := start_date + (1 - EXTRACT(DOW FROM start_date))::INTEGER * INTERVAL '1 day';

    WHILE the_date <= end_date LOOP
        monday_date := the_date;
        RETURN NEXT;
        the_date := the_date + INTERVAL '7 days';
    END LOOP;

    RETURN;
END;
$function$
;

CREATE OR REPLACE FUNCTION fo_itsd_estimate.get_pals_report_date(start_date date)
 RETURNS date
 LANGUAGE plpgsql
AS $function$
DECLARE
	sunday_date DATE;
	pals_date DATE;
BEGIN
--    IF start_date IS NULL THEN
--        RETURN;
--    END IF;

    sunday_date := start_date + (0 - EXTRACT(DOW FROM start_date))::INTEGER * INTERVAL '1 day';
	pals_date := sunday_date + INTERVAL '7 days';
	raise notice 'Start Date: %', start_date;
	raise notice 'Sunday Date: %', sunday_date;
	raise notice 'PALS Weekend Date: %', pals_date ;

    RETURN pals_date;
END;
$function$
;

CREATE OR REPLACE FUNCTION fo_itsd_estimate.generate_pals_date(start_date date, end_date date)
 RETURNS TABLE(monday_date date)
 LANGUAGE plpgsql
AS $function$
DECLARE
    the_date DATE;
BEGIN
    IF start_date IS NULL OR end_date IS NULL THEN
        RETURN;
    END IF;

    IF start_date > end_date THEN
        RAISE EXCEPTION 'Start date (%) cannot be after end date (%)', start_date, end_date;
    END IF;

    the_date := start_date + (0 - EXTRACT(DOW FROM start_date))::INTEGER * INTERVAL '1 day';

    WHILE the_date <= end_date LOOP
        monday_date := the_date;
        RETURN NEXT;
        the_date := the_date + INTERVAL '7 days';
    END LOOP;

    RETURN;
END;
$function$
;

/* Views   */


-- Activity Schedule
create or replace view fo_itsd_estimate.vext_activity_schedule as
with actvity_dates as 
(
	select min(start_date) start_date, max(end_date) - min(start_date) duration, activity_id from 
	fo_itsd_estimate.task_details
	group by activity_id
)
select activity_description, start_date, duration 
from  actvity_dates ad 
inner join 
fo_itsd_estimate.activities a  
on a.activity_id = ad.activity_id;


-- Task Schedule

create or replace view fo_itsd_estimate.vext_task_schedule as
with actvity_dates as 
(
	select start_date, end_date - start_date duration, task_id, activity_id from 
	fo_itsd_estimate.task_details
	group by task_id
)
select activity_description, task_description,  ad.start_date, duration 
from  actvity_dates ad 
inner join 
fo_itsd_estimate.activities a  
on a.activity_id = ad.activity_id
inner join fo_itsd_estimate.task_details td
on ad.activity_id = td.activity_id;


-- Generate the Spending Plan

-- Totol FTEs/Hours per employee over project lifecycle
create or replace view fo_itsd_estimate.vint_spend_rate as
with fte_inputs as 
(
	select max(end_date) - min(start_date) as duration, sum(hours) as total_hours, resource_id from 
	fo_itsd_estimate.task_details td
	group by resource_id
)
select resource_id, round(total_hours::numeric/1980,1)::numeric as ftes, round(total_hours::numeric/(duration::numeric/7),0) hrs_wk,  total_hours, duration/7 weeks from fte_inputs;

--Calculate for the task level
create or replace view fo_itsd_estimate.vint_spend_rate_task as
with fte_inputs as 
(
	select max(end_date) - min(start_date) as duration, sum(hours) as total_hours, resource_id, task_id from 
	fo_itsd_estimate.task_details td
	group by resource_id, task_id
)
select resource_id, task_id, round(total_hours::numeric/1980,1)::numeric as ftes, round(total_hours::numeric/(duration::numeric/7),0) hrs_wk,  total_hours, duration/7 weeks from fte_inputs;

-- Same at Activity level
drop view fo_itsd_estimate.vint_spend_rate_activity;
create or replace view fo_itsd_estimate.vint_spend_rate_activity as
with fte_inputs as 
(
	select max(end_date) - min(start_date) as duration, min(start_date) start_date, max(end_date) end_date, sum(hours) as total_hours, resource_id, activity_id from 
	fo_itsd_estimate.task_details td
	group by resource_id, activity_id
)
select resource_id, activity_id, 
start_date, 
end_date, 
coalesce(round(total_hours::numeric/1980,2)::numeric, 0.05) as res_activity_fte, 
coalesce(round(total_hours::numeric/(duration::numeric/7),2), 0.5) hrs_wk,  
total_hours, 
duration/7 weeks 
from fte_inputs;




-- Using the task start / end dates and hours assigned, generate the hours / week by employee
CREATE OR REPLACE VIEW fo_itsd_estimate.v_labor_plan_data
AS SELECT sp.the_date,
    sp.full_name,
    sum(sp.res_activity_fte) AS fte
   FROM ( SELECT fo_itsd_estimate.generate_pals_date(ftes.start_date, ftes.end_date) AS the_date,
            full_name,
            ftes.res_activity_fte,
            (ftes.res_activity_fte * 40::numeric)::numeric + (ftes.res_activity_fte * 40::numeric)::double precision * td.contingency_percent AS weekly_labor
           FROM fo_itsd_estimate.vint_spend_rate_activity ftes
             JOIN fo_itsd_estimate.task_details td ON td.resource_id = ftes.resource_id AND td.activity_id = ftes.activity_id
             JOIN fo_itsd_estimate.resources pt ON ftes.resource_id = pt.resource_id
          GROUP BY full_name, ftes.res_activity_fte, td.contingency_percent, ftes.start_date, ftes.end_date) sp
  WHERE sp.res_activity_fte > 0::numeric
  GROUP BY sp.the_date, sp.full_name
  ORDER BY sp.the_date, sp.full_name;