Skip to content

Commit

Permalink
Merge pull request #86 from intervention-engine/huddle_scheduling_ref…
Browse files Browse the repository at this point in the history
…actored

Huddle scheduling refactored
  • Loading branch information
eedrummer authored Oct 25, 2016
2 parents 302a7e5 + d8f05a5 commit 865fd16
Show file tree
Hide file tree
Showing 12 changed files with 1,188 additions and 760 deletions.
22 changes: 13 additions & 9 deletions config/multifactor_huddle_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,34 @@
"name": "Interdisciplinary Huddle",
"leaderID": "1",
"days": [1],
"lookAhead": 14,
"lookAhead": 16,
"riskConfig": {
"riskMethod": {"system": "http://interventionengine.org/risk-assessments", "code": "MultiFactor"},
"frequencyConfigs": [
{
"minScore": 4,
"maxScore": 4,
"minDaysBetweenHuddles": 5,
"maxDaysBetweenHuddles": 7
"idealFrequency": 1,
"minFrequency": 1,
"maxFrequency": 1
}, {
"minScore": 3,
"maxScore": 3,
"minDaysBetweenHuddles": 15,
"maxDaysBetweenHuddles": 21
"idealFrequency": 3,
"minFrequency": 2,
"maxFrequency": 4
}, {
"minScore": 2,
"maxScore": 2,
"minDaysBetweenHuddles": 36,
"maxDaysBetweenHuddles": 42
"idealFrequency": 6,
"minFrequency": 4,
"maxFrequency": 8
}, {
"minScore": 1,
"maxScore": 1,
"minDaysBetweenHuddles": 85,
"maxDaysBetweenHuddles": 91
"idealFrequency": 13,
"minFrequency": 10,
"maxFrequency": 16
}
]
},
Expand Down
15 changes: 9 additions & 6 deletions config/simple_huddle_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
{
"minScore": 6,
"maxScore": 10,
"minDaysBetweenHuddles": 5,
"maxDaysBetweenHuddles": 7
"idealFrequency": 1,
"minFrequency": 1,
"maxFrequency": 1
}, {
"minScore": 4,
"maxScore": 5,
"minDaysBetweenHuddles": 12,
"maxDaysBetweenHuddles": 14
"idealFrequency": 2,
"minFrequency": 1,
"maxFrequency": 3
}, {
"minScore": 1,
"maxScore": 3,
"minDaysBetweenHuddles": 25,
"maxDaysBetweenHuddles": 28
"idealFrequency": 4,
"minFrequency": 2,
"maxFrequency": 6
}
]
},
Expand Down
39 changes: 25 additions & 14 deletions docs/huddle_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ The following annotated huddle configuration file describes the configurable poi
you do not wish to use risk scores as a factor for scheduling huddles. */
"riskConfig": {
/* RiskMethod needs to correspond to the method code used in the FHIR RiskAssessment resources. It will be used
to lookup the risk scores of patients. */
to lookup the risk scores of patients. */
"riskMethod": {"system": "http://interventionengine.org/risk-assessments", "code": "ExampleRisk"},
/* FrequencyConfigs is a list of configurations indicating the frequency at which patients should be scheduled,
based on their risk score. Usually, the higher the score, the more frequently they should be scheduled.
based on their risk score. Usually, the higher the score, the more frequently they should be scheduled.
This example uses a fiction risk scoring algorithm that range from 0 to 20. */
"frequencyConfigs": [
/* This config indicates that patients with a risk score of 18-20 should be discussed about once a week. */
Expand All @@ -34,33 +34,41 @@ The following annotated huddle configuration file describes the configurable poi
"minScore": 18,
/* MaxScore indicates the high (inclusive) value of the risk score indicating this frequency configuration. */
"maxScore": 20,
/* MinDaysBetweenHuddles indicates the smallest number of days that should pass between huddles in which this
patient is discussed. In other words, discuss this patient no more frequently than every 5 days. */
"minDaysBetweenHuddles": 5,
/* MaxDaysBetweenHuddles indicates the largest number of days that should pass between huddles in which this
patient is discussed. In other words, discuss this patient no less frequently than every 7 days. */
"maxDaysBetweenHuddles": 7
/* IdealFrequency indicates the ideal huddle frequency for scheduling patients (e.g., every n huddles).
The scheduler will attempt to accomplish this frequency if possible. */
"idealFrequency": 1,
/* MinFrequency indicates the minimum huddle frequency for scheduling patients (e.g., no more frequent than
every n huddles). The scheduler may use MinFrequency to help ensure an even distribution of patients
across huddles. */
"minFrequency": 1,
/* MaxFrequency indicates the maximum huddle frequency for scheduling patients (e.g., no less frequent than
every n huddles). The scheduler may use MaxFrequency to help ensure an even distribution of patients
across huddles. */
"maxFrequency": 1
},
/* This config indicates that patients with a risk score of 12-17 should be discussed about once every 3 weeks. */
{
"minScore": 12,
"maxScore": 17,
"minDaysBetweenHuddles": 15,
"maxDaysBetweenHuddles": 21
"idealFrequency": 3,
"minFrequency": 2,
"maxFrequency": 4
},
/* This config indicates that patients with a risk score of 5-11 should be discussed about once every 6 weeks. */
{
"minScore": 5,
"maxScore": 11,
"minDaysBetweenHuddles": 36,
"maxDaysBetweenHuddles": 42
"idealFrequency": 6,
"minFrequency": 4,
"maxFrequency": 8
},
/* This config indicates that patients with a risk score of 0-4 should be discussed about once every 13 weeks. */
{
"minScore": 0,
"maxScore": 4,
"minDaysBetweenHuddles": 85,
"maxDaysBetweenHuddles": 91
"idealFrequency": 13,
"minFrequency": 9,
"maxFrequency": 17
}
]
},
Expand Down Expand Up @@ -118,6 +126,9 @@ The following annotated huddle configuration file describes the configurable poi
}
]
},
/* RollOverDelayInDays indicates how many days to wait before rolling over undiscussed patients to the next huddle.
If it is 0 (or less) then rollovers are disabled. */
"rollOverDelayInDays": 3,
/* SchedulerCronSpec indicates when the scheduling algorithm should be run. The six digits corresond to seconds,
minutes, hours, day of month, month, day of week. In the example below, the algorithm is run every day at
00:00:00. For more information, see: https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format */
Expand Down
15 changes: 9 additions & 6 deletions fixtures/huddle_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
{
"minScore": 6,
"maxScore": 10,
"minDaysBetweenHuddles": 5,
"maxDaysBetweenHuddles": 7
"idealFrequency": 1,
"minFrequency": 1,
"maxFrequency": 1
}, {
"minScore": 4,
"maxScore": 5,
"minDaysBetweenHuddles": 12,
"maxDaysBetweenHuddles": 14
"idealFrequency": 2,
"minFrequency": 1,
"maxFrequency": 3
}, {
"minScore": 1,
"maxScore": 3,
"minDaysBetweenHuddles": 25,
"maxDaysBetweenHuddles": 28
"idealFrequency": 4,
"minFrequency": 2,
"maxFrequency": 6
}
]
},
Expand Down
165 changes: 124 additions & 41 deletions huddles/huddle.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
package huddles

