Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

The IncidentReports kit — confidential reports, their review, corrective actions and the people who must be told

use Osysharp.IncidentReports@0; · policy ReviewsIncidents => …; · ReportIncident(category, occurredAt, location, description) · AddIncidentPerson(report, role, person, name) · StartIncidentReview(report) · ClassifyIncident(report, severity) · AssignCorrectiveAction(report, what, owner, dueOn) · InformContacts(person) · RecordAuthorityReport(report, reference) · CloseIncident(report) · IncidentRegister(from, to)

`Osysharp.IncidentReports` keeps incident reports — injuries, near misses, damage, concerns — readable by the person who filed them and by whoever reviews incidents, nobody else. The people involved with their injury, care and own statement; photos; a review with a severity, a cause, corrective actions with owners and due dates; the people who must be told (a child's guardians) and their acknowledgement; an authority's deadline on a durable clock; a register and its CSV.

preview1 example compiled by CIuikitincidentssafety

Source on GitHub: Osysharp.IncidentReports — github.com/osysharp/incidentreports ↗ · Osysharp.IncidentReports.Guardianship — github.com/osysharp/incidentreports/guardianship ↗ · Osysharp.Privacy.IncidentReports — github.com/osysharp/privacy/incidentreports ↗

Summary#

use Osysharp.IncidentReports@0; keeps incident reports. The app says who reviews them:

policy ReviewsIncidents => IsSafetyLead;   // reads every report, reviews, assigns actions, tells people, the register
policy ReportsIncidents => IsStaff;        // who may file one — anybody signed in, unless you narrow it

A report is then read by the person who filed it and by ReviewsIncidents — nobody else, in lists, counts and searches.

Signature#

virtual policy ReviewsIncidents => false;        virtual policy ReportsIncidents => IsAuthenticated;
virtual policy ManagesIncidentSetup => ReviewsIncidents;
app.Incidents = new IncidentsSetup { AuthorityWarningHours = 24, CloseWithOpenActions = false, ReportersReadOutcome = true, Organisation = "" };
public interface IIncidentContacts { List<Principal> ContactsFor(Principal person); }

IncidentReport ReportIncident(IncidentCategory category, DateTime occurredAt, string location, string description,
                              string immediateAction = "", IncidentSeverity? severity = null);
IncidentPerson AddIncidentPerson(IncidentReport report, IncidentRole role, Principal? person = null, string name = "",
                                 string injury = "", IncidentTreatment treatment = IncidentTreatment.None, string treatmentNote = "");
void GiveStatement(IncidentPerson p, string statement);
void StartIncidentReview(IncidentReport report, string notes = "");   void ClassifyIncident(IncidentReport report, IncidentSeverity severity);
void RecordIncidentFindings(IncidentReport report, string rootCause = "", string outcome = "", string notes = "");
void MarkIncidentReportable(IncidentReport report, bool reportable, string authority = "", DateTime? dueAt = null);
void RecordAuthorityReport(IncidentReport report, string reference, DateTime? at = null);
void CloseIncident(IncidentReport report, string outcome = "");   void ReopenIncident(IncidentReport report, string why);
CorrectiveAction AssignCorrectiveAction(IncidentReport report, string what, Principal owner, DateOnly dueOn);
void CompleteCorrectiveAction(CorrectiveAction action, string note);
List<IncidentNotice> InformContacts(IncidentPerson about, string message = "");   void AcknowledgeIncidentNotice(IncidentNotice n, string response = "");
List<IncidentRegisterRow> IncidentRegister(DateOnly from, DateOnly to);   string IncidentRegisterCsv(DateOnly from, DateOnly to);

Description#

What does a report hold?#

A running number (INC-00001), who filed it (stamped from the caller, never chosen), when it happened and was reported, a category and — when known — a severity, where, what happened and what was done there and then. Categories and severities are the app's rows (IncidentCategory, IncidentSeverity with a rank). A category that AsksAboutInjury asks who was hurt on the form. The person who filed it corrects the facts while it is Open; once a reviewer takes it up it is the record. Nothing is deleted.

Who was involved?#

IncidentPerson rows: hurt, involved or a witness, with an account (staff, a member, or a child or visitor with an account they never sign in to — managed accounts) or just a name. Each carries the injury, the care given, days away and their statement. A witness reads their own line, never the report, and writes their own statement (GiveStatement). Photos and files go on the report (KeepIncidentFile, AttachIncidentFile).

How is it reviewed?#

StartIncidentReview takes it up; ClassifyIncident sets its severity; RecordIncidentFindings keeps the cause (reviewers only) and the outcome (which its reporter reads once it closes, unless the app says otherwise). AssignCorrectiveAction sets what will stop it happening again, with an owner and a due date — the owner reads their action and the report's number and category it carries, never the report, and marks it done. Each action's clock raises CorrectiveActionOverdue once when it runs late. CloseIncident refuses while an action is open or the report waits for its authority, naming what is left; ReopenIncident takes it back, the reason kept. Status, notes and review fields move only inside these functions (deny … except via).

Who is told?#

The app answers IIncidentContacts — next of kin, a carer — found, never wired (App.Implementations<T>()); with Osysharp.Guardianship, Osysharp.IncidentReports.Guardianship answers with a child's guardians by itself. ContactsToInform lists who has not been told; InformAboutIncident / InformContacts send an IncidentNotice that keeps exactly what they were told (the facts of that person's part and the reviewer's own words), raising IncidentNoticeSent for the app to mail. They read their notice — never the report — and acknowledge it.

When must it go to an authority?#

A severity may carry ReportWithinHours and an Authority ("HSE (RIDDOR)", 240): classifying a report at it makes it reportable, due that long after it was reported. A reviewer can set or clear one by hand (MarkIncidentReportable). Each report's clock raises IncidentAuthorityReportDue AuthorityWarningHours before the deadline and IncidentAuthorityReportOverdue when it passes, once each; RecordAuthorityReport keeps the authority's reference and silences both.

The register#

IncidentRegister(from, to) is every report of the period, one line each — number, when, category, severity, where, who was hurt and how they were cared for, days away, status, the authority line, open actions — and IncidentRegisterCsv the same as CSV, for an inspector, an insurer or a board.

Which pages does it serve?#

/incidents — report one; your reports, the actions you own, statements asked of you, what you were told. /staff/incidents — the desk: what waits for review, what is under review, deadlines, late actions; one report open in a dialog with the whole review. /staff/incidents/register and /staff/incidents/setup. Each stands in the app's working shell with theme tokens only; take one over by declaring a component of the same name, or place MyIncidents, ReportIncidentForm, IncidentsDesk, IncidentDetail, IncidentRegisterView or IncidentSetup on a page of your own.

Whose data is kept?#

What happened and a statement are Personal.Content, names Personal.Identity, an injury and the care given Personal.Special. With Osysharp.Privacy, Osysharp.Privacy.IncidentReports arrives by itself (Bridges — a package that arrives when an app uses the two it completes): special-category data kept on a legal obligation, counted, copied and — when its period ends — erased from the lines naming the person, while the report stays.

Examples#

A workshop that tells a trainee's guardian and chases late actions#

app Workshop {
  use Osysharp.Identity@0;
  use Osysharp.Csv@0;
  use Osysharp.IncidentReports@0;
  use Osysharp.Storage;
  use Osysharp.Ui;
  use Osysharp.Workflow;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.IncidentReports;

[Role] enum ShopRole { Authenticator, Trainee, Instructor, SafetyLead }
policy IsStaff => RoleGrant.Any(g => g.Member == user && (g.Role == ShopRole.Instructor || g.Role == ShopRole.SafetyLead));
policy ReportsIncidents => IsStaff;
policy ReviewsIncidents => RoleGrant.Any(g => g.Member == user && g.Role == ShopRole.SafetyLead);

app.Incidents = new IncidentsSetup { AuthorityWarningHours = 48, Organisation = "Northgate Training Workshop" };

/// Who signed a trainee up — the one told when the trainee is named in an incident.
entity Sponsor {
  [Required("Which trainee?")] Principal Trainee;
  [Required("Who sponsors them?")] Principal Contact;
  security { allow read when IsStaff; }
}

public class Sponsors : IIncidentContacts {
  public List<Principal> ContactsFor(Principal person) => Sponsor.Where(s => s.Trainee == person).Select(s => s.Contact).Take(5).ToList();
}

/// A reminder the safety lead sees when an action runs late.
entity LateAction {
  [MaxLength(1000)] string What = "";
  security { allow read when ReviewsIncidents; }
}

[On]
void NoteLate(CorrectiveActionOverdue e) { new LateAction { What = e.Action.What }; }

See also#

Related

The Guardianship kit — who acts for whom: children, carers, consents and emergency contacts

`Osysharp.Guardianship` links a person to the people who look after them. A parent adds a child (an account the child…

The UserAccounts kit — sign-in done properly, once

`Osysharp.UserAccounts` is the default way into an app: sign-in with a password or a provider (Google, GitHub, …)…

[Attachment] — a file readable wherever its row is

`[Attachment]` on a `FileAsset` reference makes the file readable by exactly the people who may read the row holding it…

deny … except via

Names the ONLY functions whose own code may change something a deny withholds: `deny update UnitsLeft except via…

[Event] — what happens to a record, declared once

An event is a class marked `[Event]`: something that happens to a record, declared once by the code that owns the…

[On] — hear an event, after the commit that raised it

`[On]` on a function makes it a HANDLER of the event its one parameter is. Every handler of an event — in the app and…

settings a package asks its app for (app settings)

A package — or an app — declares a setting by marking a class `[AppSetting("Name")]`. The app that uses it fills it in…

The Privacy kit — erasure that keeps only what the company must or may

`Osysharp.Privacy` answers a person's GDPR request — access, portability, erasure, restriction — per category of what…

Bridges — a package that arrives when an app uses the two it completes

A bridge is a package whose manifest says `completes A@0, B@0;`. An app that uses every package it completes gets it…

App.Implementations<T>()

One new instance of every class in the app's composition — its own and every package's — that implements the interface…