File

src/app/child-dev-project/attendance/model/activity-attendance.ts

Description

Aggregate information about all events for a RecurringActivity within a given time period.

This object is not saved in the database but instead generated dynamically from stored Events to avoid problems keeping all information in sync in the database.

Extends

Entity

Index

Properties
Methods
Accessors

Properties

activity
Type : RecurringActivity

The general, recurring activity for which this instance aggregates actual events that took place within a limited time period.

individualLogicalStatusCounts
Default value : new Map< string, { [key in AttendanceLogicalStatus]?: number } >()

Mapping child ids to a map with all logical status as object keys and their counts as values.

individualStatusTypeCounts
Default value : new Map<string, { [key: string]: number }>()

Mapping child ids to a map with all status type ids as object keys and their counts as values.

periodFrom
Type : Date

Starting date of the period this data refers to

periodTo
Type : Date

End date of the period this data refers to

Static Readonly THRESHOLD_URGENT
Type : number
Default value : 0.6
Static Readonly THRESHOLD_WARNING
Type : number
Default value : 0.8
Static Optional _isCustomizedType
todo: This property is no longer used and will be removed in future versions.
Type : boolean
Inherited from Entity
Defined in Entity:77

True if this type's schema has been customized dynamically from the config.

_rev
Type : string
Decorators :
@DatabaseField({anonymize: 'retain'})
Inherited from Entity
Defined in Entity:213

internal database doc revision, used to detect conflicts by PouchDB/CouchDB

anonymized
Type : boolean
Decorators :
@DatabaseField({anonymize: 'retain'})
Inherited from Entity
Defined in Entity:232

Whether this entity has been anonymized and therefore cannot be re-activated.

Static color
Type : string
Inherited from Entity
Defined in Entity:140

color used for to highlight this entity type across the app

created
Type : UpdateMetadata
Decorators :
@DatabaseField({anonymize: 'retain'})
Inherited from Entity
Defined in Entity:218
Static DATABASE
Type : string
Default value : "app"
Inherited from Entity
Defined in Entity:63

The database where these entities are stored.

Static Optional enableUserAccounts
Type : boolean
Inherited from Entity
Defined in Entity:108

Whether to enable user account creation for this entity type. When true, the UI will allow management of user accounts associated with this entity.

Static ENTITY_TYPE
Type : string
Default value : "Entity"
Inherited from Entity
Defined in Entity:58

The entity's type. In classes extending Entity this is usually overridden by the class annotation @DatabaseEntity('NewEntity'). The type needs to be used as routing path in lower case. The routing path can be defined in the configuration file.

Static hasPII
Type : boolean
Default value : false
Inherited from Entity
Defined in Entity:203

whether this entity type can contain "personally identifiable information" (PII) and therefore should follow strict data protection requirements and offer a function to anonymize records.

Static icon
Type : IconName
Inherited from Entity
Defined in Entity:135

icon id used for this entity

inactive
Type : boolean
Decorators :
@DatabaseField({anonymize: 'retain'})
Inherited from Entity
Defined in Entity:226
Static Optional isInternalEntity
Type : boolean
Inherited from Entity
Defined in Entity:102

if this entity type is an internal entity, i.e. only defined in the code base to store internal system data and not visible to the user for customization.

Static label
Type : string
Inherited from Entity
Defined in Entity:95

human-readable name/label of the entity in the UI

Static schema
Type : EntitySchema
Inherited from Entity
Defined in Entity:71

EntitySchema defining property transformations from/to the database. This is auto-generated from the property annotations @DatabaseField().

see /additional-documentation/how-to-guides/create-a-new-entity-type.html

Static Optional toBlockDetailsAttributes
Type : EntityBlockConfig
Inherited from Entity
Defined in Entity:90

Defining which attributes will be displayed in a tooltip on hover when the record is displayed as an entity-block.

Static toStringAttributes
Type : []
Default value : ["entityId"]
Inherited from Entity
Defined in Entity:85

Defining which attribute values of an entity should be shown in the .toString() method.