import "github.com/intervention-engine/fhir/models"
import (
"fmt"
"time"

"github.com/intervention-engine/fhir/models"
"gopkg.in/mgo.v2/bson"
)

// Huddle provides convenient functions on a Group to get access to extended huddle data fields
type Huddle models.Group

// NewHuddle constructs a new Huddle using the provided information
func NewHuddle(name string, leaderID string, date time.Time) *Huddle {
tru := true
huddle := Huddle(models.Group{
DomainResource: models.DomainResource{
Resource: models.Resource{
Id: bson.NewObjectId().Hex(),
ResourceType: "Group",
Meta: &models.Meta{
Profile: []string{"http://interventionengine.org/fhir/profile/huddle"},
},
},
Extension: []models.Extension{
{
Url: "http://interventionengine.org/fhir/extension/group/activeDateTime",
ValueDateTime: &models.FHIRDateTime{Time: date, Precision: models.Precision(models.Date)},
},
{
Url: "http://interventionengine.org/fhir/extension/group/leader",
ValueReference: &models.Reference{
Reference: "Practitioner/" + leaderID,
ReferencedID: leaderID,
Type: "Practitioner",
External: new(bool),
},
},
},
},
Type: "person",
Actual: &tru,
Code: &models.CodeableConcept{
Coding: []models.Coding{
{System: "http://interventionengine.org/fhir/cs/huddle", Code: "HUDDLE"},
},
Text: "Huddle",
},
Name: name,
})

return &huddle
}

