diff --git a/Epsilon.Abstractions/Epsilon.Abstractions.csproj b/Epsilon.Abstractions/Epsilon.Abstractions.csproj
index d3a8bb24..8f641cfd 100644
--- a/Epsilon.Abstractions/Epsilon.Abstractions.csproj
+++ b/Epsilon.Abstractions/Epsilon.Abstractions.csproj
@@ -9,5 +9,4 @@
-
diff --git a/Epsilon.Abstractions/Model/CompetenceOutcomeResult.cs b/Epsilon.Abstractions/Model/CompetenceOutcomeResult.cs
new file mode 100644
index 00000000..7d6e8cc5
--- /dev/null
+++ b/Epsilon.Abstractions/Model/CompetenceOutcomeResult.cs
@@ -0,0 +1,7 @@
+namespace Epsilon.Abstractions.Model;
+
+public record CompetenceOutcomeResult(
+ int OutcomeId,
+ double Grade,
+ DateTime SubmittedAt
+);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/CompetenceProfile.cs b/Epsilon.Abstractions/Model/CompetenceProfile.cs
index 096eb4fb..1aa51940 100644
--- a/Epsilon.Abstractions/Model/CompetenceProfile.cs
+++ b/Epsilon.Abstractions/Model/CompetenceProfile.cs
@@ -6,7 +6,5 @@ public record CompetenceProfile(
IHboIDomain HboIDomain,
IEnumerable ProfessionalTaskOutcomes,
IEnumerable ProfessionalSkillOutcomes,
- IEnumerable Terms,
- IEnumerable DecayingAveragesPerTask,
- IEnumerable DecayingAveragesPerSkill
+ IEnumerable Terms
);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/CourseModule.cs b/Epsilon.Abstractions/Model/CourseModule.cs
index 035d9b02..d7a5950b 100644
--- a/Epsilon.Abstractions/Model/CourseModule.cs
+++ b/Epsilon.Abstractions/Model/CourseModule.cs
@@ -4,7 +4,5 @@ public class CourseModule
{
public string Name { get; set; } = string.Empty;
public IEnumerable Kpis { get; set; } = Enumerable.Empty();
-
- public string DecayingAverage { get; set; }
}
}
diff --git a/Epsilon.Abstractions/Model/DecayingAverage.cs b/Epsilon.Abstractions/Model/DecayingAverage.cs
new file mode 100644
index 00000000..0de50cae
--- /dev/null
+++ b/Epsilon.Abstractions/Model/DecayingAverage.cs
@@ -0,0 +1,7 @@
+namespace Epsilon.Abstractions.Model;
+
+public record DecayingAverage(
+ double Score,
+ int ArchitectureLayer,
+ int Activity
+);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/DecayingAveragePerActivity.cs b/Epsilon.Abstractions/Model/DecayingAveragePerActivity.cs
deleted file mode 100644
index 0c7c7645..00000000
--- a/Epsilon.Abstractions/Model/DecayingAveragePerActivity.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Epsilon.Abstractions.Model;
-
-public record DecayingAveragePerActivity(
- int Activity,
- double DecayingAverage
-);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/DecayingAveragePerLayer.cs b/Epsilon.Abstractions/Model/DecayingAveragePerLayer.cs
deleted file mode 100644
index 7c3d648e..00000000
--- a/Epsilon.Abstractions/Model/DecayingAveragePerLayer.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Epsilon.Abstractions.Model;
-
-public record DecayingAveragePerLayer(
- int ArchitectureLayer,
- IEnumerable LayerActivities
-);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/DecayingAveragePerSkill.cs b/Epsilon.Abstractions/Model/DecayingAveragePerSkill.cs
deleted file mode 100644
index 90eef853..00000000
--- a/Epsilon.Abstractions/Model/DecayingAveragePerSkill.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Epsilon.Abstractions.Model;
-
-public record DecayingAveragePerSkill(
- int Skill,
- double DecayingAverage
-);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/ProfessionalSkillResult.cs b/Epsilon.Abstractions/Model/ProfessionalSkillResult.cs
index 8cd48934..505b4f3b 100644
--- a/Epsilon.Abstractions/Model/ProfessionalSkillResult.cs
+++ b/Epsilon.Abstractions/Model/ProfessionalSkillResult.cs
@@ -1,8 +1,9 @@
namespace Epsilon.Abstractions.Model;
public record ProfessionalSkillResult(
+ int OutcomeId,
int Skill,
int MasteryLevel,
double Grade,
DateTime AssessedAt
-);
\ No newline at end of file
+) : CompetenceOutcomeResult(OutcomeId, Grade, AssessedAt);
\ No newline at end of file
diff --git a/Epsilon.Abstractions/Model/ProfessionalTaskResult.cs b/Epsilon.Abstractions/Model/ProfessionalTaskResult.cs
index 55998383..a83e9c83 100644
--- a/Epsilon.Abstractions/Model/ProfessionalTaskResult.cs
+++ b/Epsilon.Abstractions/Model/ProfessionalTaskResult.cs
@@ -1,9 +1,10 @@
namespace Epsilon.Abstractions.Model;
public record ProfessionalTaskResult(
+ int OutcomeId,
int ArchitectureLayer,
int Activity,
int MasteryLevel,
double Grade,
DateTime AssessedAt
-);
\ No newline at end of file
+) : CompetenceOutcomeResult(OutcomeId, Grade, AssessedAt);
\ No newline at end of file
diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs
index 7c6ed535..c09367c5 100644
--- a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs
+++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs
@@ -4,5 +4,6 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl;
public record SubmissionsHistoriesConnectionNode(
[property: JsonPropertyName("attempt")] int? Attempt,
+ [property: JsonPropertyName("submittedAt")] DateTime? SubmittedAt,
[property: JsonPropertyName("rubricAssessmentsConnection")] RubricAssessmentsConnection? RubricAssessments
);
\ No newline at end of file
diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs
index 09e169c2..9cbf3ac6 100644
--- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs
+++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs
@@ -6,20 +6,4 @@ public record OutcomeResultCollection(
[property: JsonPropertyName("outcome_results")]
IEnumerable OutcomeResults,
[property: JsonPropertyName("linked")] OutcomeResultCollectionLink? Links
-)
-{
- public double GetDecayingAverage()
- {
- var decayingAverage = 0.0;
-
- foreach(var grade in OutcomeResults)
- {
- if (grade.Score != null)
- {
- decayingAverage = decayingAverage * 0.35 + grade.Score.Value * 0.65;
- }
- }
-
- return decayingAverage;
- }
-}
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/Epsilon.Canvas/QueryConstants.cs b/Epsilon.Canvas/QueryConstants.cs
index 4481e619..721f4eb5 100644
--- a/Epsilon.Canvas/QueryConstants.cs
+++ b/Epsilon.Canvas/QueryConstants.cs
@@ -54,6 +54,7 @@ query MyQuery {
}
}
attempt
+ submittedAt
}
}
postedAt
diff --git a/Epsilon.Host.Frontend/src/components/CompetenceGraph.vue b/Epsilon.Host.Frontend/src/components/CompetenceGraph.vue
index 176fac24..44185abb 100644
--- a/Epsilon.Host.Frontend/src/components/CompetenceGraph.vue
+++ b/Epsilon.Host.Frontend/src/components/CompetenceGraph.vue
@@ -1,18 +1,22 @@
+ :series="series"
+ height="350"
+ type="bar"
+ width="750" />
diff --git a/Epsilon.Host.Frontend/src/components/PersonalDevelopmentGraph.vue b/Epsilon.Host.Frontend/src/components/PersonalDevelopmentGraph.vue
index 61b89581..6190ea02 100644
--- a/Epsilon.Host.Frontend/src/components/PersonalDevelopmentGraph.vue
+++ b/Epsilon.Host.Frontend/src/components/PersonalDevelopmentGraph.vue
@@ -1,23 +1,28 @@
+ :series="series"
+ height="350"
+ type="bar"
+ width="200" />
diff --git a/Epsilon.Host.Frontend/src/logic/Api.ts b/Epsilon.Host.Frontend/src/logic/Api.ts
index 68f98af0..135ff2d9 100644
--- a/Epsilon.Host.Frontend/src/logic/Api.ts
+++ b/Epsilon.Host.Frontend/src/logic/Api.ts
@@ -1,6 +1,5 @@
/* eslint-disable */
/* tslint:disable */
-
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
@@ -12,140 +11,132 @@
export interface Activity {
/** @format int32 */
- id?: number;
- name?: string | null;
- color?: string | null;
+ id?: number
+ name?: string | null
+ color?: string | null
}
export interface ArchitectureLayer {
/** @format int32 */
- id?: number;
- name?: string | null;
- shortName?: string | null;
- color?: string | null;
+ id?: number
+ name?: string | null
+ shortName?: string | null
+ color?: string | null
}
export interface CompetenceProfile {
- hboIDomain?: IHboIDomain;
- professionalTaskOutcomes?: ProfessionalTaskResult[] | null;
- professionalSkillOutcomes?: ProfessionalSkillResult[] | null;
- terms?: EnrollmentTerm[] | null;
- decayingAveragesPerTask?: DecayingAveragePerLayer[] | null;
- decayingAveragesPerSkill?: DecayingAveragePerSkill[] | null;
+ hboIDomain?: IHboIDomain
+ professionalTaskOutcomes?: ProfessionalTaskResult[] | null
+ professionalSkillOutcomes?: ProfessionalSkillResult[] | null
+ terms?: EnrollmentTerm[] | null
+ decayingAverages?: DecayingAverage[] | null
}
-export interface DecayingAveragePerActivity {
- /** @format int32 */
- activity?: number;
+export interface DecayingAverage {
/** @format double */
- decayingAverage?: number;
-}
-
-export interface DecayingAveragePerLayer {
+ score?: number
/** @format int32 */
- architectureLayer?: number;
- layerActivities?: DecayingAveragePerActivity[] | null;
-}
-
-export interface DecayingAveragePerSkill {
+ architectureLayer?: number
/** @format int32 */
- skill?: number;
- /** @format double */
- decayingAverage?: number;
+ activity?: number
}
export interface EnrollmentTerm {
- name?: string | null;
+ name?: string | null
/** @format date-time */
- start_at?: string | null;
+ start_at?: string | null
/** @format date-time */
- end_at?: string | null;
+ end_at?: string | null
}
export interface IHboIDomain {
- architectureLayers?: ArchitectureLayer[] | null;
- activities?: Activity[] | null;
- professionalSkills?: ProfessionalSkill[] | null;
- masteryLevels?: MasteryLevel[] | null;
+ architectureLayers?: ArchitectureLayer[] | null
+ activities?: Activity[] | null
+ professionalSkills?: ProfessionalSkill[] | null
+ masteryLevels?: MasteryLevel[] | null
}
export interface MasteryLevel {
/** @format int32 */
- id?: number;
+ id?: number
/** @format int32 */
- level?: number;
- color?: string | null;
+ level?: number
+ color?: string | null
}
export interface ProfessionalSkill {
/** @format int32 */
- id?: number;
- name?: string | null;
- shortName?: string | null;
- color?: string | null;
+ id?: number
+ name?: string | null
+ shortName?: string | null
+ color?: string | null
}
export interface ProfessionalSkillResult {
/** @format int32 */
- skill?: number;
- /** @format int32 */
- masteryLevel?: number;
+ outcomeId?: number
/** @format double */
- grade?: number;
+ grade?: number
/** @format date-time */
- assessedAt?: string;
+ assessedAt?: string
+ /** @format int32 */
+ skill?: number
+ /** @format int32 */
+ masteryLevel?: number
}
export interface ProfessionalTaskResult {
/** @format int32 */
- architectureLayer?: number;
- /** @format int32 */
- activity?: number;
- /** @format int32 */
- masteryLevel?: number;
+ outcomeId?: number
/** @format double */
- grade?: number;
+ grade?: number
/** @format date-time */
- assessedAt?: string;
+ assessedAt?: string
+ /** @format int32 */
+ architectureLayer?: number
+ /** @format int32 */
+ activity?: number
+ /** @format int32 */
+ masteryLevel?: number
}
-export type QueryParamsType = Record;
-export type ResponseFormat = keyof Omit;
+export type QueryParamsType = Record
+export type ResponseFormat = keyof Omit
export interface FullRequestParams extends Omit {
/** set parameter to `true` for call `securityWorker` for this request */
- secure?: boolean;
+ secure?: boolean
/** request path */
- path: string;
+ path: string
/** content type of request body */
- type?: ContentType;
+ type?: ContentType
/** query params */
- query?: QueryParamsType;
+ query?: QueryParamsType
/** format of response (i.e. response.json() -> format: "json") */
- format?: ResponseFormat;
+ format?: ResponseFormat
/** request body */
- body?: unknown;
+ body?: unknown
/** base url */
- baseUrl?: string;
+ baseUrl?: string
/** request cancellation token */
- cancelToken?: CancelToken;
+ cancelToken?: CancelToken
}
-export type RequestParams = Omit;
+export type RequestParams = Omit
export interface ApiConfig {
- baseUrl?: string;
- baseApiParams?: Omit;
- securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void;
- customFetch?: typeof fetch;
+ baseUrl?: string
+ baseApiParams?: Omit
+ securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void
+ customFetch?: typeof fetch
}
export interface HttpResponse extends Response {
- data: D;
- error: E;
+ data: D
+ error: E
}
-type CancelToken = Symbol | string | number;
+type CancelToken = Symbol | string | number
export enum ContentType {
Json = "application/json",
@@ -155,73 +146,76 @@ export enum ContentType {
}
export class HttpClient {
- public baseUrl: string = "https://localhost:7084";
- private securityData: SecurityDataType | null = null;
- private securityWorker?: ApiConfig["securityWorker"];
- private abortControllers = new Map();
- private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams);
+ public baseUrl: string = "https://localhost:7084"
+ private securityData: SecurityDataType | null = null
+ private securityWorker?: ApiConfig["securityWorker"]
+ private abortControllers = new Map()
+ private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams)
private baseApiParams: RequestParams = {
credentials: "same-origin",
headers: {},
redirect: "follow",
referrerPolicy: "no-referrer",
- };
+ }
constructor(apiConfig: ApiConfig = {}) {
- Object.assign(this, apiConfig);
+ Object.assign(this, apiConfig)
}
public setSecurityData = (data: SecurityDataType | null) => {
- this.securityData = data;
- };
+ this.securityData = data
+ }
protected encodeQueryParam(key: string, value: any) {
- const encodedKey = encodeURIComponent(key);
- return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
+ const encodedKey = encodeURIComponent(key)
+ return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`
}
protected addQueryParam(query: QueryParamsType, key: string) {
- return this.encodeQueryParam(key, query[key]);
+ return this.encodeQueryParam(key, query[key])
}
protected addArrayQueryParam(query: QueryParamsType, key: string) {
- const value = query[key];
- return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
+ const value = query[key]
+ return value.map((v: any) => this.encodeQueryParam(key, v)).join("&")
}
protected toQueryString(rawQuery?: QueryParamsType): string {
- const query = rawQuery || {};
- const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
+ const query = rawQuery || {}
+ const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key])
return keys
- .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
- .join("&");
+ .map((key) =>
+ Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)
+ )
+ .join("&")
}
protected addQueryParams(rawQuery?: QueryParamsType): string {
- const queryString = this.toQueryString(rawQuery);
- return queryString ? `?${queryString}` : "";
+ const queryString = this.toQueryString(rawQuery)
+ return queryString ? `?${queryString}` : ""
}
private contentFormatters: Record any> = {
[ContentType.Json]: (input: any) =>
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
- [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
+ [ContentType.Text]: (input: any) =>
+ input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
[ContentType.FormData]: (input: any) =>
Object.keys(input || {}).reduce((formData, key) => {
- const property = input[key];
+ const property = input[key]
formData.append(
key,
property instanceof Blob
? property
: typeof property === "object" && property !== null
- ? JSON.stringify(property)
- : `${property}`,
- );
- return formData;
+ ? JSON.stringify(property)
+ : `${property}`
+ )
+ return formData
}, new FormData()),
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
- };
+ }
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
return {
@@ -233,90 +227,90 @@ export class HttpClient {
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
- };
+ }
}
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
if (this.abortControllers.has(cancelToken)) {
- const abortController = this.abortControllers.get(cancelToken);
+ const abortController = this.abortControllers.get(cancelToken)
if (abortController) {
- return abortController.signal;
+ return abortController.signal
}
- return void 0;
+ return void 0
}
- const abortController = new AbortController();
- this.abortControllers.set(cancelToken, abortController);
- return abortController.signal;
- };
+ const abortController = new AbortController()
+ this.abortControllers.set(cancelToken, abortController)
+ return abortController.signal
+ }
public abortRequest = (cancelToken: CancelToken) => {
- const abortController = this.abortControllers.get(cancelToken);
+ const abortController = this.abortControllers.get(cancelToken)
if (abortController) {
- abortController.abort();
- this.abortControllers.delete(cancelToken);
+ abortController.abort()
+ this.abortControllers.delete(cancelToken)
}
- };
+ }
public request = async ({
- body,
- secure,
- path,
- type,
- query,
- format,
- baseUrl,
- cancelToken,
- ...params
- }: FullRequestParams): Promise> => {
+ body,
+ secure,
+ path,
+ type,
+ query,
+ format,
+ baseUrl,
+ cancelToken,
+ ...params
+ }: FullRequestParams): Promise> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
- {};
- const requestParams = this.mergeRequestParams(params, secureParams);
- const queryString = query && this.toQueryString(query);
- const payloadFormatter = this.contentFormatters[type || ContentType.Json];
- const responseFormat = format || requestParams.format;
+ {}
+ const requestParams = this.mergeRequestParams(params, secureParams)
+ const queryString = query && this.toQueryString(query)
+ const payloadFormatter = this.contentFormatters[type || ContentType.Json]
+ const responseFormat = format || requestParams.format
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
...requestParams,
headers: {
...(requestParams.headers || {}),
- ...(type && type !== ContentType.FormData ? {"Content-Type": type} : {}),
+ ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
},
signal: cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
}).then(async (response) => {
- const r = response as HttpResponse;
- r.data = null as unknown as T;
- r.error = null as unknown as E;
+ const r = response as HttpResponse
+ r.data = null as unknown as T
+ r.error = null as unknown as E
const data = !responseFormat
? r
: await response[responseFormat]()
- .then((data) => {
- if (r.ok) {
- r.data = data;
- } else {
- r.error = data;
- }
- return r;
- })
- .catch((e) => {
- r.error = e;
- return r;
- });
+ .then((data) => {
+ if (r.ok) {
+ r.data = data
+ } else {
+ r.error = data
+ }
+ return r
+ })
+ .catch((e) => {
+ r.error = e
+ return r
+ })
if (cancelToken) {
- this.abortControllers.delete(cancelToken);
+ this.abortControllers.delete(cancelToken)
}
- if (!response.ok) throw data;
- return data;
- });
- };
+ if (!response.ok) throw data
+ return data
+ })
+ }
}
/**
@@ -352,7 +346,7 @@ export class Api extends HttpClient extends HttpClient r.outcomeId as unknown as string)
+ ).map(([, j]) => {
+ return {
+ decayingAverage: this.getDecayingAverageFromOneOutcomeType(j),
+ skill: j.at(0)?.skill,
+ masteryLevel: j
+ .sort((a) => a.masteryLevel as never as number)
+ .at(0)?.masteryLevel,
+ } as DecayingAveragePerSkill
+ })
+
+ return domain.professionalSkills?.map((s) => {
+ let score = 0.0
+ const filteredResults = listOfResults.filter(
+ (r) => r.skill === s.id
+ )
+ filteredResults.map((result) => {
+ score += result.decayingAverage
+ })
+ return {
+ decayingAverage: score / filteredResults.length,
+ skill: s.id,
+ masteryLevel: filteredResults
+ .sort((a) => a.masteryLevel as never as number)
+ .at(0)?.masteryLevel,
+ } as DecayingAveragePerSkill
+ }) as DecayingAveragePerSkill[]
+ }
+
+ /**
+ * Calculate the averages for each task type divided in architecture layers.
+ * @param taskResults
+ * @param domain
+ * @constructor
+ */
+ public static getAverageTaskOutcomeScores(
+ taskResults: ProfessionalTaskResult[],
+ domain: IHboIDomain
+ ): DecayingAveragePerLayer[] {
+ const canvasDecaying = this.getDecayingAverageForAllOutcomes(
+ taskResults,
+ domain
+ )
+ return domain.architectureLayers?.map((layer) => {
+ return {
+ architectureLayer: layer.id,
+ layerActivities: domain.activities?.map((activity) => {
+ let totalScoreActivity = 0
+ let totalScoreArchitectureActivity = 0
+ let amountOfActivities = 0
+
+ //Calculate the total score from activity
+ canvasDecaying.map((l) =>
+ l.layerActivities
+ ?.filter((la) => la.activity === activity.id)
+ .map(
+ (la) =>
+ (totalScoreActivity +=
+ la.decayingAverage &&
+ amountOfActivities++)
+ )
+ )
+
+ //Calculate the total score from activity inside this architecture layer
+ canvasDecaying
+ .filter((l) => l.architectureLayer === layer.id)
+ .map((l) =>
+ l.layerActivities
+ ?.filter((la) => la.activity === activity.id)
+ .map(
+ (la) =>
+ (totalScoreArchitectureActivity +=
+ la.decayingAverage)
+ )
+ )
+
+ return {
+ activity: activity.id,
+ decayingAverage:
+ ((totalScoreActivity / amountOfActivities) *
+ totalScoreArchitectureActivity) /
+ totalScoreActivity,
+ } as DecayingAveragePerActivity
+ }),
+ }
+ }) as DecayingAveragePerLayer[]
+ }
+
+ /**
+ * Calculate average of given tasks divided in architectural layers
+ * @param taskResults
+ * @param domain
+ * @constructor
+ * @private
+ */
+ private static getDecayingAverageForAllOutcomes(
+ taskResults: ProfessionalTaskResult[],
+ domain: IHboIDomain
+ ): DecayingAveragePerLayer[] {
+ return domain.architectureLayers?.map((l) => {
+ return {
+ architectureLayer: l.id,
+ layerActivities: Object.entries(
+ this.groupBy(
+ //Ensure that given results are only relined on the architecture that is currently being used.
+ taskResults.filter(
+ (layer) => layer.architectureLayer === l.id
+ ),
+ (r) => r.outcomeId as unknown as string
+ )
+ ).map(([i, j]) => {
+ //From all selected outcomes calculate the decaying average, Give outcome id and activity layer.
+ return {
+ outcome: i,
+ activity: j.at(0)?.activity,
+ decayingAverage:
+ this.getDecayingAverageFromOneOutcomeType(j),
+ }
+ }) as unknown as DecayingAveragePerActivity[],
+ }
+ }) as DecayingAveragePerLayer[]
+ }
+
+ /**
+ * Calculate decaying average described by Canvas: https://community.canvaslms.com/t5/Canvas-Basics-Guide/What-are-Outcomes/ta-p/75#decaying_average
+ * !IMPORTANT, The list of results will always have to be a list of the same outcome id. Not a list of equal activity and architecture layer.
+ * @param results
+ * @constructor
+ * @private
+ */
+ private static getDecayingAverageFromOneOutcomeType(
+ results: ProfessionalTaskResult[] | ProfessionalSkillResult[]
+ ): number {
+ let totalGradeScore = 0.0
+
+ const recentResult = results.reverse().pop()
+ if (recentResult && recentResult.grade) {
+ if (results.length > 0) {
+ results.forEach(
+ (r) => (totalGradeScore += r.grade ? r.grade : 0)
+ )
+ totalGradeScore =
+ (totalGradeScore / results.length) * 0.35 +
+ recentResult.grade * 0.65
+ } else {
+ totalGradeScore = recentResult.grade
+ }
+ }
+
+ return totalGradeScore
+ }
+
+ private static groupBy(
+ arr: T[],
+ fn: (item: T) => number | string
+ ): Record {
+ return arr.reduce>((prev, curr) => {
+ const groupKey = fn(curr)
+ const group = prev[groupKey] || []
+ group.push(curr)
+ return { ...prev, [groupKey]: group }
+ }, {})
+ }
+}
diff --git a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue
index 3e5989d3..e487b248 100644
--- a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue
+++ b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue
@@ -4,16 +4,16 @@
:terms="data.terms"
@on-term-selected="setTermFilter" />
+ :data="filteredProfessionalTaskOutcomes"
+ :domain="data.hboIDomain" />
-
+ :data="filteredProfessionalTaskOutcomes"
+ :domain="data.hboIDomain" />
+
@@ -21,17 +21,17 @@