Skip to content

Commit

Permalink
Merge pull request #828 from GabrielKS/expectations
Browse files Browse the repository at this point in the history
Basic server-side implementation of configurable expectation proposal
  • Loading branch information
shankari authored Jul 19, 2021
2 parents ef04ba4 + cb8eb03 commit ed2adbb
Show file tree
Hide file tree
Showing 20 changed files with 915 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ CFC_DataCollector/moves_collect.log

webapp/www/lib
conf/**/*.json
!conf/**/*.schema.json

*.ipynb_checkpoints*

Expand Down
87 changes: 87 additions & 0 deletions conf/ux/expectations.conf.json.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"$schema": "./expectations.conf.schema.json",
"modes": [
{
"label": "intensive",
"schedule": {
"startDate": "2021-06-01T20:00:00.000",
"recurrenceUnit": "months",
"recurrenceValue": 1,
"duration": 7
},
"confidenceThreshold": 0.65,
"rules": [
{
"trigger": -1,
"expect": {
"type": "all"
},
"notify": {
"type": "dayEnd"
}
},
{
"trigger": 1,
"expect": {
"type": "all"
},
"notify": {
"type": "weekDay",
"value": 5
}
},
{
"trigger": 0.75,
"expect": {
"type": "all"
},
"notify": {
"type": "dayEnd"
}
}
]
},
{
"label": "relaxed",
"confidenceThreshold": 0.55,
"rules": [
{
"trigger": -1,
"expect": {
"type": "randomDays",
"value": 2
},
"notify": {
"type": "dayEnd"
}
},
{
"trigger": 1,
"expect": {
"type": "none"
}
},
{
"trigger": 0.95,
"expect": {
"type": "randomFraction",
"value": 0.05
},
"notify": {
"type": "dayEnd"
}
},
{
"trigger": 0.75,
"expect": {
"type": "randomDays",
"value": 2
},
"notify": {
"type": "dayEnd"
}
}
]
}
]
}
220 changes: 220 additions & 0 deletions conf/ux/expectations.conf.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"title": "Expectations and Notifications",
"description": "Specifies which trips users are expected to label and when they should be reminded to do so",
"type": "object",
"properties": {
"modes": {
"description": "The various modes we can be in and their specifications. The active mode is the first mode in this list with a schedule matching the current date or with no schedule.",
"type": "array",
"items": {
"$ref": "#/definitions/modeType"
},
"contains": {
"$comment": "Must contain a \"default\" element -- one with no schedule",
"not": {"required": ["schedule"]}
}
}
},
"required": ["modes"],
"definitions": {
"modeType": {
"description": "Specifies a collection mode that defines expectation and notification settings for a given time period",
"type": "object",
"properties": {
"enabled": {
"description": "Whether or not to enable this mode; mode is enabled if this property is omitted",
"type": "boolean"
},
"label": {
"description": "A human-readable label for the mode",
"type": "string"
},
"confidenceThreshold": {
"description": "Only display yellow labels with confidence greater than this threshold (0 to display all, 1 to display none)",
"type": "number",
"minimum": 0,
"maximum": 1
},
"schedule": {
"$ref": "#/definitions/scheduleType"
},
"rules": {
"description": "A list of rules that define the expectation and notification settings for label types",
"type": "array",
"items": {
"$ref": "#/definitions/ruleType"
},
"allOf": [
{
"contains": {
"$comment": "Must contain a rule for red labels",
"properties": {"trigger": {"const": -1}}
}
},
{
"contains": {
"$comment": "Must contain a rule for all yellow labels",
"properties": {"trigger": {"const": 1}}
}
}
]
}
},
"required": ["label", "confidenceThreshold", "rules"]
},
"scheduleType": {
"description": "Describes when the mode is active; omit to make always active",
"type": "object",
"properties": {
"startDate": {
"description": "The base date from which to measure time; omit time zone to signify user's local time",
"$comment": "Officially, the \"date-time\" format is supposed to be RFC 3339. It breaks RFC 3339 (but not ISO 8601) to omit a time zone. My current design decision is to accept this rather than add a whole other field.",
"type": "string",
"format": "date-time"
},
"recurrenceUnit": {
"description": "The units for recurrenceValue",
"type": "string",
"enum": ["days", "weeks", "months"]
},
"recurrenceValue": {
"description": "How often the mode recurs",
"type": "integer",
"minimum": 1
},
"duration": {
"description": "How long the mode lasts in days",
"type": "integer",
"minimum": 1
}
},
"required": ["startDate", "recurrenceUnit", "recurrenceValue", "duration"]
},
"ruleType": {
"description": "Specifies the expectation and notification settings for a given label type trigger",
"type": "object",
"properties": {
"trigger": {
"description": "The type of label that this rule applies to: -1 for red labels, 0.0<=x<=1.0 for all yellow labels with confidence <= x, regardless of whether confidenceThreshold allows us to display them (1 for all yellow labels)",
"type": "number",
"anyOf": [
{"minimum": 0, "maximum": 1},
{"const": -1}
]
},
"expect": {
"$ref": "#/definitions/expectType"
},
"notify": {
"$ref": "#/definitions/notifyType"
}
},
"required": ["trigger", "expect"],
"allOf": [
{
"$comment": "If no labeling expectation, do not send a notification; should not even configure",
"if": {"properties": {"expect": {"properties": {"type": {"const": "none"}}}}},
"then": {"not": {"required": ["notify"]}}
},
{
"$comment": "The only way for a \"notify\" field to not exist is if there is no labeling expectation",
"then": {"properties": {"expect": {"properties": {"type": {"const": "none"}}}}},
"if": {"not": {"required": ["notify"]}}
}
]
},
"expectType": {
"description": "Specifies whether the user must label all of the trips with the associated trigger, none of them, or somewhere in between",
"type": "object",
"properties": {
"type": {
"description": "\"all\": must label all trips\n\"randomFraction\": must label a certain fraction of trips\n\"randomDays\": must label for a certain number of days per week\n\"none\": not expected to label any trips",
"type": "string",
"enum": ["all", "randomFraction", "randomDays", "none"]
},
"value": {
"$comment": "If \"type\" is \"randomFraction\": this is the fraction (0.0, 1.0) of trips that must be labeled\nIf \"type\" is \"randomDays\": this is the number of days per week [1, 6] for which trips must be labeled"
}
},
"allOf": [
{
"if": {"properties": {"type": {"enum": ["all", "none"]}}},
"then": {"not": {"required": ["value"]}}
},
{
"if": {"properties": {"type": {"const": "randomFraction"}}},
"then": {
"properties": {
"value": {
"description": "The fraction (0.0, 1.0) of trips that must be labeled",
"type": "number",
"exclusiveMinimum": 0,
"exclusiveMaximum": 1
}
},
"required": ["value"]
}
},
{
"if": {"properties": {"type": {"const": "randomDays"}}},
"then": {
"properties": {"value": {"description": "The number of days per week [1, 6] for which trips must be labeled", "type": "integer", "minimum": 1, "maximum": 6}},
"required": ["value"]
}
}
]
},
"notifyType": {
"description": "Specifies when the user should be notified to complete the expected labeling, if at all",
"type": "object",
"properties": {
"type": {
"description": "\"immediately\": notify as soon as the label data comes in\n\"dayEnd\": notify at the end of each day\n\"nDays\": notify every certain number of days\n\"weekDay\": notify on a certain day of the week\n\"none\": do not notify",
"type": "string",
"enum": ["immediately", "dayEnd", "nDays", "weekDay", "none"]
},
"value": {
"$comment": "If \"type\" is \"nDays\": this is the number [1, Infinity) of days we wait in between notifications\nIf \"type\" is \"weekDay\": this is the number of the day [1=Monday, 7=Sunday] on which we notify"
}
},
"allOf": [
{
"if": {"properties": {"type": {"enum": ["immediately", "dayEnd", "none"]}}},
"then": {"not": {"required": ["value"]}}
},
{
"if": {"properties": {"type": {"const": "nDays"}}},
"then": {
"properties": {
"value": {
"description": "The number [1, Infinity) of days we wait in between notifications",
"type": "integer",
"minimum": 1
}
},
"required": ["value"]
}
},
{
"if": {"properties": {"type": {"const": "weekDay"}}},
"then": {
"properties": {
"value": {
"description": "The number of the day [1=Monday, 7=Sunday] on which we notify",
"type": "integer",
"minimum": 1,
"maximum": 7
}
},
"required": ["value"]
}
},
{
"if": {"properties": {"type": {"const": "none"}}},
"then": {"not": {"required": ["value"]}}
}
]
}
}
}
38 changes: 38 additions & 0 deletions emission/analysis/classification/inference/labels/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def placeholder_predictor_1(trip):
# This third scenario provides labels designed to test the soundness and resilience of
# the client-side inference processing algorithms.
def placeholder_predictor_2(trip):
# Timestamp2index gives us a deterministic way to match test trips with labels
# Hardcoded to match "test_july_22" -- clearly, this is just for testing
timestamp2index = {494: 5, 565: 4, 795: 3, 805: 2, 880: 1, 960: 0}
timestamp = trip["data"]["start_local_dt"]["hour"]*60+trip["data"]["start_local_dt"]["minute"]
Expand Down Expand Up @@ -87,6 +88,43 @@ def placeholder_predictor_2(trip):
]
][index]