// IsHuddle checks the Group's code to ensure it has the proper Huddle code
func (h *Huddle) IsHuddle() bool {
return h.Code.MatchesCode("http://interventionengine.org/fhir/cs/huddle", "HUDDLE")
Expand Down Expand Up @@ -48,55 +96,90 @@ func (h *Huddle) FindHuddleMember(patientID string) *HuddleMember {
return nil
}

// HuddleMember provides convenient functions on a GroupMemberComponent to get access to extended huddle data fields
type HuddleMember models.GroupMemberComponent

// ID returns the members ID
func (h *HuddleMember) ID() string {
if h.Entity == nil {
return ""
}
return h.Entity.ReferencedID
}

// Reason returns the reason the member was added to the huddle (or nil if the reason isn't set)
func (h *HuddleMember) Reason() *models.CodeableConcept {
reason := findExtension(h.Extension, "http://interventionengine.org/fhir/extension/group/member/reason")
if reason != nil {
return reason.ValueCodeableConcept
}
return nil
// AddHuddleMemberDueToRiskScore adds the patient to the huddle using RISK_SCORE as the reason. If the
// patient is already in the huddle, nothing will be updated.
func (h *Huddle) AddHuddleMemberDueToRiskScore(patientID string) {
h.addHuddleMember(patientID, &models.CodeableConcept{
Coding: []models.Coding{
{System: "http://interventionengine.org/fhir/cs/huddle-member-reason", Code: "RISK_SCORE"},
},
Text: "Risk Score Warrants Discussion",
})
}

// ReasonIsManuallyAdded indicates if the member reason is due to the patient being manually added to the huddle
func (h *HuddleMember) ReasonIsManuallyAdded() bool {
reason := h.Reason()
return reason != nil && reason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "MANUAL_ADDITION")
// AddHuddleMemberDueToRecentEvent adds the patient to the huddle using RECENT_ENCOUNTER event code as the reason.
// If the patient is already in the huddle, nothing will be updated.
func (h *Huddle) AddHuddleMemberDueToRecentEvent(patientID string, code EventCode) {
h.addHuddleMember(patientID, &models.CodeableConcept{
Coding: []models.Coding{
{System: "http://interventionengine.org/fhir/cs/huddle-member-reason", Code: "RECENT_ENCOUNTER"},
},
Text: code.Name,
})
}

// ReasonIsRecentEncounter indicates if the member reason is due to a recent significant encounter
func (h *HuddleMember) ReasonIsRecentEncounter() bool {
reason := h.Reason()
return reason != nil && reason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "RECENT_ENCOUNTER")
// AddHuddleMemberDueToRollOver adds the patient to the huddle using the ROLLOVER and previous reason.
// If the patient is already in the huddle, nothing will be updated.
func (h *Huddle) AddHuddleMemberDueToRollOver(patientID string, from time.Time, previousReason *models.CodeableConcept) {
var reason string
if previousReason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "ROLLOVER") {
reason = previousReason.Text
} else if previousReason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "MANUAL_ADDITION") {
reason = fmt.Sprintf("Rolled Over from %s (Manually Added - %s)", from.Format("Jan 2"), previousReason.Text)
} else {
reason = fmt.Sprintf("Rolled Over from %s (%s)", from.Format("Jan 2"), previousReason.Text)
}
h.addHuddleMember(patientID, &models.CodeableConcept{
Coding: []models.Coding{
{System: "http://interventionengine.org/fhir/cs/huddle-member-reason", Code: "ROLLOVER"},
},
Text: reason,
})
}