The default is the ID of the entity (entityId). This can be overwritten in subclasses or through the config.

updated
Type : UpdateMetadata
Decorators :
@DatabaseField({anonymize: 'retain'})
Inherited from Entity
Defined in Entity:223

Methods

countEventsAbsent
countEventsAbsent(childId: string)
Parameters :
Name Type Optional
childId string No
Returns : number
countEventsPresent
countEventsPresent(childId: string)
Parameters :
Name Type Optional
childId string No
Returns : number
countEventsTotal
countEventsTotal()
Returns : number
countEventsWithUnknownStatus
countEventsWithUnknownStatus(forChildId?: string)

The number of events that have at least one participant with an undefined status. This may occur when the user does not complete the full roll call or skips participants. The count of unknown status can indicate if manual checking and corrections are required.

Parameters :
Name Type Optional Description
forChildId string Yes

filter the calculation to only include status of the given participant id

Returns : number
countTotalAbsent
countTotalAbsent()
Returns : any
countTotalPresent
countTotalPresent()
Returns : any
Static create
create(from: Date, events: EventNote[])

Create an instance with the given initial properties.

Parameters :
Name Type Optional Default value
from Date No
events EventNote[] No []
Returns : ActivityAttendance
getAttendancePercentage
getAttendancePercentage(childId: string)
Parameters :
Name Type Optional
childId string No
Returns : number
getAttendancePercentageAverage
getAttendancePercentageAverage()
Returns : number
Public getColor
getColor(forChildId?: string)
Inherited from Entity
Defined in Entity:226
Parameters :
Name Type Optional
forChildId string Yes
Returns : string
Public getWarningLevel
getWarningLevel(forChildId?: string)
Inherited from Entity
Defined in Entity:207

Custom warning level for attendance thresholds - optionally for a specific child.

Parameters :
Name Type Optional
forChildId string Yes
Returns : WarningLevel
recalculateStats
recalculateStats()
Returns : void
assertValid
assertValid()
Inherited from Entity
Defined in Entity:399

Checks if the entity is valid and if the check fails, throws an error explaining the failed check.

Returns : void
Public copy
copy(newId: string | boolean)
Inherited from Entity
Defined in Entity:382

Shallow copy of the entity. The resulting entity will be of the same type as this (taking into account subclassing)

Parameters :
Name Type Optional Default value Description
newId string | boolean No false

if true, a new entityId will be generated; if a string, that value is used as new entityId

Static createPrefixedId
createPrefixedId(type: string, id: string)
Inherited from Entity
Defined in Entity:188

Create a prefixed id by adding the type prefix if it isn't already part of the given id.

Parameters :
Name Type Optional Description
type string No

The type prefix to be added.

id string No

The id to be extended with a prefix.

Returns : string
Static extractEntityIdFromId
extractEntityIdFromId(id: string)
Inherited from Entity
Defined in Entity:172

Extract entityId without prefix.

Parameters :
Name Type Optional Description
id string No

An entity's id including prefix.

Returns : string
Static extractTypeFromId
extractTypeFromId(id: string)
Inherited from Entity
Defined in Entity:163

Extract the ENTITY_TYPE from an id.

Parameters :
Name Type Optional Description
id string No

An entity's id including prefix.

Returns : string
getConstructor
getConstructor()
Inherited from Entity
Defined in Entity:293

Get the class (Entity or the actual subclass of the instance) to call static methods on the correct class considering inheritance

Public getId
getId(withoutPrefix)
Inherited from Entity
Defined in Entity:312

Returns the id of this entity.

Note that an id is final and can't be changed after the object has been instantiated, hence there is no setId() method.

Parameters :
Name Optional Default value
withoutPrefix No false
Returns : string

the unique id of this entity

getSchema
getSchema()
Inherited from Entity
Defined in Entity:300

Get the entity schema of this class

Returns : EntitySchema
Public getType
getType()
Inherited from Entity
Defined in Entity:323

Returns the type which is used to categorize this entity in the database.

