diff --git a/CSETWebNg/src/app/app.module.ts b/CSETWebNg/src/app/app.module.ts index 868f1426af..7362407bab 100644 --- a/CSETWebNg/src/app/app.module.ts +++ b/CSETWebNg/src/app/app.module.ts @@ -302,6 +302,8 @@ import { CommonModule } from '@angular/common'; import { NavBackNextComponent } from './assessment/navigation/nav-back-next/nav-back-next.component'; import { CsetOriginComponent } from './initial/cset-origin/cset-origin.component'; import { ComplianceScoreComponent } from './assessment/results/mat-cmmc/chart-components/compliance-score/compliance-score.component'; +import { ScoreRangeComponent } from './assessment/results/score-range/score-range.component'; +import { ScoreRangesComponent } from './assessment/results/score-ranges/score-ranges.component'; import { CmmcStyleService } from './services/cmmc-style.service'; import { InherentRiskProfileComponent } from './acet/inherent-risk-profile/inherent-risk-profile.component'; import { IrpSectionComponent } from './reports/irp/irp.component'; @@ -840,6 +842,8 @@ import { AnalyticsResultsComponent } from './assessment/results/analytics-result Cmmc2DomainResultsComponent, SprsScoreComponent, ComplianceScoreComponent, + ScoreRangeComponent, + ScoreRangesComponent, ModelSelectComponent, CategoryBlockComponent, AskQuestionsComponent, diff --git a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html index 02065c9f87..a06d49ce18 100644 --- a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html +++ b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.html @@ -22,10 +22,20 @@

Assessment Analytics