// ReasonIsRiskScore indicates if the member reason is due to the patient's current risk score
func (h *HuddleMember) ReasonIsRiskScore() bool {
reason := h.Reason()
return reason != nil && reason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "RISK_SCORE")
}
func (h *Huddle) addHuddleMember(patientID string, reason *models.CodeableConcept) {
// First look to see if the patient is already in the group and act accordingly.
existing := h.FindHuddleMember(patientID)
if existing != nil {
if existing.ReasonIsRollOver() {
// We allow overwrites of rollovers, so remove the existing entry and continue
h.RemoveHuddleMember(patientID)
} else {
// We don't allow overwrites of other reasons, so just ignore this request
return
}
}

// ReasonIsRollOver indicates if the member reason is due to roll over from a previous huddle
func (h *HuddleMember) ReasonIsRollOver() bool {
reason := h.Reason()
return reason != nil && reason.MatchesCode("http://interventionengine.org/fhir/cs/huddle-member-reason", "ROLLOVER")
// The patient is not yet in the group, so add him/her
h.Member = append(h.Member, models.GroupMemberComponent{
BackboneElement: models.BackboneElement{
Element: models.Element{
Extension: []models.Extension{
{
Url: "http://interventionengine.org/fhir/extension/group/member/reason",
ValueCodeableConcept: reason,
},
},
},
},
Entity: &models.Reference{
Reference: "Patient/" + patientID,
ReferencedID: patientID,
Type: "Patient",
External: new(bool),
},
})
}

// Reviewed returns the date that the member was reviewed for this huddle (or nil if they haven't been reviewed)
func (h *HuddleMember) Reviewed() *models.FHIRDateTime {
reviewed := findExtension(h.Extension, "http://interventionengine.org/fhir/extension/group/member/reviewed")
if reviewed != nil {
return reviewed.ValueDateTime
// RemoveHuddleMember removes the requested huddle member and returns the removed member.
// If no matching member is found, it returns nil.
func (h *Huddle) RemoveHuddleMember(patientID string) *HuddleMember {
for i := range h.Member {
if h.Member[i].Entity.ReferencedID == patientID {
hm := HuddleMember(h.Member[i])
h.Member = append(h.Member[:i], h.Member[i+1:]...)
return &hm
}
}
return nil
}
Expand Down
25 changes: 21 additions & 4 deletions huddles/huddle_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,27 @@ type ScheduleByRiskConfig struct {

// RiskScoreFrequencyConfig represents the relationship between risk scores and frequency of huddle discussion
type RiskScoreFrequencyConfig struct {
MinScore float64
MaxScore float64
MinDaysBetweenHuddles int
MaxDaysBetweenHuddles int
MinScore float64
MaxScore float64
IdealFrequency int
MinFrequency int
MaxFrequency int
}

// FindRiskScoreFrequencyConfigByScore finds the proper risk config for a given score
func (hc *HuddleConfig) FindRiskScoreFrequencyConfigByScore(score float64) *RiskScoreFrequencyConfig {
if hc.RiskConfig == nil {
return nil
}

for i := range hc.RiskConfig.FrequencyConfigs {
fc := hc.RiskConfig.FrequencyConfigs[i]
if score >= fc.MinScore && score <= fc.MaxScore {
return &fc
}
}

return nil
}

// ScheduleByEventConfig represents how recent events should influence huddle population
Expand Down
Loading

0 comments on commit 865fd16

Please sign in to comment.