Important: Do not overwrite this method! Types are handled internally.

Returns : string

the entity's type (which is the class name).

Accessors

events
getevents()
setevents(value: EventNote[])
Parameters :
Name Type Optional
value EventNote[] No
Returns : void
participants
getparticipants()

List of (actual, recorded in at least one event) participants.

Returns : string[]
import { AttendanceLogicalStatus } from "./attendance-status";
import { RecurringActivity } from "./recurring-activity";
import { defaultAttendanceStatusTypes } from "../../../core/config/default-config/default-attendance-status-types";
import { EventNote } from "./event-note";
import { getWarningLevelColor, WarningLevel } from "../../warning-level";
import { Entity } from "../../../core/entity/model/entity";

/**
 * Aggregate information about all events for a {@link RecurringActivity} within a given time period.
 *
 * This object is not saved in the database but instead generated dynamically from stored Events
 * to avoid problems keeping all information in sync in the database.
 */
export class ActivityAttendance extends Entity {
  static readonly THRESHOLD_URGENT = 0.6;
  static readonly THRESHOLD_WARNING = 0.8;

  /**
   * Create an instance with the given initial properties.
   */
  static create(from: Date, events: EventNote[] = []) {
    const instance = new ActivityAttendance();
    instance.periodFrom = from;
    instance.events = events;
    return instance;
  }

  /**
   * Starting date of the period this data refers to
   */
  periodFrom: Date;
  /**
   * End date of the period this data refers to
   */
  periodTo: Date;

  /**
   * Events within the period relating to the activity
   */
  private _events: EventNote[] = [];

  set events(value: EventNote[]) {
    this._events = value;
    this.recalculateStats();
  }

  get events(): EventNote[] {
    return this._events;
  }

  /**
   * The general, recurring activity for which this instance aggregates actual events that took place within a limited time period.
   */
  activity: RecurringActivity;

  /**
   * List of (actual, recorded in at least one event) participants.
   */
  get participants(): string[] {
    return Array.from(new Set(this.events.flatMap((event) => event.children)));
  }

  /**
   * Mapping child ids to a map with all *logical* status as object keys and their counts as values.
   */
  individualLogicalStatusCounts = new Map<
    string,
    { [key in AttendanceLogicalStatus]?: number }
  >();

  /**
   * Mapping child ids to a map with all status type ids as object keys and their counts as values.
   */
  individualStatusTypeCounts = new Map<string, { [key: string]: number }>();

  countEventsTotal(): number {
    return this.events.length;
  }

  countEventsPresent(childId: string): number {
    return this.countIndividual(childId, AttendanceLogicalStatus.PRESENT);
  }

  countEventsAbsent(childId: string): number {
    return this.countIndividual(childId, AttendanceLogicalStatus.ABSENT);
  }

  private countIndividual(
    childId: string,
    countingType: AttendanceLogicalStatus,
  ) {
    return this.events.filter(
      (eventNote) =>
        eventNote.getAttendance(childId)?.status.countAs === countingType,
    ).length;
  }

  getAttendancePercentage(childId: string): number {
    const present = this.countEventsPresent(childId);
    const absent = this.countEventsAbsent(childId);

    return present / (present + absent);
  }

  countTotalPresent() {
    return this.countWithStatus(AttendanceLogicalStatus.PRESENT);
  }

  countTotalAbsent() {
    return this.countWithStatus(AttendanceLogicalStatus.ABSENT);
  }

  private countWithStatus(matchingType: AttendanceLogicalStatus) {
    return this.events.reduce(
      (total, event) => total + event.countWithStatus(matchingType),
      0,
    );
  }

  getAttendancePercentageAverage(): number {
    return this.countPercentage(AttendanceLogicalStatus.PRESENT, false);
  }