-
+
+ + +
+
+
+
+
+ + +
diff --git a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts index 8701b96f6d..599b319245 100644 --- a/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts +++ b/CSETWebNg/src/app/assessment/results/analytics-results/analytics-results.component.ts @@ -48,6 +48,9 @@ export class AnalyticsResultsComponent implements OnInit { @ViewChild('barCanvas') private barCanvas!: ElementRef; private barChart!: Chart; + // result from API call + scoreBarData: any; + // Toggle state dataType: "mySector" | "allSectors" = "mySector"; @@ -113,7 +116,14 @@ export class AnalyticsResultsComponent implements OnInit { } else { result = await this.analyticsSvc.getAnalyticResults(this.assessmentId, this.modelId, this.sectorId).toPromise(); } - this.setData(result); + //this.setData(result); + + + this.scoreBarData = result; + this.sampleSize = result.sampleSize; + + + } catch (error) { console.error('Error fetching analytics results', error); } diff --git a/CSETWebNg/src/app/assessment/results/score-range/score-range.component.html b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.html new file mode 100644 index 0000000000..26cbe4b0c4 --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/CSETWebNg/src/app/assessment/results/score-range/score-range.component.scss b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.scss new file mode 100644 index 0000000000..55ef941a5c --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.scss @@ -0,0 +1,3 @@ +rect, circle, line { + transition: all 0.5s ease; + } \ No newline at end of file diff --git a/CSETWebNg/src/app/assessment/results/score-range/score-range.component.ts b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.ts new file mode 100644 index 0000000000..dd573b16e6 --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/score-range/score-range.component.ts @@ -0,0 +1,78 @@ +//////////////////////////////// +// +// Copyright 2024 Battelle Energy Alliance, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////// +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-score-range', + standalone: false, + templateUrl: './score-range.component.html', + styleUrl: './score-range.component.scss' +}) +export class ScoreRangeComponent implements OnInit { + + @Input() + chartWidth: number; + + containerWidth: number; + + + + /** + * height this svg is rendered at + */ + h = 50; + + barH: number; + + @Input() + label: string; + + @Input() + min: number; + + @Input() + max: number; + + @Input() + median: number; + + @Input() + myScore: number; + + @Input() + myColor = "#0000aa"; + + rangeColor = "#87909e"; + + /** + * padding value to get things away from the left and right edge + */ + p = 10; + + + ngOnInit(): void { + this.containerWidth = this.chartWidth * 1.05; + this.barH = this.h * .1; + } +} diff --git a/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.html b/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.html new file mode 100644 index 0000000000..5d788d551a --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.html @@ -0,0 +1,63 @@ + +
+
+
+ + + + Current assessment score +
+
+ | + Vertical bar is median score for sample +
+
+ + + + + + + + + + +
+ {{cat.label}} + + +
+ + + + + + + {{ tick.value }}% + + +
+
diff --git a/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.scss b/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.ts b/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.ts new file mode 100644 index 0000000000..0997a90b78 --- /dev/null +++ b/CSETWebNg/src/app/assessment/results/score-ranges/score-ranges.component.ts @@ -0,0 +1,83 @@ +//////////////////////////////// +// +// Copyright 2024 Battelle Energy Alliance, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////// +import { Component, ElementRef, HostListener, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; + +@Component({ + selector: 'app-score-ranges', + standalone: false, + templateUrl: './score-ranges.component.html', + styleUrl: './score-ranges.component.scss' +}) +export class ScoreRangesComponent implements OnInit, OnChanges { + + @Input() + data: any; + + categories: any[]; + + // categories: any[] = [ + // { label: 'Invent', min: 10, max: 77, median: 42, myScore: 33}, + // { label: 'Prevent', min: 40, max: 95, median: 61, myScore: 83}, + // { label: 'Circumvent', min: 25, max: 54, median: 33, myScore: 50}, + // { label: 'Dryer Vent', min: 0, max: 94, median: 67, myScore: 23}, + // { label: 'Lament', min: 47, max: 62, median: 52, myScore: 47}, + // { label: 'Intent', min: 8, max: 80, median: 63, myScore: 33}, + // { label: 'Get Bent', min: 14, max: 58, median: 36, myScore: 29} + // ]; + + @Input() + chartWidth: number; + + containerWidth: number; + + + @Input() + myColor: string; + + ticks: any; + + @ViewChild('myDiv') myDiv!: ElementRef; + divWidth: number | null = null; + + + /** + * + */ + ngOnInit(): void { + this.containerWidth = this.chartWidth * 1.05; + + // build scale + this.ticks = Array.from({ length: 11 }, (_, i) => ({ + value: i * 10, + x: (i * this.chartWidth * .1) + })); + } + + /** + * + */ + ngOnChanges(changes: SimpleChanges): void { + this.categories = this.data?.categories; + } +} diff --git a/CSETWebNg/src/app/services/analytics.service.ts b/CSETWebNg/src/app/services/analytics.service.ts index 09183dcae2..ba9c136045 100644 --- a/CSETWebNg/src/app/services/analytics.service.ts +++ b/CSETWebNg/src/app/services/analytics.service.ts @@ -45,23 +45,23 @@ export class AnalyticsService { url += `§orId=${sectorId}`; } return this.http.get(url); -} + } getAnalyticsToken(username, password): any { return this.http.post( - this.analyticsUrl + 'auth/login', { "email":username, password }, this.headers + this.analyticsUrl + 'auth/login', { "email": username, password }, this.headers ); } postAnalyticsWithLogin(token): any { - + return this.http.get( - this.baseUrl + 'assessment/exportandsend?token='+token + this.baseUrl + 'assessment/exportandsend?token=' + token ); } // pingAnalyticsService(): any { - // return this.http.get(this.analyticsUrl + 'ping/GetPing'); + // return this.http.get(this.analyticsUrl + 'ping/GetPing'); // } } \ No newline at end of file diff --git a/CSETWebNg/src/assets/settings/config.json b/CSETWebNg/src/assets/settings/config.json index 3978bcfe7d..d2f48d2bbc 100644 --- a/CSETWebNg/src/assets/settings/config.json +++ b/CSETWebNg/src/assets/settings/config.json @@ -22,7 +22,7 @@ "port": "5000", "apiIdentifier": "api" }, - "csetAnalyticsUrl": "http://csetac:5210/api/analytics/maturity?", + "csetAnalyticsUrl": "http://localhost:5002/api/analytics/maturity/bars?", "csetGithubApiUrl": "https://api.github.com/repos/cisagov/cset/releases/latest", "helpContactEmail": "CSET_PMO@cisa.dhs.gov", "helpContactPhone": "", diff --git a/Database Scripts/Functions/func_MQ.func.sql b/Database Scripts/Functions/func_MQ.func.sql index 6891159cf2..c4a2f8ccc8 100644 --- a/Database Scripts/Functions/func_MQ.func.sql +++ b/Database Scripts/Functions/func_MQ.func.sql @@ -1,3 +1,4 @@ + -- ============================================= -- Author: Randy Woods -- Create date: 10-OCT-2023 @@ -34,7 +35,10 @@ RETURNS [Scope] [nvarchar](250) NULL, [Recommend_Action] [nvarchar](max) NULL, [Risk_Addressed] [nvarchar](max) NULL, - [Services] [nvarchar](max) NULL + [Services] [nvarchar](max) NULL, + [Outcome] nvarchar(max) null, + [Security_Practice] nvarchar(max) null, + [Implementation_Guides] nvarchar(max) null ) AS BEGIN diff --git a/Database Scripts/Stored Procedures/FillAll.proc.sql b/Database Scripts/Stored Procedures/FillAll.proc.sql new file mode 100644 index 0000000000..22a52687c6 --- /dev/null +++ b/Database Scripts/Stored Procedures/FillAll.proc.sql @@ -0,0 +1,19 @@ + +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE PROCEDURE [dbo].[FillAll] + -- Add the parameters for the stored procedure here + @Assessment_Id int +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + -- Insert statements for procedure here + exec FillEmptyMaturityQuestionsForAnalysis @assessment_id + exec FillEmptyQuestionsForAnalysis @assessment_id +END diff --git a/Database Scripts/Stored Procedures/FillEmptyMaturityQuestionsForModel.proc.sql b/Database Scripts/Stored Procedures/FillEmptyMaturityQuestionsForModel.proc.sql new file mode 100644 index 0000000000..e54a673f41 --- /dev/null +++ b/Database Scripts/Stored Procedures/FillEmptyMaturityQuestionsForModel.proc.sql @@ -0,0 +1,38 @@ + +-- ============================================= +-- Author: Randy Woods +-- Create date: 09/10/2024 +-- Description: Create empty data for questions that have not been filled out. +-- This version of the proc is designed for deliberately fleshing out SSG questions +-- because their relevance is not determined by AVAILABLE_MATURITY_MODELS. +-- ============================================= +CREATE PROCEDURE [dbo].[FillEmptyMaturityQuestionsForModel] + @Assessment_Id int, + @Model_Id int +AS +BEGIN + DECLARE @result int; + begin + BEGIN TRANSACTION; + EXEC @result = sp_getapplock @Resource = '[Answer]', @LockMode = 'Exclusive'; + INSERT INTO [dbo].[ANSWER] ([Question_Or_Requirement_Id],[Answer_Text],[Question_Type],[Assessment_Id]) + select mq.Mat_Question_Id,Answer_Text = 'U', Question_Type='Maturity', Assessment_Id = @Assessment_Id + from [dbo].[MATURITY_QUESTIONS] mq + where Maturity_Model_Id = @Model_Id + and Mat_Question_Id not in + (select Question_Or_Requirement_Id from [dbo].[ANSWER] + where Assessment_Id = @Assessment_Id and Maturity_Model_Id = @Model_Id) + IF @result = -3 + BEGIN + ROLLBACK TRANSACTION; + END + ELSE + BEGIN + EXEC sp_releaseapplock @Resource = '[Answer]'; + COMMIT TRANSACTION; + END + end + +END +/****** Object: StoredProcedure [dbo].[FillEmptyQuestionsForAnalysis] Script Date: 12/16/2020 11:01:33 AM ******/ +SET ANSI_NULLS ON diff --git a/Database Scripts/Stored Procedures/GetAnswerCountsForGroupings.proc.sql b/Database Scripts/Stored Procedures/GetAnswerCountsForGroupings.proc.sql new file mode 100644 index 0000000000..26b07c7f84 --- /dev/null +++ b/Database Scripts/Stored Procedures/GetAnswerCountsForGroupings.proc.sql @@ -0,0 +1,28 @@ + +-- ============================================= +-- Author: WOODRK +-- Create date: 8/29/2024 +-- Description: Generically return answer counts for all groupings in +-- an assessment's maturity model +-- ============================================= +CREATE PROCEDURE [dbo].[GetAnswerCountsForGroupings] + @assessmentId int +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + exec [FillEmptyMaturityQuestionsForAnalysis] @assessmentId + + select Title, Sequence, Grouping_Id, Parent_Id, Answer_Text, count(*) as AnsCount + from ( + select mg.Title, mg.Sequence, mg.grouping_id, mg.Parent_Id, a.Answer_Text + from answer a + left join maturity_questions mq on a.Question_Or_Requirement_Id = mq.Mat_Question_Id and a.Question_Type = 'maturity' + left join maturity_groupings mg on mq.Grouping_Id = mg.Grouping_Id + where assessment_id = @assessmentId + ) b + group by title, sequence, grouping_id, parent_id, answer_text + order by sequence, answer_text +END diff --git a/Database Scripts/Stored Procedures/GetAnswerDistribGroupings.proc.sql b/Database Scripts/Stored Procedures/GetAnswerDistribGroupings.proc.sql index 00692004db..4134e868ed 100644 --- a/Database Scripts/Stored Procedures/GetAnswerDistribGroupings.proc.sql +++ b/Database Scripts/Stored Procedures/GetAnswerDistribGroupings.proc.sql @@ -1,3 +1,4 @@ + -- ============================================= -- Author: Randy Woods -- Create date: 15 November 2022 @@ -7,14 +8,22 @@ -- specific grouping? g.Parent_Id = X -- ============================================= CREATE PROCEDURE [dbo].[GetAnswerDistribGroupings] - @assessmentId int + @assessmentId int, + @modelId int = null AS BEGIN SET NOCOUNT ON; exec FillEmptyMaturityQuestionsForAnalysis @assessmentId + -- get the main model ID for the assessment declare @maturityModelId int = (select model_id from AVAILABLE_MATURITY_MODELS where Assessment_Id = @assessmentId) + -- if the caller specified a model ID, use that instead + if @modelId is not null + BEGIN + select @maturityModelId = @modelId + END + select [grouping_id], [title], [answer_text], count(answer_text) as [answer_count] from ( select g.grouping_id, g.title, g.sequence, a.Answer_Text diff --git a/Database Scripts/Stored Procedures/GetMaturityComparisonBestToWorst.proc.sql b/Database Scripts/Stored Procedures/GetMaturityComparisonBestToWorst.proc.sql new file mode 100644 index 0000000000..e70e2e6317 --- /dev/null +++ b/Database Scripts/Stored Procedures/GetMaturityComparisonBestToWorst.proc.sql @@ -0,0 +1,58 @@ + +-- ============================================= +-- Author: WOODRK +-- Create date: 8/29/2024 +-- Description: +-- ============================================= +CREATE PROCEDURE [dbo].[GetMaturityComparisonBestToWorst] +@assessment_id int +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + + SELECT Assessment_Id, + AssessmentName = Alias, + Name = Title, + * + --AlternateCount = [I], + --AlternateValue = Round(((cast(([I]) as float)/isnull(nullif(Total,0),1)))*100,2), + --NaCount = [S], + --NaValue = Round(((cast(([S]) as float)/isnull(nullif(Total,0),1)))*100,2), + --NoCount = [N], + --NoValue = Round(((cast(([N]) as float)/isnull(nullif(Total,0),1)))*100,2), + --TotalCount = Total, + --TotalValue = Total, + --UnansweredCount = [U], + --UnansweredValue = Round(cast([U] as float)/Total*100,2), + --YesCount = [Y], + --YesValue = Round((cast(([Y]) as float)/isnull(nullif(Total,0),1))*100,2), + --Value = Round(((cast(([Y]+ isnull([I],0)) as float)/isnull(nullif((Total-[S]),0),1)))*100,2) + FROM + ( + select b.Assessment_Id, f.Alias, b.Title, b.Answer_Text, isnull(c.Value,0) as Value, + Total = sum(c.Value) over(partition by b.Assessment_Id, b.Title) + from + (select distinct a.[Assessment_Id], g.Title, l.answer_Text + from answer_lookup l, (select * from Answer_Maturity where assessment_id = @assessment_id) a + join [MATURITY_QUESTIONS] q on a.Question_Or_Requirement_Id = q.Mat_Question_Id + join MATURITY_GROUPINGS g on q.Grouping_Id = g.Grouping_Id + ) b left join + (select a.Assessment_Id, g.Title, a.Answer_Text, count(a.answer_text) as Value + from (select * from Answer_Maturity where assessment_id = @assessment_id) a + join [MATURITY_QUESTIONS] q on a.Question_Or_Requirement_Id = q.Mat_Question_Id + join MATURITY_GROUPINGS g on q.Grouping_Id = g.Grouping_Id + group by Assessment_Id, g.Title, a.Answer_Text) c + on b.Assessment_Id = c.Assessment_Id and b.Title = c.Title and b.Answer_Text = c.Answer_Text + join ASSESSMENTS f on b.Assessment_Id = f.Assessment_Id + ) p + + + PIVOT + ( + sum(value) + FOR answer_text IN ( [Y],[I],[S],[N],[U] ) + ) AS pvt +END diff --git a/Database Scripts/Stored Procedures/Get_Cie_Merge_Conflicts.proc.sql b/Database Scripts/Stored Procedures/Get_Cie_Merge_Conflicts.proc.sql new file mode 100644 index 0000000000..0d4d1b533a --- /dev/null +++ b/Database Scripts/Stored Procedures/Get_Cie_Merge_Conflicts.proc.sql @@ -0,0 +1,231 @@ +-- ============================================= +-- Author: Matt Winston +-- Create date: 4/12/24 +-- Description: Gets the merge conflicts for CIE assessments +-- ============================================= +CREATE PROCEDURE [dbo].[Get_Cie_Merge_Conflicts] + -- At least 2 assessments are required to merge + @id1 int, @id2 int, + + -- Up to 10 total assessments are allowed + @id3 int = NULL, @id4 int = NULL, @id5 int = NULL, @id6 int = NULL, + @id7 int = NULL, @id8 int = NULL, @id9 int = NULL, @id10 int = NULL + +AS +BEGIN + SET NOCOUNT ON; + +EXEC FillEmptyMaturityQuestionsForAnalysis @id1 +EXEC FillEmptyMaturityQuestionsForAnalysis @id2 +EXEC FillEmptyMaturityQuestionsForAnalysis @id3 +EXEC FillEmptyMaturityQuestionsForAnalysis @id4 +EXEC FillEmptyMaturityQuestionsForAnalysis @id5 +EXEC FillEmptyMaturityQuestionsForAnalysis @id6 +EXEC FillEmptyMaturityQuestionsForAnalysis @id7 +EXEC FillEmptyMaturityQuestionsForAnalysis @id8 +EXEC FillEmptyMaturityQuestionsForAnalysis @id9 +EXEC FillEmptyMaturityQuestionsForAnalysis @id10 + +SELECT + (SELECT Question_Text FROM MATURITY_QUESTIONS WHERE Mat_Question_Id = a.Question_Or_Requirement_Id) as Question_Text, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id1) as Assessment_Name1, + (SELECT Facility_Name FROM INFORMATION WHERE Id = @id1) as Facility, + a.Assessment_Id as Assessment_id1, + a.Question_Or_Requirement_Id as Question_Or_Requirement_Id1, + a.Answer_Text as Answer_Text1, + a.FeedBack as Feedback1, + a.Comment as Comment1, + a.Free_Response_Answer as Free_Response_Answer1, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id2) as Assessment_Name2, + b.Assessment_Id as Assessment_id2, + b.Question_Or_Requirement_Id as Question_Or_Requirement_Id2, + b.Answer_Text as Answer_Text2, + b.FeedBack as Feedback2, + b.Comment as Comment2, + b.Free_Response_Answer as Free_Response_Answer2, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id3) as Assessment_Name3, + c.Assessment_Id as Assessment_id3, + c.Question_Or_Requirement_Id as Question_Or_Requirement_Id3, + c.Answer_Text as Answer_Text3, + c.FeedBack as Feedback3, + c.Comment as Comment3, + c.Free_Response_Answer as Free_Response_Answer3, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id4) as Assessment_Name4, + d.Assessment_Id as Assessment_id4, + d.Question_Or_Requirement_Id as Question_Or_Requirement_Id4, + d.Answer_Text as Answer_Text4, + d.FeedBack as Feedback4, + d.Comment as Comment4, + d.Free_Response_Answer as Free_Response_Answer4, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id5) as Assessment_Name5, + e.Assessment_Id as Assessment_id5, + e.Question_Or_Requirement_Id as Question_Or_Requirement_Id5, + e.Answer_Text as Answer_Text5, + e.FeedBack as Feedback5, + e.Comment as Comment5, + e.Free_Response_Answer as Free_Response_Answer5, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id6) as Assessment_Name6, + f.Assessment_Id as Assessment_id6, + f.Question_Or_Requirement_Id as Question_Or_Requirement_Id6, + f.Answer_Text as Answer_Text6, + f.FeedBack as Feedback6, + f.Comment as Comment6, + f.Free_Response_Answer as Free_Response_Answer6, + + g.Assessment_Id as Assessment_id7, + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id7) as Assessment_Name7, + g.Question_Or_Requirement_Id as Question_Or_Requirement_Id7, + g.Answer_Text as Answer_Text7, + g.FeedBack as Feedback7, + g.Comment as Comment7, + g.Free_Response_Answer as Free_Response_Answer7, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id8) as Assessment_Name8, + h.Assessment_Id as Assessment_id8, + h.Question_Or_Requirement_Id as Question_Or_Requirement_Id8, + h.Answer_Text as Answer_Text8, + h.FeedBack as Feedback8, + h.Comment as Comment8, + h.Free_Response_Answer as Free_Response_Answer8, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id9) as Assessment_Name9, + i.Assessment_Id as Assessment_id9, + i.Question_Or_Requirement_Id as Question_Or_Requirement_Id9, + i.Answer_Text as Answer_Text9, + i.FeedBack as Feedback9, + i.Comment as Comment9, + i.Free_Response_Answer as Free_Response_Answer9, + + (SELECT Assessment_Name FROM INFORMATION WHERE Id = @id10) as Assessment_Name10, + j.Assessment_Id as Assessment_id10, + j.Question_Or_Requirement_Id as Question_Or_Requirement_Id10, + j.Answer_Text as Answer_Text10, + j.FeedBack as Feedback10, + j.Comment as Comment10, + j.Free_Response_Answer as Free_Response_Answer10 + + +FROM (SELECT * FROM ANSWER WHERE Assessment_Id = @id1) a + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id2) b +ON (a.Question_Or_Requirement_Id = b.Question_Or_Requirement_Id) AND (a.Question_Type = b.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id3) c +ON (a.Question_Or_Requirement_Id = c.Question_Or_Requirement_Id) AND (a.Question_Type = c.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id4) d +ON (a.Question_Or_Requirement_Id = d.Question_Or_Requirement_Id) AND (a.Question_Type = d.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id5) e +ON (a.Question_Or_Requirement_Id = e.Question_Or_Requirement_Id) AND (a.Question_Type = e.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id6) f +ON (a.Question_Or_Requirement_Id = f.Question_Or_Requirement_Id) AND (a.Question_Type = f.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id7) g +ON (a.Question_Or_Requirement_Id = g.Question_Or_Requirement_Id) AND (a.Question_Type = g.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id8) h +ON (a.Question_Or_Requirement_Id = h.Question_Or_Requirement_Id) AND (a.Question_Type = h.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id9) i +ON (a.Question_Or_Requirement_Id = i.Question_Or_Requirement_Id) AND (a.Question_Type = i.Question_Type) + +FULL OUTER JOIN (SELECT * FROM ANSWER WHERE Assessment_Id = @id10) j +ON (a.Question_Or_Requirement_Id = j.Question_Or_Requirement_Id) AND (a.Question_Type = j.Question_Type) + +WHERE + -- Compare Exam 1 (a) to all other exams being merged + ( + (a.Answer_Text != b.Answer_Text) + AND ((a.Answer_Text = 'NA') OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is not null)) + AND ((b.Answer_Text = 'NA') OR (b.Answer_Text = 'U' AND b.Free_Response_Answer is not null)) + ) OR + ((a.Answer_Text != c.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (c.Answer_Text = 'NA' OR (c.Answer_Text = 'U' AND c.Free_Response_Answer is null))) OR + ((a.Answer_Text != d.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (d.Answer_Text = 'NA' OR (d.Answer_Text = 'U' AND d.Free_Response_Answer is null))) OR + ((a.Answer_Text != e.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (e.Answer_Text = 'NA' OR (e.Answer_Text = 'U' AND e.Free_Response_Answer is null))) OR + ((a.Answer_Text != f.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (f.Answer_Text = 'NA' OR (f.Answer_Text = 'U' AND f.Free_Response_Answer is null))) OR + ((a.Answer_Text != g.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (g.Answer_Text = 'NA' OR (g.Answer_Text = 'U' AND g.Free_Response_Answer is null))) OR + ((a.Answer_Text != h.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (h.Answer_Text = 'NA' OR (h.Answer_Text = 'U' AND h.Free_Response_Answer is null))) OR + ((a.Answer_Text != i.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (i.Answer_Text = 'NA' OR (i.Answer_Text = 'U' AND i.Free_Response_Answer is null))) OR + ((a.Answer_Text != j.Answer_Text) AND (a.Answer_Text = 'NA' OR (a.Answer_Text = 'U' AND a.Free_Response_Answer is null)) AND (j.Answer_Text = 'NA' OR (j.Answer_Text = 'U' AND j.Free_Response_Answer is null))) OR + + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (c.Answer_Text = 'U' AND c.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (d.Answer_Text = 'U' AND d.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (e.Answer_Text = 'U' AND e.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (f.Answer_Text = 'U' AND f.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (g.Answer_Text = 'U' AND g.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (h.Answer_Text = 'U' AND h.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (i.Answer_Text = 'U' AND i.Free_Response_Answer is NULL))) OR + --((a.Answer_Text != b.Answer_Text) AND ((a.Answer_Text = 'U' AND a.Free_Response_Answer is NULL) OR (j.Answer_Text = 'U' AND j.Free_Response_Answer is NULL))) OR + + --((a.Answer_Text != b.Answer_Text) OR (a.Answer_Text != b.Answer_Text)) OR + --((a.Answer_Text != c.Answer_Text) OR (a.Answer_Text != c.Answer_Text)) OR + --((a.Answer_Text != d.Answer_Text) OR (a.Answer_Text != d.Answer_Text)) OR + --((a.Answer_Text != e.Answer_Text) OR (a.Answer_Text != e.Answer_Text)) OR + --((a.Answer_Text != f.Answer_Text) OR (a.Answer_Text != f.Answer_Text)) OR + --((a.Answer_Text != g.Answer_Text) OR (a.Answer_Text != g.Answer_Text)) OR + --((a.Answer_Text != h.Answer_Text) OR (a.Answer_Text != h.Answer_Text)) OR + --((a.Answer_Text != i.Answer_Text) OR (a.Answer_Text != i.Answer_Text)) OR + --((a.Answer_Text != j.Answer_Text) OR (a.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 2 (b) to all other exams being merged + ((b.Answer_Text != c.Answer_Text) OR (b.Answer_Text != c.Answer_Text)) OR + ((b.Answer_Text != d.Answer_Text) OR (b.Answer_Text != d.Answer_Text)) OR + ((b.Answer_Text != e.Answer_Text) OR (b.Answer_Text != e.Answer_Text)) OR + ((b.Answer_Text != f.Answer_Text) OR (b.Answer_Text != f.Answer_Text)) OR + ((b.Answer_Text != g.Answer_Text) OR (b.Answer_Text != g.Answer_Text)) OR + ((b.Answer_Text != h.Answer_Text) OR (b.Answer_Text != h.Answer_Text)) OR + ((b.Answer_Text != i.Answer_Text) OR (b.Answer_Text != i.Answer_Text)) OR + ((b.Answer_Text != j.Answer_Text) OR (b.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 3 (c) + ((c.Answer_Text != d.Answer_Text) OR (c.Answer_Text != d.Answer_Text)) OR + ((c.Answer_Text != e.Answer_Text) OR (c.Answer_Text != e.Answer_Text)) OR + ((c.Answer_Text != f.Answer_Text) OR (c.Answer_Text != f.Answer_Text)) OR + ((c.Answer_Text != g.Answer_Text) OR (c.Answer_Text != g.Answer_Text)) OR + ((c.Answer_Text != h.Answer_Text) OR (c.Answer_Text != h.Answer_Text)) OR + ((c.Answer_Text != i.Answer_Text) OR (c.Answer_Text != i.Answer_Text)) OR + ((c.Answer_Text != j.Answer_Text) OR (c.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 4 (d) + ((d.Answer_Text != e.Answer_Text) OR (d.Answer_Text != e.Answer_Text)) OR + ((d.Answer_Text != f.Answer_Text) OR (d.Answer_Text != f.Answer_Text)) OR + ((d.Answer_Text != g.Answer_Text) OR (d.Answer_Text != g.Answer_Text)) OR + ((d.Answer_Text != h.Answer_Text) OR (d.Answer_Text != h.Answer_Text)) OR + ((d.Answer_Text != i.Answer_Text) OR (d.Answer_Text != i.Answer_Text)) OR + ((d.Answer_Text != j.Answer_Text) OR (d.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 5 (e) + ((e.Answer_Text != f.Answer_Text) OR (e.Answer_Text != f.Answer_Text)) OR + ((e.Answer_Text != g.Answer_Text) OR (e.Answer_Text != g.Answer_Text)) OR + ((e.Answer_Text != h.Answer_Text) OR (e.Answer_Text != h.Answer_Text)) OR + ((e.Answer_Text != i.Answer_Text) OR (e.Answer_Text != i.Answer_Text)) OR + ((e.Answer_Text != j.Answer_Text) OR (e.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 6 (f) + ((f.Answer_Text != g.Answer_Text) OR (f.Answer_Text != g.Answer_Text)) OR + ((f.Answer_Text != h.Answer_Text) OR (f.Answer_Text != h.Answer_Text)) OR + ((f.Answer_Text != i.Answer_Text) OR (f.Answer_Text != i.Answer_Text)) OR + ((f.Answer_Text != j.Answer_Text) OR (f.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 7 (g) + ((g.Answer_Text != h.Answer_Text) OR (g.Answer_Text != h.Answer_Text)) OR + ((g.Answer_Text != i.Answer_Text) OR (g.Answer_Text != i.Answer_Text)) OR + ((g.Answer_Text != j.Answer_Text) OR (g.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 8 (h) + ((h.Answer_Text != i.Answer_Text) OR (h.Answer_Text != i.Answer_Text)) OR + ((h.Answer_Text != j.Answer_Text) OR (h.Answer_Text != j.Answer_Text)) OR + + -- Compare Exam 9 (i) + ((i.Answer_Text != j.Answer_Text) OR (i.Answer_Text != j.Answer_Text)) + + +END diff --git a/Database Scripts/Stored Procedures/analytics_Compute_MaturityAll.proc.sql b/Database Scripts/Stored Procedures/analytics_Compute_MaturityAll.proc.sql index 800e0ee323..0d3c473d27 100644 --- a/Database Scripts/Stored Procedures/analytics_Compute_MaturityAll.proc.sql +++ b/Database Scripts/Stored Procedures/analytics_Compute_MaturityAll.proc.sql @@ -1,13 +1,20 @@ + -- ============================================= -- Author: Luke G, Lilly, Barry -- Create date: 4-6-2022 -- Description: 18 stored procedures for analytics. -- This procedure returns the AVG, MIN, MAX, MEDIAN Question Group Heading for the Question_Type 'Maturity' for all sectory and industry. +-- +-- Modification date: 12-NOV-2024 +-- Author: Randy +-- Description: Made sector and industry parameters optional, to cast a wider net. +-- Also added consideration for a sector and industry stored in DETAILS_DEMOGRAPHICS. +-- Also the groupings are sorted in their sequence order, rather than alphabetically. -- ============================================= CREATE PROCEDURE [dbo].[analytics_Compute_MaturityAll] @maturity_model_id int, -@sector_id int, -@industry_id int +@sector_id int = NULL, +@industry_id int = NULL AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from @@ -22,6 +29,7 @@ BEGIN IF OBJECT_ID('tempdb..#Temp2') IS NOT NULL DROP TABLE #Temp2 IF OBJECT_ID('tempdb..#Temp3') IS NOT NULL DROP TABLE #Temp3 + --step 1 get the base data select a.Assessment_Id,Question_Group, Answer_Text, isnull(COUNT(a.answer_text),0) Answer_Count, sum(isnull(count(answer_text),0)) OVER(PARTITION BY a.assessment_id,question_group) AS Total @@ -31,30 +39,38 @@ select a.Assessment_Id,Question_Group, Answer_Text, isnull(COUNT(a.answer_text), join MATURITY_QUESTIONS q on a.Question_Or_Requirement_Id = q.Mat_Question_Id join ANALYTICS_MATURITY_GROUPINGS g on q.Mat_Question_Id=g.Maturity_Question_Id left join demographics d on a.assessment_id = d.assessment_id + left join details_demographics ddsector on a.Assessment_Id = ddsector.Assessment_Id and ddsector.DataItemName = 'SECTOR' + left join details_demographics ddsubsector on a.Assessment_Id = ddsubsector.Assessment_Id and ddsubsector.DataItemName = 'SUBSECTOR' where a.question_type = 'Maturity' and q.Maturity_Model_Id=@maturity_model_id and g.Maturity_Model_Id=@maturity_model_id - and nullif(@sector_id,sectorid) is null - and nullif(@industry_id,industryid) is null + and (nullif(@sector_id, sectorid) is null or nullif(@sector_id, ddsector.IntValue) is null) + and (nullif(@industry_id,industryid) is null or nullif(@industry_id, ddsubsector.IntValue) is null) group by a.assessment_id, Question_Group, Answer_Text + + --step 2 handle the cases where we have all yes, all no, and mixed --get the yes and mixed case select * into #temp2 from #temp where answer_text='Y' --get the all no case insert #temp2 select assessment_id,QUESTION_GROUP,Answer_Text='Y',Answer_Count,total, [percentage]=0 from #temp where Answer_Text = 'N' and Answer_Count=total + + --step 3 calculate the min,max,avg - select G1.Question_Group as Question_Group_Heading, min(isnull([percentage],0)) [minimum],max(isnull([percentage],0)) [maximum],avg(isnull([percentage],0)) [average] + select G1.Question_Group as Question_Group_Heading, g1.Global_Sequence, min(isnull([percentage],0)) [minimum],max(isnull([percentage],0)) [maximum],avg(isnull([percentage],0)) [average] into #temp3 from ( - select distinct Question_Group from ANALYTICS_MATURITY_GROUPINGS where Maturity_Model_Id = @maturity_model_id + select distinct Question_Group, global_sequence from ANALYTICS_MATURITY_GROUPINGS where Maturity_Model_Id = @maturity_model_id ) G1 LEFT OUTER JOIN #temp2 G2 ON G1.Question_Group = G2.Question_Group - group by G1.Question_Group + group by G1.Question_Group, global_sequence + + --step 4 add median - select a.Question_Group_Heading,cast(a.minimum as float) as minimum,cast(a.maximum as float) as maximum,cast(a.average as float) as average,isnull(b.median,0) as median from + select a.Question_Group_Heading, cast(a.minimum as float) as minimum,cast(a.maximum as float) as maximum,cast(a.average as float) as average,isnull(b.median,0) as median from #temp3 a left join ( select distinct Question_Group as Question_Group_Heading ,isnull(PERCENTILE_disc(0.5) WITHIN GROUP (ORDER BY [Percentage]) OVER (PARTITION BY question_group),0) AS median from #temp2) b on a.Question_Group_Heading=b.Question_Group_Heading - order by a.Question_Group_Heading + order by a.Global_Sequence end diff --git a/Database Scripts/Stored Procedures/analytics_Compute_MaturitySampleSize.proc.sql b/Database Scripts/Stored Procedures/analytics_Compute_MaturitySampleSize.proc.sql new file mode 100644 index 0000000000..9cc6d55fc1 --- /dev/null +++ b/Database Scripts/Stored Procedures/analytics_Compute_MaturitySampleSize.proc.sql @@ -0,0 +1,33 @@ + +-- ============================================= +-- Author: Mostafa, Randy +-- Create date: 11-12-2024 +-- Description: Return Assessment count based on maturity model id and optional sector_id +-- ============================================= +CREATE PROCEDURE [dbo].[analytics_Compute_MaturitySampleSize] +@maturity_model_id int, +@sector_id int = NULL +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + --test base case where there is no data in db at all +SELECT SectorId, COUNT(Assessment_Id) AS AssessmentCount +FROM ( + SELECT dd.Assessment_Id, dd.IntValue as SectorId FROM DETAILS_DEMOGRAPHICS dd + JOIN AVAILABLE_MATURITY_MODELS amm + ON dd.Assessment_Id = amm.Assessment_Id + WHERE amm.model_id = @maturity_model_id + AND (@sector_id IS NULL OR (DataItemName = 'SECTOR' AND dd.IntValue = @sector_id)) + + UNION + + SELECT d.Assessment_Id, d.SectorId FROM DEMOGRAPHICS d + JOIN AVAILABLE_MATURITY_MODELS amm + ON d.Assessment_Id = amm.Assessment_Id + WHERE amm.model_id = @maturity_model_id + AND (@sector_id IS NULL OR d.SectorId = @sector_id) +) AS CombinedResult +GROUP BY SectorId; +END diff --git a/Database Scripts/Stored Procedures/analytics_SequenceMaturityGroups.proc.sql b/Database Scripts/Stored Procedures/analytics_SequenceMaturityGroups.proc.sql new file mode 100644 index 0000000000..cdbc0e768f --- /dev/null +++ b/Database Scripts/Stored Procedures/analytics_SequenceMaturityGroups.proc.sql @@ -0,0 +1,54 @@ + +-- ============================================= +-- Author: Randy +-- Create date: 19-NOV-2024 +-- Description: Determine a "global" sequence for all maturity groupings in context. +-- This is needed because a child grouping may start its sequencing at 1, which would +-- put it ahead of its parent with a simple sort by sequence. +-- +-- All groupings with their global sequence are stored in [MATURITY_GLOBAL_SEQUENCES]. +-- +-- It can currently handle a grouping structure depth of 4. This can be expanded in the future if needed. +-- ============================================= +CREATE PROCEDURE [dbo].[analytics_SequenceMaturityGroups] +AS +BEGIN + SET NOCOUNT ON; + + DELETE FROM [MATURITY_GLOBAL_SEQUENCES] + + + DECLARE seq_cursor CURSOR FOR + select g1.Maturity_Model_Id, + ROW_NUMBER() over (order by (select null)) as global_sequence, + g1.grouping_Id as [g1], g2.grouping_id as [g2], g3.grouping_id as [g3], g4.grouping_id as [g4] + from [MATURITY_GROUPINGS] g1 + left join [MATURITY_GROUPINGS] g2 on g2.parent_id = g1.grouping_id + left join [MATURITY_GROUPINGS] g3 on g3.parent_id = g2.grouping_id + left join [MATURITY_GROUPINGS] g4 on g4.parent_id = g3.grouping_id + where g1.parent_id is null + and (g1.Maturity_Model_Id = g2.Maturity_Model_Id or g2.Maturity_Model_Id is null) + and (g2.Maturity_Model_Id = g3.Maturity_Model_Id or g3.Maturity_Model_Id is null) + and (g3.Maturity_Model_Id = g4.Maturity_Model_Id or g4.Maturity_Model_Id is null) + order by g1.maturity_model_id, g1.sequence, g2.sequence, g3.sequence, g4.sequence; + + + DECLARE @Maturity_Model_Id int, @global_sequence int, @g1 int, @g2 int, @g3 int, @g4 int; + + OPEN seq_cursor; + + FETCH NEXT FROM seq_cursor INTO @Maturity_Model_Id, @global_sequence, @g1, @g2, @g3, @g4 + + WHILE @@FETCH_STATUS = 0 + BEGIN + INSERT INTO [MATURITY_GLOBAL_SEQUENCES]( + [Maturity_Model_Id], [global_sequence], [g1], [g2], [g3], [g4]) + values (@Maturity_Model_Id, @global_sequence, @g1, @g2, @g3, @g4) + + FETCH NEXT FROM seq_cursor into @Maturity_Model_Id, @global_sequence, @g1, @g2, @g3, @g4 + END + + CLOSE seq_cursor; + DEALLOCATE seq_cursor; +END + diff --git a/Database Scripts/Stored Procedures/analytics_compute_single_averages_maturity.proc.sql b/Database Scripts/Stored Procedures/analytics_compute_single_averages_maturity.proc.sql index 1278bf611a..b9a0848fd2 100644 --- a/Database Scripts/Stored Procedures/analytics_compute_single_averages_maturity.proc.sql +++ b/Database Scripts/Stored Procedures/analytics_compute_single_averages_maturity.proc.sql @@ -1,3 +1,4 @@ + -- ============================================= -- Author: Barry -- Create date: 4/6/2022 @@ -16,13 +17,13 @@ BEGIN from ( select distinct Assessment_Id= @assessment_id, - Question_Group as Title, Answer_Text = 'Y' + Question_Group as Title, Global_Sequence, Answer_Text = 'Y' from ANALYTICS_MATURITY_GROUPINGS where Maturity_Model_Id= @maturity_model_id ) G1 LEFT OUTER JOIN ( select - Question_Group as Title,answer_text, count(answer_text) answer_count, + Question_Group as Title, answer_text, count(answer_text) answer_count, sum(count(answer_text)) OVER(PARTITION BY Question_Group) AS Total, cast(IsNull(Round((cast((COUNT(a.answer_text)) as float)/(isnull(nullif(sum(count(answer_text)) OVER(PARTITION BY Question_Group),0),1)))*100,0),0) as int) as [Percentage] from Analytics_Answers a @@ -31,7 +32,5 @@ BEGIN group by Question_Group, Answer_Text ) G2 ON G1.Title = G2.Title AND G1.Answer_Text = G2.Answer_Text where g1.answer_text = 'Y' - order by Title + order by Global_Sequence END - ---update ANSWER set Answer_Text = 'NA' where Assessment_Id = 9 diff --git a/Database Scripts/Stored Procedures/analytics_setup_maturity_groupings.proc.sql b/Database Scripts/Stored Procedures/analytics_setup_maturity_groupings.proc.sql index 461a3f6daf..81afac0a98 100644 --- a/Database Scripts/Stored Procedures/analytics_setup_maturity_groupings.proc.sql +++ b/Database Scripts/Stored Procedures/analytics_setup_maturity_groupings.proc.sql @@ -1,3 +1,4 @@ + -- ============================================= -- Author: hansbk -- Create date: 3/31/2022 @@ -10,6 +11,8 @@ CREATE PROCEDURE [dbo].[analytics_setup_maturity_groupings] AS BEGIN SET NOCOUNT ON; + + /* clean out the table and rebuild it go through the maturity models table and for each one select the appropriate level @@ -18,6 +21,7 @@ BEGIN */ delete from analytics_maturity_Groupings + declare @maturity_model_id int, @analytics_rollup_level int @@ -30,12 +34,16 @@ OPEN maturity_cursor FETCH NEXT FROM maturity_cursor INTO @maturity_model_id, @analytics_rollup_level + WHILE @@FETCH_STATUS = 0 BEGIN - INSERT INTO [dbo].[ANALYTICS_MATURITY_GROUPINGS] ([Maturity_Model_Id],[Maturity_Question_Id],[Question_Group]) - select distinct g.Maturity_Model_Id,q.Mat_Question_Id, title - from MATURITY_GROUPINGS g join MATURITY_QUESTIONS q on g.Grouping_Id=q.Grouping_Id - where q.Maturity_Model_Id = @maturity_model_id and g.Maturity_Model_Id=@maturity_model_id + INSERT INTO [dbo].[ANALYTICS_MATURITY_GROUPINGS] + ([Maturity_Model_Id], [Maturity_Question_Id], [Question_Group], [Group_Id], [Group_Sequence], [Global_Sequence]) + + select distinct q.Maturity_Model_Id, q.Mat_Question_Id, title, g.Grouping_Id as [Group_Id], g.sequence, null + from [MATURITY_GROUPINGS] g + join [MATURITY_QUESTIONS] q on g.Grouping_Id = q.Grouping_Id + where g.Maturity_Model_Id = @maturity_model_id and g.Maturity_Model_Id=@maturity_model_id and Group_Level = @analytics_rollup_level FETCH NEXT FROM maturity_cursor @@ -44,5 +52,15 @@ END CLOSE maturity_cursor; DEALLOCATE maturity_cursor; + +-- define a 'global' sequence for all groupings in all models +EXEC [analytics_SequenceMaturityGroups] + +-- include the global sequence on the ANALYTICS_MATURITY_GROUPINGS work table +update ANALYTICS_MATURITY_GROUPINGS +set Global_Sequence = ( + select top 1 global_sequence from [MATURITY_GLOBAL_SEQUENCES] + where group_id = g1 or group_id = g2 or group_id = g3 or group_id = g4 +) END diff --git a/Database Scripts/Stored Procedures/spEXECsp_RECOMPILE.proc.sql b/Database Scripts/Stored Procedures/spEXECsp_RECOMPILE.proc.sql new file mode 100644 index 0000000000..0c85f23aed --- /dev/null +++ b/Database Scripts/Stored Procedures/spEXECsp_RECOMPILE.proc.sql @@ -0,0 +1,45 @@ +CREATE PROCEDURE [dbo].[spEXECsp_RECOMPILE] AS + +SET NOCOUNT ON + +-- 1 - Declaration statements for all variables +DECLARE @TableName varchar(128) +DECLARE @OwnerName varchar(128) +DECLARE @CMD1 varchar(8000) +DECLARE @TableListLoop int +DECLARE @TableListTable table +(UIDTableList int IDENTITY (1,1), +OwnerName varchar(128), +TableName varchar(128)) + +-- 2 - Outer loop for populating the database names +INSERT INTO @TableListTable(OwnerName, TableName) +SELECT u.[Name], o.[Name] +FROM sys.objects o +INNER JOIN sys.schemas u + ON o.schema_id = u.schema_id +WHERE o.Type = 'V' +ORDER BY o.[Name] + + + +-- 3 - Determine the highest UIDDatabaseList to loop through the records +SELECT @TableListLoop = MAX(UIDTableList) FROM @TableListTable + +-- 4 - While condition for looping through the database records +WHILE @TableListLoop > 0 + BEGIN + + -- 5 - Set the @DatabaseName parameter + SELECT @TableName = TableName, + @OwnerName = OwnerName + FROM @TableListTable + WHERE UIDTableList = @TableListLoop + + -- 6 - String together the final backup command + SELECT @CMD1 = 'EXEC sp_recompile ' + '[' + @OwnerName + '.' + @TableName + ']' + char(13) + + -- 7 - Execute the final string to complete the backups + SELECT @CMD1 + --EXEC (@CMD1) + end diff --git a/Database Scripts/Stored Procedures/usp_GetMaturityAnswerTotals.proc.sql b/Database Scripts/Stored Procedures/usp_GetMaturityAnswerTotals.proc.sql new file mode 100644 index 0000000000..c3ca50de9d --- /dev/null +++ b/Database Scripts/Stored Procedures/usp_GetMaturityAnswerTotals.proc.sql @@ -0,0 +1,40 @@ + +-- ============================================= +-- Author: Randy Woods +-- Create date: 27 AUG 2024 +-- Description: Flexible answer count/percentages for maturity models that have their own +-- answer options other than Y, N, NA, etc. +-- +-- A model ID can be supplied (for querying SSG answers), or if not supplied, +-- the assigned model ID is used. +-- ============================================= +CREATE PROCEDURE [dbo].[usp_GetMaturityAnswerTotals] + @assessment_id int, + @model_id int = null +AS +BEGIN + -- Get the assigned maturity model ID if a model was not specified in the arguments + if @model_id is null begin + set @model_id = (select model_id from AVAILABLE_MATURITY_MODELS where assessment_id = @assessment_id) + end + + -- Create all missing answer rows + exec [FillEmptyMaturityQuestionsForAnalysis] @assessment_id + + + select * + into #answers + from answer + where question_type = 'maturity' and Question_Or_Requirement_Id in (select mat_question_id from maturity_questions where maturity_model_id = @model_id) + + select [Answer_Text], + isnull(qc, 0) as [QC], + isnull(m.Total, 0) as [Total], + isnull(cast(IsNull(Round((cast((qc) as float)/(isnull(nullif(Total,0),1)))*100,0),0) as int), 0) as [Percent] + from ( + SELECT a.Answer_Text, count(a.question_or_requirement_id) qc, SUM(count(a.question_or_requirement_id)) OVER() AS [Total] + FROM #answers a + where a.Assessment_Id = @assessment_id + group by a.Answer_Text + ) m +END diff --git a/Database Scripts/Stored Procedures/usp_GetQuestions.proc.sql b/Database Scripts/Stored Procedures/usp_GetQuestions.proc.sql new file mode 100644 index 0000000000..a75634a782 --- /dev/null +++ b/Database Scripts/Stored Procedures/usp_GetQuestions.proc.sql @@ -0,0 +1,74 @@ +-- ============================================= +-- Author: Barry Hansen +-- Create date: 10/8/2024 +-- Description: Ranked Questions +-- ============================================= +CREATE PROCEDURE [dbo].[usp_GetQuestions] +@assessment_id INT +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + EXECUTE [dbo].[FillEmptyQuestionsForAnalysis] @Assessment_Id + -- get the application mode + declare @applicationMode nvarchar(50) + exec dbo.GetApplicationModeDefault @assessment_id, @ApplicationMode output + -- get currently selected sets + IF OBJECT_ID('tempdb..#mySets') IS NOT NULL DROP TABLE #mySets + select set_name into #mySets from AVAILABLE_STANDARDS where Assessment_Id = @assessment_Id and Selected = 1 + + if(@ApplicationMode = 'Questions Based') + begin + SELECT Short_Name as ShortName, + Question_Group_Heading as [Category], + Simple_Question as [QuestionText], + c.Question_Id as [QuestionId], + null as [RequirementId], + a.Answer_ID as [AnswerID], + Answer_Text as [AnswerText], + c.Universal_Sal_Level as [Level], + CONVERT(varchar(10), a.Question_Number) as [QuestionRef], + Question_Group_Heading + ' # ' + CONVERT(varchar(10), a.Question_Number) as CategoryAndNumber, + a.Question_Or_Requirement_Id as [QuestionOrRequirementID] + FROM Answer_Questions a + join NEW_QUESTION c on a.Question_Or_Requirement_Id = c.Question_Id + join vQuestion_Headings h on c.Heading_Pair_Id = h.heading_pair_Id + join ( + select distinct s.question_id, ns.Short_Name from NEW_QUESTION_SETS s + join AVAILABLE_STANDARDS v on s.Set_Name = v.Set_Name + join SETS ns on s.Set_Name = ns.Set_Name + join NEW_QUESTION_LEVELS l on s.New_Question_Set_Id = l.New_Question_Set_Id + join STANDARD_SELECTION ss on v.Assessment_Id = ss.Assessment_Id + join UNIVERSAL_SAL_LEVEL ul on ss.Selected_Sal_Level = ul.Full_Name_Sal + where v.Selected = 1 and v.Assessment_Id = @assessment_id and l.Universal_Sal_Level = ul.Universal_Sal_Level + ) s on c.Question_Id = s.Question_Id + where a.Assessment_Id = @assessment_id + order by ShortName,Question_Group_Heading,Question_Number + end + else + begin + SELECT Short_Name ShortName, + Standard_Category as [Category], + Requirement_Text as [QuestionText], + null as [QuestionId], + req.Requirement_Id as [RequirementId], + Answer_Id as [AnswerID], + Answer_Text as [AnswerText], + u.Universal_Sal_Level as [Level], + requirement_title as [QuestionRef], + Standard_Category + ' - ' + requirement_title as CategoryAndNumber, + rs.Requirement_Id as [QuestionOrRequirementID] + from REQUIREMENT_SETS rs + left join ANSWER ans on ans.Question_Or_Requirement_Id = rs.Requirement_Id + left join [SETS] s on rs.Set_Name = s.Set_Name + left join NEW_REQUIREMENT req on rs.Requirement_Id = req.Requirement_Id + left join REQUIREMENT_LEVELS rl on rl.Requirement_Id = req.Requirement_Id + left join STANDARD_SELECTION ss on ss.Assessment_Id = @assessment_Id + left join UNIVERSAL_SAL_LEVEL u on u.Full_Name_Sal = ss.Selected_Sal_Level + where rs.Set_Name in (select set_name from #mySets) + and ans.Assessment_Id = @assessment_id + and rl.Standard_Level = u.Universal_Sal_Level + order by rs.Requirement_Sequence + end +END diff --git a/Database Scripts/Stored Procedures/usp_getMaturitySummaryOverall.proc.sql b/Database Scripts/Stored Procedures/usp_getMaturitySummaryOverall.proc.sql new file mode 100644 index 0000000000..92aa21c38c --- /dev/null +++ b/Database Scripts/Stored Procedures/usp_getMaturitySummaryOverall.proc.sql @@ -0,0 +1,29 @@ +-- ============================================= +-- Author: hansbk +-- Create date: 8/30/2018 +-- Description: Stub needs completed +-- ============================================= +CREATE PROCEDURE [dbo].[usp_getMaturitySummaryOverall] + @assessment_id int +AS +BEGIN + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED + + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + +select * +into #answers +from answer +where question_type = 'maturity' and Question_Or_Requirement_Id in (select mat_question_id from maturity_questions where maturity_model_id = 5) + select a.Answer_Full_Name,a.Answer_Text, isnull(m.qc,0) qc,isnull(m.Total,0) Total, isnull(cast(IsNull(Round((cast((qc) as float)/(isnull(nullif(Total,0),1)))*100,0),0) as int),0) as [Percent] + from ANSWER_LOOKUP a left join ( + SELECT a.Answer_Text, isnull(count(a.question_or_requirement_id),0) qc, SUM(count(a.question_or_requirement_id)) OVER() AS Total + FROM #answers a + where a.Assessment_Id = @assessment_id + group by a.Answer_Text + ) m on a.Answer_Text = m.Answer_Text + +END + diff --git a/Database Scripts/Views/Analytics_Answers.view.sql b/Database Scripts/Views/Analytics_Answers.view.sql index b839b0a7f5..60ea3b376a 100644 --- a/Database Scripts/Views/Analytics_Answers.view.sql +++ b/Database Scripts/Views/Analytics_Answers.view.sql @@ -1,13 +1,7 @@ - CREATE VIEW [dbo].[Analytics_Answers] AS -SELECT -Assessment_Id, -Question_Or_Requirement_Id, -Question_Type, -CASE WHEN ANSWER.Answer_Text = 'U' OR ANSWER.Answer_Text = 'N' THEN N'N' -WHEN ANSWER.Answer_Text = 'A' OR ANSWER.Answer_Text = 'Y' THEN N'Y' END -AS Answer_Text -FROM [dbo].[ANSWER] -WHERE ANSWER.Answer_Text != 'NA' +SELECT Assessment_Id, Question_Or_Requirement_Id, Question_Type, CASE WHEN ANSWER.Answer_Text = 'A' OR + ANSWER.Answer_Text = 'Y' THEN N'Y' ELSE 'N' END AS Answer_Text +FROM dbo.ANSWER +WHERE (Answer_Text <> 'NA')