# This fourth scenario provides labels designed to test the expectation and notification system.
def placeholder_predictor_3(trip):
timestamp2index = {494: 5, 565: 4, 795: 3, 805: 2, 880: 1, 960: 0}
timestamp = trip["data"]["start_local_dt"]["hour"]*60+trip["data"]["start_local_dt"]["minute"]
index = timestamp2index[timestamp] if timestamp in timestamp2index else 0
return [
[
{"labels": {"mode_confirm": "bike", "purpose_confirm": "work"}, "p": 0.80},
{"labels": {"mode_confirm": "walk", "purpose_confirm": "shopping"}, "p": 0.20}
],
[
{"labels": {"mode_confirm": "bike", "purpose_confirm": "work"}, "p": 0.80},
{"labels": {"mode_confirm": "walk", "purpose_confirm": "shopping"}, "p": 0.20}
],
[
{"labels": {"mode_confirm": "drove_alone", "purpose_confirm": "entertainment"}, "p": 0.70},
],
[
{"labels": {"mode_confirm": "bike", "purpose_confirm": "work"}, "p": 0.96},
{"labels": {"mode_confirm": "walk", "purpose_confirm": "shopping"}, "p": 0.04}
],
[
{"labels": {"mode_confirm": "walk", "purpose_confirm": "shopping"}, "p": 0.45},
{"labels": {"mode_confirm": "walk", "purpose_confirm": "entertainment"}, "p": 0.35},
{"labels": {"mode_confirm": "drove_alone", "purpose_confirm": "work"}, "p": 0.15},
{"labels": {"mode_confirm": "shared_ride", "purpose_confirm": "work"}, "p": 0.05}
],
[
{"labels": {"mode_confirm": "walk", "purpose_confirm": "shopping"}, "p": 0.60},
{"labels": {"mode_confirm": "walk", "purpose_confirm": "entertainment"}, "p": 0.25},
{"labels": {"mode_confirm": "drove_alone", "purpose_confirm": "work"}, "p": 0.11},
{"labels": {"mode_confirm": "shared_ride", "purpose_confirm": "work"}, "p": 0.04}
]
][index]


# For each algorithm in ecwl.AlgorithmTypes that runs on a trip (e.g., not the ensemble, which
# runs on the results of other algorithms), primary_algorithms specifies a corresponding
# function to run. This makes it easy to plug in additional algorithms later.
Expand Down
Loading

0 comments on commit ed2adbb

Please sign in to comment.