  private countPercentage(
    matchingType: AttendanceLogicalStatus,
    rounded: boolean = false,
  ) {
    const calculatedStats = this.events
      .map((event) => {
        const eventStats = {
          matching: 0,
          total: event.children.length,
        };
        for (const childId of event.children) {
          const att = event.getAttendance(childId).status;
          if (att.countAs === matchingType) {
            eventStats.matching++;
          } else if (att.countAs === AttendanceLogicalStatus.IGNORE) {
            eventStats.total--;
          }
        }

        return eventStats;
      })
      .reduce(
        (accumulatedStats, currentEventStats) => {
          accumulatedStats.total += currentEventStats.total;
          accumulatedStats.matching += currentEventStats.matching;
          return accumulatedStats;
        },
        { total: 0, matching: 0 },
      );

    const result = calculatedStats.matching / calculatedStats.total;
    if (rounded) {
      return Math.round(result * 10) / 10;
    } else {
      return result;
    }
  }

  /**
   * The number of events that have at least one participant with an undefined status.
   * This may occur when the user does not complete the full roll call or skips participants.
   * The count of unknown status can indicate if manual checking and corrections are required.
   *
   * @param forChildId filter the calculation to only include status of the given participant id
   */
  countEventsWithUnknownStatus(forChildId?: string): number {
    return this.events
      .filter((e) => !forChildId || e.children.includes(forChildId))
      .reduce(
        (count: number, e: EventNote) =>
          e.hasUnknownAttendances(forChildId) ? count + 1 : count,
        0,
      );
  }

  recalculateStats() {
    this.individualStatusTypeCounts = new Map();
    this.individualLogicalStatusCounts = new Map();

    for (const event of this.events) {
      for (const participant of event.children) {
        let logicalCount = this.individualLogicalStatusCounts.get(participant);
        if (!logicalCount) {
          logicalCount = {};
          this.individualLogicalStatusCounts.set(participant, logicalCount);
        }
        let typeCount = this.individualStatusTypeCounts.get(participant);
        if (!typeCount) {
          typeCount = {};
          this.individualStatusTypeCounts.set(participant, typeCount);
        }

        const att = event.getAttendance(participant);
        logicalCount[att.status.countAs] =
          (logicalCount[att.status.countAs] ?? 0) + 1;
        typeCount[att.status.id] = (typeCount[att.status.id] ?? 0) + 1;
      }
    }
  }

  /**
   * Custom warning level for attendance thresholds - optionally for a specific child.
   */
  public override getWarningLevel(forChildId?: string): WarningLevel {
    let attendancePercentage;
    if (forChildId) {
      attendancePercentage = this.getAttendancePercentage(forChildId);
    } else {
      attendancePercentage = this.getAttendancePercentageAverage();
    }

    if (!attendancePercentage) {
      return WarningLevel.NONE;
    } else if (attendancePercentage < ActivityAttendance.THRESHOLD_URGENT) {
      return WarningLevel.URGENT;
    } else if (attendancePercentage < ActivityAttendance.THRESHOLD_WARNING) {
      return WarningLevel.WARNING;
    } else {
      return WarningLevel.OK;
    }
  }

  public override getColor(forChildId?: string): string {
    return getWarningLevelColor(this.getWarningLevel(forChildId));
  }
}

/**
 * Generate a event with children for the given AttendanceStatus array.
 *
 * This is particularly useful to generate simple data for demo or test purposes.
 *
 * @param participating Object where keys are string childId and values are its attendance status
 * @param date (Optional) date of the event; if not given today's date is used
 * @param activity (Optional) reference to the connected activity entity
 */
export function generateEventWithAttendance(
  participating: (
    | [string, AttendanceLogicalStatus]
    | [string, AttendanceLogicalStatus, string]
  )[],
  date = new Date(),
  activity?: RecurringActivity,
): EventNote {
  const event = EventNote.create(date);
  for (const att of participating) {
    event.addChild(att[0]);
    event.getAttendance(att[0]).status = defaultAttendanceStatusTypes.find(
      (t) => t.countAs === att[1],
    );
    if (att.length === 3) {
      event.getAttendance(att[0]).remarks = att[2];
    }
  }
  event.relatesTo = activity?.getId();
  return event;
}

results matching ""

    No results matching ""