GithubHelp home page GithubHelp logo

flow-spec's People

Contributors

bzabos avatar lakhbawa avatar markboots avatar nancywinder avatar nditada avatar nicpottier avatar pld avatar rowanseymour avatar seifertk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flow-spec's Issues

TypeScript / JS primitive engine

I think it would be really useful to have an implementation of a primitive engine that ran in browser, would help in prototyping and demonstration behavior.

In my ideal world we could embed this in the specs to show behavior of how more compound blocks work and make that interactive!

Generalize Quick Replies for WhatsApp and other RICH_MESSAGING channels

Quick Replies are an important part of effective chatbot user experience, to guide contacts on how to respond, and to accelerate interacting with flows. Many chatbot channels (WhatsApp, Facebook Messenger, Telegram, etc.) support Quick Replies. For instance, "Reply Buttons" and "List Messages" are available on the WhatsApp Business API.

quick_replies_whatsapp

Until now, the only standard Flow Block that supported Quick Replies was the Select One block: its choices could be easily presented as quick replies. However, this is limited in two ways:

  • Other input blocks (Open Response, Numeric, etc.) may want to suggest Quick Replies when useful possibilities are known.
  • There is an emerging use-case where Quick Replies are loaded dynamically (at runtime) from a third-party service (e.g. via Webhook request). Using Select One block here for its existing Quick Reply capability runs counter to the goal of Select One, which is to select from discrete options that are structured within the Flow at authoring time, rather than runtime.

Proposal: To add an optional config parameter quick_replies to all blocks, scoped to the RICH_MESSAGING channel, with an array of quick replies to be presented to the user. To support dynamic loading, this can either be an expression (which is expected to return an array), or an array of resources. (Resources are necessary for content localization into the contact's language.)

Parameter: block.config.RICH_MESSAGING.quick_replies.expression or block.config.RICH_MESSAGING.quick_replies.resources:

{
  "blocks": [
    {
      // Example 1: Loaded dynamically from an expression
      // ...
      "config": {
        // ...
        "RICH_MESSAGING": {
          "quick_replies": {
            "expression": "@results.q3_webhook.value"
          }
        }
      }
    },
    {
      // Example 2: From an array of localized resources
      // ...
      "config": {
        // ...
        "RICH_MESSAGING": {
          "quick_replies": {
            "resources": [
              "c0eb929e-8cb0-45bd-826f-9c0a0e3b988c",
              "bd6274bf-5f22-4394-8ae7-ea3c8143a4f0",
              "78f8b5ef-7f4f-42c2-8f3a-bb2c85649609"
            ]
          }
        }
      }
    }
  ]
}

The channel driver would be responsible for determining the presentation (and the viability) of how the quick replies would be implemented on a particular channel/chatbot. For example, on WhatsApp, if the number of quick_replies is up to 3, they can be presented as Reply Buttons; if up to 10, they can be presented as a List Message. If more than 10, another approach (or no display) would be the result. Quick Reply capabilities and limits vary by chatbot channel, so they should not be relied on for flow functionality, only to improve the contact's user experience.

@smn, any additions from your team?

Remove `block.exit.config` , as it seems to be unused and unnecessary

The config field on Block Exits seems to be unused and unnecessary in implementations so far. We suggest removing this field. To provided extensibility on exits (if required), we could introduce vendor_metadata, for consistency with all other significant entities in the specification.

SelectOne and SelectMany (Multiple choice) blocks: Need an independent way to determine the choice selected, separate from the exit configuration

Short version of the problem:

SelectOne and SelectMany require the contact to pick a choice(s) from the set of choices, which determines the output value of the block. How a contact's raw response is mapped to a choice could depend on the language and mode. Currently the spec is lacking a way to specify this mapping, other than through exit test configurations. This is a problem because someone might want to create a SelectOne block that has a single exit for all valid responses (or a different combo of exits), but still needs to specify how a raw response maps to a choice selection.

Example:

Consider a SelectOne with 3 choices: What's your favorite ice cream?

  • chocolate
  • vanilla
  • strawberry

The value of the block (available via block.value, and within expressions in future blocks) needs to be set to one of the above. The exit tests below suggest how that might be determined from the contact input (block.response) based on the current language and mode:

"exits": [
    {
      "uuid": "95fd672c-92e9-4352-b761-7008b27cbe26",
      "test":
        "OR(
          AND(flow.mode = 'IVR', block.response = 7), 
          AND(flow.mode != 'IVR', 
            OR(block.response = 1, 
              AND(flow.language = 'eng', OR(lower(block.response) = 'chocolate'), lower(block.response) = 'c'), lower(block.response) = '1')), 
              AND(flow.language = 'fre', OR(lower(block.response) = 'chocolat', lower(block.response) = 'c', lower(block.response) = '1'))
            )
          )
        )",
      "label": "b0f6d3ec-b9ec-4761-b280-6777d965deab",
      "name": "chocolate",
    },
    {
      "uuid": "9fab760c-a680-4e40-83b7-9b3f8c66ccdb",
      "test":
        "OR(
          AND(flow.mode = 'IVR', block.response = 8), 
          AND(flow.mode != 'IVR', 
            OR( 
              AND(flow.language = 'eng', OR(lower(block.response) = 'vanilla'), lower(block.response) = 'v'), lower(block.response) = '2')), 
              AND(flow.language = 'fre', OR(lower(block.response) = 'vanille', lower(block.response) = 'v', lower(block.response) = '2'))
            )
          )
        )",
      "label": "b75fa302-8ff7-4f49-bf26-8f915e807222",
      "name": "vanilla",
    },
    {
      "uuid": "d99d43ec-6f0a-42b4-97f9-aa1c50ddebe0",
      "test": 
        "OR(
          AND(flow.mode = 'IVR', block.response = 9), 
          AND(flow.mode != 'IVR', 
            OR(
              AND(flow.language = 'eng', OR(lower(block.response) = 'strawberry'), lower(block.response) = 's'), lower(block.response) = '3')), 
              AND(flow.language = 'fre', OR(lower(block.response) = 'fraise', lower(block.response) = 'f', lower(block.response) = '3'))
            )
          )
        )",
      "label": "22619b04-b06d-483e-af83-ee3ba9c8c867",
      "name": "strawberry",
    }
    {
      "uuid": "78012084-b811-4177-88ea-5de5d3eba57d",
      "default": true,
      "label": "10a11345-9575-4e4a-bf61-0e04758626e7",
      "name": "Default",
    },
  ]

However, the problem here is that these are exit tests: they determine the exit used, not the block.value. Exits are also configurable -- one could choose to create a different combination of exits, and we would still need to resolve the selected choice.

Proposed solution

  1. Expand the choices array in the config for SelectOne and SelectMany. Currently it is a mapping from choice keys to the resource that presents these choices to a contact on different languages and modes:
  "config": {
    "prompt": "42095857-6782-425d-809b-4226c4d53d4d",
    "choices": {
      "chocolate": "b0f6d3ec-b9ec-4761-b280-6777d965deab",
      "vanilla": "b75fa302-8ff7-4f49-bf26-8f915e807222",
      "strawberry": "22619b04-b06d-483e-af83-ee3ba9c8c867"
    }
  }

This could be updated to have a name (choice key), resource, and test for each choice. An expression test here would be used to define the choice name that is put into block.value based on the contact's response.

{
  "type": "MobilePrimitives.SelectOneResponse",
  "name": "favorite_ice_cream",
  "label": "Favorite Ice Cream",
  "config": {
    "prompt": "42095857-6782-425d-809b-4226c4d53d4d",
    "choices": [
      {
        "name": "Chocolate",
        "resource": "b0f6d3ec-b9ec-4761-b280-6777d965deab",
        "test": "OR(
            AND(flow.mode = 'IVR', block.response = 7), 
            AND(flow.mode != 'IVR', 
              OR(
                AND(flow.language = 'eng', OR(lower(block.response) = 'chocolate'), lower(block.response) = 'c', lower(block.response) = '1'), 
                AND(flow.language = 'fre', OR(lower(block.response) = 'chocolat'), lower(block.response) = 'c', lower(block.response) = '1'))
              )
            )
          )"
      },
      {
        "name": "Vanilla",
        "resource": "b75fa302-8ff7-4f49-bf26-8f915e807222",
        "test": "OR(
            AND(flow.mode = 'IVR', block.response = 8), 
            AND(flow.mode != 'IVR', 
              OR(
                AND(flow.language = 'eng', OR(lower(block.response) = 'vanilla'), lower(block.response) = 'v', lower(block.response) = '1'), 
                AND(flow.language = 'fre', OR(lower(block.response) = 'vanille'), lower(block.response) = 'v', lower(block.response) = '1'))
              )
            )
          )"
      },
      {
        "name": "Strawberry",
        "resource": "22619b04-b06d-483e-af83-ee3ba9c8c867",
        "test": "OR(
            AND(flow.mode = 'IVR', block.response = 9), 
            AND(flow.mode != 'IVR', 
              OR(
                AND(flow.language = 'eng', OR(lower(block.response) = 'strawberry'), lower(block.response) = 's', lower(block.response) = '3'), 
                AND(flow.language = 'fre', OR(lower(block.response) = 'fraise'), lower(block.response) = 'f', lower(block.response) = '3'))
              )
            )
          )"
      }
    ]
  },
  "exits": [
    {
      "uuid": "95fd672c-92e9-4352-b761-7008b27cbe26",
      "test": "OR(block.value = Chocolate, block.value = Vanilla, block.value = Strawberry)",
      "label": "35f4f314-2ea1-49fa-99ec-36fab034b5b5",
      "name": "1",
    },
    {
      "uuid": "78012084-b811-4177-88ea-5de5d3eba57d",
      "default": true,
      "label": "10a11345-9575-4e4a-bf61-0e04758626e7",
      "name": "Default",
    },
  ]
}
  1. Expressions would support the following. Assuming the name of this block was "favorite_ice_cream", the following expressions would evaluate as:
  • flow.favorite_ice_cream.value // "strawberry" (name of the choice selected on the block.)

New fields to add support for on expressions:

  • flow.favorite_ice_cream.response // Potentially "3" or "fraise" -- Access the raw input that the contact provided in response to the block.
  • flow.favorite_ice_cream.exit // "strawberry" -- Access the name of the exit passed through out of the block. (Note that exit names and exit configurations are not necessarily linked to the choices.)

Upgrade the structure of Resources for block media

Rationale:

We've encountered a need to have multiple pieces of media within a resource. These multiple pieces of media might provide different formats (ie: ContentTypes) for a single mode (e.g. rich_messaging).

  • e.g. A chatbot block may need to provide an image AND text, or an image and an audio file.

There is also a need to specify different content for different modes,

  • e.g. A flow builder may want to specify different text-format content for the SMS channel (ie: shortened) vs. the text-format content for the rich_messaging channel.

The proposed upgrade to the Resource structure to support this is:

Example:

Resource {
   uuid: "c2dbafbd-e9bd-408f-aabc-25cf67040002",
   values: [
      {
         language_id: "47",
         modes: ["sms", "ussd"],
         content_type: "text",
         mime_type: "text/plain",
         value: "Howdy! You've reached the Museum of Interoperability!"
      },
      {
         language_id: "47",
         modes: ["rich_messaging"],
         content_type: "text",
         mime_type: "text/plain",
         value: "Howdy! You've reached the Museum of Interoperability! This is a long message for you because we've gone beyond the limitations for 180 characters. I'm your guide, Florian. I hope you're excited for this two hour tour through the history of interoperable data systems."
      },
      {
         language_id: "47",
         modes: ["rich_messaging"],
         content_type: "image",
         mime_type: "image/png",
         value: "https://your-server-somewhere.flowinteroperability.org/example-image.png"
      }
   ]
}

Structure:

Resource {
  uuid: string,
  values: ResourceVariant[]
}

ResourceVariant {
  language_id: string,
  content_type: SupportedContentType,
  mime_type: string,
  modes: SupportedMode[],
  value: string,
}

SupportedContentType {
  TEXT = 'text',
  AUDIO = 'audio',
  IMAGE = 'image',
  VIDEO = 'video',
}

SupportedMode {
  SMS = 'sms',
  USSD = 'ussd',
  IVR = 'ivr',
  RICH_MESSAGING = 'rich_messaging',
  OFFLINE = 'offline',
}

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

Need primitive for block level variables

A few of the constructs we need to support, specifically limited looping (repeat 3 times, randomly repeat 3 times) would be cleaner with the idea of a scope/context that is limited to the block.

Expression Proposal: `Between` - Checks if given `date-time` is within given `starting date-time` and `ending date-time`

Type of Expression: Date Function
Return Type: Boolean
Case Sensitive: NA

Expression Example:
@between(NOW(), '2024-01-20 20:22:5', '2025-01-20 20:22:5') -> FALSE # When We are in 2023
@between(NOW(), '2022-01-20 20:22:5', '2025-01-20 20:22:5') -> TRUE # When We are in 2023

Common Use Case:
This expression can be used to make the block exits more dynamic by choosing the exit based on the current datetime/given datetime

Remove prompt_audio, message_audio, etc. from MobilePrimitives\Message and related blocks

The point of resources is to contain content for different modes. Therefore, there should just be one resource (prompt), for IVR and SMS and other channels, rather than prompt + prompt_audio

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

New Block Type: MobilePrimitives\SelectManyResponse

Rationale: This is a common question type, especially for rich_messaging and offline data collection apps. It's also present as a question type in Flow Results (https://floip.gitbook.io/flow-results-specification/specification#select_many) and should likely be supported in the Flow Spec. It is general enough to exist across vendors, and would be challenging/impossible to compose out of individual blocks.

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

Upgrades to the Expressions spec

The following proposals to the Expressions spec are useful to support validation of data in exit tests, and do meaningful operations with array data within the Context (e.g. within a Contact):

  1. Add ISNUMBER(), ISBOOL(), and ISSTRING() to support validation of data received in block exit tests
  2. Add NULL type, and the ability to test against NULL
  3. Add BETWEEN() function to simplify comparisons within a range
  4. Add CONTAINS() function to enable keyword matching within strings
  5. Array handling: ARRAY() generator function, and IN() function for testing membership within arrays. This is necessary because the context (including Contacts) has objects with array data types. A common use-case is to check if a contact is a member of a group.
  6. Add COUNT() function for number of items in arrays.

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

Language identifiers and details: Support more than one language per ISO 639-3 code

The first draft of Flow Spec used bare ISO 639-3 codes to identify languages. This was assumed possible since ISO 639-3 is supposed to contain every human written and spoken language.

However, what the consortium realized in discussions over time is that 639-3 alone doesn't make sense as a "primary key" for languages in flows, because:

  • It could be common to have multiple "languages" with the same 639-3 code, to support use-cases where organizations might have distinct sets of content ("language variants") for the same 639-3 value. A common example of variants could be "English - India" and "English - East Africa", or "English - Male voice" and "English - Female voice".
  • There might be rare cases of languages with no 639-3 code.

There is also additional data that might be useful to associate with languages, beyond the ISO 639-3 code; an example is the BCP 47 language+locale, useful often in conjunction with speech recognition and synthesis tools, etc.

Possible solution:

languages: [
   {
      id: "eng-male",
      label: "English - Male Voice",
      iso_639_3: "eng",
      variant: "male",
      bcp_47: "en-US"
   },
   {
      id: "eng-female",
      label: "English - Female Voice",
      iso_639_3: "eng",
      variant: "female",
      bcp_47: "en-US"
   },
   {
      id: "mis-mysecretlanguage",
      label: "My secret code language",
      iso_639_3: "mis",
      variant: "mysecretlanguage",
      bcp_47: null
   },
   {
      id: "fre",
      label: "Francais",
      iso_639_3: "fre",
      variant: null,
      bcp_47: fr-FR
   }
]

The language id would be a string, but the recommended format for language IDs would be:

  • <iso_639_3>-variant, where the variant is not null.
  • <iso_639_3>, when the variant is null.

Define the license

There were good arguments in favor of different licenses:

  • BSD
  • Apache
  • MIT
  • GPLv3

API specification for publishing Flows, listing flows, and triggering Flow runs on Contacts

Problem Statement: Until now, the Flow specification has not included in scope any APIs that would be used to:

  • Exchange Flow content across platforms
  • Trigger Flow runs to happen on an external system, against a set of Contacts
  • Share or synchronize Contacts, as required for context at the start of the Flow

When exploring or implementing practical use-cases for Flow Interop (such as in mHero discussions with Intrahealth), it's become clear that these capabilities are required to get to the "finish line" of using Flow Interop standards in an end-to-end integration. A typical dataflow in this integration would be:

  • Users of System A develop Flow content
  • System A maintains a database of Contacts
  • An event (manual or automated) requires running a Flow on one or more Contacts. System A needs to use System B to run the Flow (for its telecom connections, channel capabilities, etc.)
    • System A makes an API call to System B to share the Flow content
    • System A makes an API call to System B to launch the Flow against a desired set of Contacts
  • System B runs the Flow on the requested Contacts
  • System A retrieves the interactions from System B using the Flow Results standard

Proposed solution

A minimal API interface is proposed in the drafts/1.0.0-rc3 branch to achieve this end-to-end functionality. For consistency with the Flow Results API standard, we continue to use the JSON:API standard for the overall format and envelope.

Intentionally, we do not aim to cover Contact synchronization. This is a hard and relevant problem, but the needs for it vary significantly across use-cases; it is best addressed with existing solutions that are developed specifically for contact synchronization (e.g.: LDAP). We do provide a way to share "as of now" Contact state so that external flow runners can start a flow with the required Contact context.

This initial proposal emerged from input from the Intrahealth and Viamo workshops, and discussions with other partners. Additional feedback is welcome!

Core.SetGroupMembership block: Allow to "clear" a contact from all groups (without having to explicitly list them).

Problem: In some flows it may be interesting to "reset" the groups for the contact, however it is necessary to explicitly list the groups to which the contact belongs.

Proposal: Add the "clear" field to the block config, which, if present and true, removes the contact from all groups. When the "clear" field is present, the other fields will be ignored. Example:

{
  "type": "Core.SetGroupMembership",
  "name": "ClearContactGroups",
  "label": "Remove contact from all groups",
  "config": {
    "clear": true
  },
  "exits": [
    {
      "uuid": "6827500a-38b7-4dc3-803c-d2c14e961a0f",
      "name": "Default",
      "default": true
    }
  ]
}

Core.SetGroupMembership block: Allow set multiple groups at once

In some cases it would be interesting to be able to add or remove a contact from more than one group in the same block. For example:

{
  "type": "Core.SetGroupMembership",
  "name": "AddToMultipleGroups",
  "label": "Add to multiple groups",
  "config": {
    "groups": [
      {
        "group_key": "59c2ef5d-aeff-4df3-b717-f0624f96db74",
        "group_name": "Survey Audience"
      },
      {
        "group_key": "2d5a6b73-c1d1-448d-99d6-8942dd8e4935",
        "group_name": "Unsatisfied Customers"
      }
    ],
    "is_member": true
  },
  "exits": [
    {
      "uuid": "6827500a-38b7-4dc3-803c-d2c14e961a0f",
      "name": "Default",
      "default": true
    }
  ]
}

It is even a very common case in some RapidPro flows, where a certain condition (sometimes simply the beginning of the flow) justifies categorizing the contact in more than one group.

It is perfectly possible to do the same thing by creating a block for each group, but this ends up making the flow more extensive to perform the same action N times. Does it make any sense or is it not worth the increase in block complexity?

Webhook block specification

Initial outline to kick off the discussion.

The webhook block will define an HTTP call to an external service. We need to define how the block will behave and what are the required parameters to configure it.

This is not a generic HTTP block but rather a specific ‘interface with services that play well with FLOW’ block.

What has worked for us very well is the notion of a manifest that declares specific blocks based on services that can then be put in flows. See https://github.com/instedd/verboice/wiki/External-Services

Example:

results:
  displayName: Analysis Results
  icon: medicalkit
  type: callback
  callbackUrl: http://{service_domain}/results
  settings:
    -
      name: pin
      displayName: Patient ping
      type: string
  response:
    -
      name: result
      displayName: Result
      type: string

Configuration

  • URL: Either a string with interpolation or a raw expression. There may to be a 'global' constant value provided by the tool with the base URL of the service and each block will construct the service URL from it.
  • HTTP verb: POST required, GET suggested. We may want to support PUT, PATCH and DELETE later.
  • Authentication credentials: Services need to authenticate. Credentials may be defined outside the scope of the flow, but we need to be able to reference them from the flow, or be bound to the service URL. Support OAuth2 tokens and HTTP basic.
  • Parameters: A map from parameter name to an expression. Query or POST body? Or both? Query string works for any HTTP verb but it's limited in size (depending on the server implementation). POST body will not work for GET. Let’s choose one and use the other as optional.
  • Extra HTTP headers. Optional, but can be very useful. Same as parameters, a map from name to expressions.
  • Timeout waiting for the response.

Behaviour

  • Synchronous/asynchronous mode. A service can declare it is one-way-able. In this case, requests can be queued for offline operation. Do we specify ordering guarantees or leave it up to each implementation?
  • Mimetype of the response. Support JSON at a minimum. Add XML and plain text later.
  • Follow redirects. This is probably a given, but needs to be specified. Allows e.g. Google scripts APIs and other sort of frameworks to be used.
  • Cookie handling. Optional. Can be used to establish a session with the service (eg. for authentication). We may want to define different scopes, eg. use different cookie jars depending on flow/contact/session.
  • Mapping of results, ie. how to parse the result and put it in the context. Use JSONPath (or a subset) to reference data in a JSON response. We want (a subset of) XPath if we support XML responses.

Finalize Context variables for use within Expressions

We need to update and finalize the specification for context variables that can be accessed from within Expressions. This was drafted long ago, but needs an update to consider compatibility with RapidPro's current context model, and to align different vendors.

Goals:

  • Not too disruptive to existing implementations
  • Enables RapidPro convertability (but not necessarily exactly the same variable names)
  • Avoids redundancy of similar data in multiple places
  • Clear, logical, and complete for Flow Authors. Try as much as possible to avoid the need for future breaking changes, since it's really hard to change the context once authors have written expressions into Flows.
  • No larger than needed; ensure there are use-cases for everything we keep in the spec.

The context is primarily intended for supporting Expressions... but having standard elements in the Context also helps implementations understand what they must track during runtimes of flows.

Draft for feedback:

https://github.com/FLOIP/flow-spec/blob/s1/context/fundamentals/context.md

Sprint 2 Planning (deadline: Feb 4)

What do we build in Sprint 2?

e.g.:

  1. Spec for Mobile Primitives Blocks
  2. Which blocks?
  3. Webhook (not necessarily a Mobile primtivie, but not core)
  4. ...
  5. Engine to run the Core Layer (Level 1)
  6. and console IO (Layer 2) --> Automated tests
  7. dependency: define how an engine hooks-out to channel implementations? Does this need to be part of the spec? [no] part of the engine design [yes]
  8. Finish the definition of the Context variables

Actions:

  • Propose tasks for Sprint 2
  • Volunteer your team for tasks [make sure to volunteer for capacity you have available]
  • Estimate the work (dev days) for tasks

Define high level interoperability criteria

Test for interoperability drafted in DC:

  • Tool A (claiming compliance with flow spec) exports a tool that uses only "MUST" blocks specified in the standard.
  • Tool B (claiming compliance with flow spec) imports the output of A.
  • There is NO loss of behaviour:
    • UX for caller is identical
    • Data output is identical

Data model for output of flows

I am not sure if this is in scope of FLOIP or not, but we have some projects that we are interested in a normalized data structure for the data collected in a flow. Use cases include:

  • mHero for one-time/routine surveys of health workers
  • DHIS2 for aggregate data collection and reporting

For mHero we have already made use of FHIR Questionnaire and QuestionnaireResponse resources:

For mHero we have already mapped flow output for RapidPro according to:
https://wiki.ohie.org/display/documents/mHero+Workflow
I know Bob from the DHIS2 side of things is also looking at these resources for the Aggregate Data eXchange (ADX) standard.

I am assuming that FLOIP is not specific to the health context and so you wouldn't want to use the FHIR resources. What we would need is the ability to extract from the FLOIP metadata enough information to generate the Questionnaire resources.

Determine format for media IDs

Resources will need to refer to media identifiers / files / URLs for localized audio content. Responses collected from Contacts will also need to refer to media provided by Contacts in responses (audio recordings, pictures shared, etc.)

Requirements:

  • These media identifiers might refer to local IDs within a platform, or to online URLs accessible from anywhere.
  • They may need to identify the file type (generally, such as audio), as well as specifically (e.g. ogg)

Branch as core primitive

I propose we need a branching operation that is more than just an if (only because that is a common case and will significantly reduce complexity)

As a shwag, perhaps something like:

{
  "type": "branch",
  "uuid": "uuid-blah",
  "outputs": [
    {  
      "uuid": "uuid-output-1",
      "test": "contact.age < 13",
      "destination": "uuid-dest-1"
    },
    {
      "uuid": "uuid-output-2",
      "test": "contact.age < 18",
      "destination": "uuid-dest-2"
    },
    {
      "uuid": "uuid-output-2",
      "test": "contact.age >= 18 && contact.age < 65",
      "destination": "uuid-dest-3"
    },
    {
      "uuid": "uuid-output-3",
      "test": "1",
      "destination": "uuid-dest-3"
    }
  ]
}

Semantics would be that the tests are evaluated one by one and the first "truthy" result that matches will cause the flow to continue from there.

Discussion points:

  1. In RapidPro our tests are structured, ie, closer to:
{ "operand": "contact.age",
   "test": { "type": "lt", "test": "18" } }

This is nice when building tests in the UI but strikes me as maybe overkill if we are trying to build out of primitives if we expect our expression language to be, well, expressive. The downside is that editors need to parse the tree if they want to build UIs around this, but I think that's ok. (and they can always punt if they don't know how to build the UI and just show the raw expression)

  1. Do branches need labels and such (and associated localization)? From a UI perspective that sure is nice in that you can describe these tests.
  2. Should the branching block output something to the session state? Seems that might be useful if the user wants to save the result later.

CC: @rowanseymour @ericnewcomer

Expression Proposal: `IN` - Checks the existence of element in an Array

Type of Expression: Array Function
Return Type: Boolean
Case Sensitive: Yes

Expression Example:
@in('Ten', array(5, 34, 'Ten')) -> TRUE
@in('ten', array(5, 34, 'Ten') -> FALSE
@in(5, array(5, 34, 'Ten') -> TRUE
@in(5, array('5', 34, 'Ten') -> TRUE
@in('5', array(5, 34, 'Ten') -> TRUE

Common Use Case:
This expression can be used to find the existence of specific words/characters in a given array of possible options

New capability for blocks: Setting contact properties within any block

Rationale:

For vendors that have flows that interact with contacts, assigning responses during flows to Contacts is a very frequent operation. It can simplify flow designs, and reduce the chance of user errors, to have this functionality available directly within blocks, rather than requiring a subsequent SetContactProperty or SetGroupMembership block.

Instead of having this functionality defined on some specific block types, it seems simpler to define this functionality generically to run as a final step, immediately prior to the "exiting phase" of any block.

Proposed additional config available on all blocks:

config {
   ...,
   set_contact_property: {
      property_key: string
      property_value: expression
   }
}

The keys within the set_contact_property section are identical in behaviour to the SetContactProperty block.

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

New Block Types: Core\SetContactProperty and Core\SetGroupMembership

Rationale:

Setting properties on contacts, and adding/removing contacts from groups, are key operations for all vendors that have flows that interact with Contacts. There is no way to specify this using combos of existing blocks, so this requires new functionality. It may be valuable to standardize this across vendors, instead of having a number of independent vendor-specific blocks to do this within their own platforms.

Key Operations:

  1. Set a contact property (key, value from expression)
  2. Set the membership of a contact within a group (add, remove).

Proposed config parameters: SetContactProperty:

config {
   property_key: string,
   property_value: expression
}

The property_key is a string and is not further restricted by the spec. For complete block interoperability across vendors, vendors would need to agree on the format and identity of property_key. (e.g. property_key could be "gender" , or it could be a uuid referenced to an external taxonomy service.)

Proposed config parameters: SetGroupMembership:

config {
   group_key: string,
   group_name?: string,
   is_member: expression  (Values: 0 to remove, truthy to add, and null to not change the existing membership)
}

The group_key is a string and is not further restricted by the spec. For complete block interoperability across vendors, vendors would need to agree on the format and identity of group_key. The group_name is provided as a human-readable label in addition to the group_key, in cases where the group_name needs to be displayed to the Contact.

Note: In the course of developing the open-source Flow Runner and Flow Builder implementations, the initial dev team proposed a list of cleanups and clarifications to the spec. This is one of a set of changes proposed to improve consistency, usability, and functionality of the spec based on the first complete build-outs.

Approve the Project Charter drafted at the Kickoff Workshop

Purpose

The purpose of the project is to enable useful interoperability between Flow-based platforms, and to incentivize the joining of future software tools into an interoperable ecosystem. We will accomplish this through a set of open specifications, and a set of open-source toolsets that reduce barriers to adoption. The initial focus is on tools for mobile/web engagement.

Members

The initial collaborating organisations are Nyaruka, InSTEDD, Ona, and VOTO Mobile. UNICEF and USAID is providing guidance and input. The project received initial funding from USAID. We recognize and hope that this project will outlast USAID funding and grow beyond the initial set of collaborating organisations.

Scope

  • We will create an open specification that allows sharing flow definitions across platforms, so that flows can be authored, run, and visualized across platforms
  • We will create an open specification for the data resulting from interacting with flows, so that data can move across platforms and accelerate the creation of dashboards, analysis tools, and archiving.
  • We will create a set of supporting tools to incentivize and accelerate adoption of these two specifications.

Principles

The principles of the project will guide and be used as a criteria for decision-making:

  • Think about and enable a future ecosystem.
  • Do the hard work to make it simple
  • Start with needs
  • Focus on what’s good for end-users, within our context
  • There are multiple good solutions;
  • Aim for consistency when possible but recognize unique needs
  • Multiple perspectives are an asset
  • “This will do”
  • Make evidence-based decisions
  • Understand that iteration will be needed (short-term, but also in the long-term to stay relevant and adapt)
  • Make it open [includes how the initial partners work together, but also how others can join]

Governance and Decision-making on the overall project:

(Includes specifications, recommendations for APIs, design of tools/toolsets)

  • There is a ‘core’ team (for this: Nyaruka, InSTEDD, Ona, VOTO on all components; additionally UNICEF is on the Flow Data/Results Spec)
    • Criteria for being part of the core team:
      • Doers, not just users
      • Are (or soon will be) an implementor of the spec in some way
  • We will operate on a consensus model: Discuss until all agree on the change, or one core member walks away. The goal is to avoid anyone walking away: we share a commitment to ensuring “this will work” for everyone
    • Consensus doesn’t mean agreement that a proposal is ideal; it means willingness to move forward with it
    • If we notice that one member is continuously dissenting but giving a final go-ahead: this is a red flag that either:
      • One partner is less aligned on the direction of the project
      • Or one voice is consistently not being heard
  • Pull requests for incorporation into the spec require “all to approve” among the core team
    • The implicit default if no response is received in a week, or no official position given in a month, is to abstain.
  • Anyone in the world can contribute to a branch
  • Core team is the ones that can merge into master
  • Where/how the discussion happens:
    • Asynchronous conversations on Github Issues and pull requests
    • Bi-weekly phone calls with the core team (and open to anyone), to discuss the tickets that need discussion
  • In each ‘approval’ process, there is one vote per core organization. The vote choices are: Approve, Abstain, Disagree but Allow, Block
    • Agree: The org thinks the change is positive and approves it.
    • Abstain: The org doesn't have a position on this change, but approves it.
    • Disagree but Allow: The org thinks the change is negative but is willing to approve it.
    • Block: The org thinks the change is negative and is not willing to approve it until concerns are addressed.

Change exit.label from a translated resource to an optional string, or remove it

Currently, block.exit.label is a translated resource, and is required for exits. This is inconsistent with flow.label and block.label, which are optional human-readable strings that expand on flow.name, block.name.

By making exit.label into an optional string, it would:

  • Significantly reduce the number of resources required on a flow.
  • Avoid the problem where we do not typically have a good input source of translated exit names from flow builder UIs.
  • Create consistency with block.label and flow.label

Since exits are a flow logic element that are not visually exposed to contacts (end-users), we're not aware of places where translation for the exit name needs to be presented to contacts.

Proposal:

  • Change block.exit.label to an optional string. exit.name to be used on flow displays if exit.label is not provided. This creates consistency in the usage of name and label on flows, blocks, and exits.

Miscellaneous clarifications identified while developing Flow Runner and Flow Builder

In the course of developing the open-source Flow Runner and Flow Builder implementations, the dev team proposed a list of cleanups and clarifications to the spec. The following suggestions don't significantly change the behaviour or capabilities of the spec, but improve consistency and usability. (Other changes with larger functional implications have been proposed as separate issues.)

  1. flow->destination_block can be null, e.g. at the end of a flow path (where there is no next block from an exit).
  2. Add "offline" mode in Flow intro docs. (This is referenced in Layer 4, but not on the Flow intro)
  3. The spec should use snake_case consistently
  4. Add first_block_id [string] to the Flow definition. This is more reliable than relying on block ordering to find the starting block.
  5. Block exits: The optional "default" exit should apply generally; this is only discussed on the Case block rather than in the Block definition.
  6. Add an optional exit_block_id to the Flow definition. The flow would jump to here on "fatal" errors within blocks.
  7. Clarify the list of modes for supported_modes; this indicates the "channel type" that the flow or resource can run through: text (any general channel that supports bi-directional text), sms, ussd, ivr, rich_messaging (data channels that support multimedia including text, audio, images, and video, such as social network chatbots), and offline (mobile apps designed to run offline without a data connection).

Supporting custom / contrib block types

Given that the floip spec is a standardisation and interoperability effort, it's safe to assume that the specification will likely trail behind some immediate implementation needs.

What are the proposed conventions, if any, for:

  1. Announcing a custom block / extension in a flow specification file
  2. Namespacing these custom (contrib?) extensions in the block definitions

Some immediate follow up questions this:

  1. Would name spacing block types with Contrib.VendorID.Name suffice?
  2. Is a top level key needed to announce these block types, possibly with URLs pointing to their implementation details should others also want to support it?
  3. How does one signal contrib deprecation. I'm thining of a scenario when a block, previously only available as a contrib type, is now supported as a standard type. I'm anticipating this for webhooks as an example.

Ideally some of these contrib extensions would then in the future be merged into the spec and gain wider support. What can be done by implementors now to ensure that custom contributions lead to strengthening of future interoperability rather than fragmenting the specification?

Determine format for Resources

We need to decide on the format used for Resources, which are set of localized strings or audio(media) resources.

An example draft to start:

A Resource describes a collection of localized strings or media resources, used when content needs to be presented to Contacts in multiple languages. Resources have the following following structure:

[resource-uuid]:{
  "id":[resource-uuid],
  "text":{
    [iso-639-3 language code]:[localized text],
    [iso-639-3 language code]:[localized text],
    ...
    [iso-639-3 language code]:[localized text],
  },
  "audio":{
    [iso-639-3 language code]:[media ID],
    [iso-639-3 language code]:[media ID],
    ...
    [iso-639-3 language code]:[media ID],
  }
}

for instance,

"6681c54c-9fcd-11e7-abc4-cec278b6b50a":{
  "id":"6681c54c-9fcd-11e7-abc4-cec278b6b50a"
  "text":{
    "eng":"How are you?",
    "aka":"Wo ho te sɛn?",
    "dag":"A mal’ alaafee?"
  },
  "audio":{
    "eng":"5718eb16.wav",
    "aka":"5f6e4694.wav",
    "dag":"68bd67f2.wav"
  }
}

Questions:

  • Do we need to support media resources other than audio? e.g. localized video, for smartphone channels?
  • Is it valuable to have the key also inside the resource body, not just in the external key?
  • Should we list the languages separately at the top level?

Do not use JSON objects for Block configs that should have a stable key order

The SelectOneResponse block definition states that the config should have a choices key with a mapping of tags to localized names for choices. The implementation uses a JSON object for the mapping. This is problematic because the JSON specification states that An object is an unordered set of name/value pairs.

Some languages may load these keys in the order that they're defined but that should be seen as a unintended side effect.

The language we're using translates JSON objects to Map structures of which the key order is non-deterministic. The unintended side effect of this is that when we're surfacing a SelectOneResponse as a multiple choice menu, the order of the choices is different from what the floip specification suggests.

Is there appetite to change the choices config to a list of objects with predictable keys or a list of tuples?

The current example from the documentation is

{
 ...
 "config": {
    "prompt": "42095857-6782-425d-809b-4226c4d53d4d",
    "choices": {
      "chocolate": "66623eff-fd17-4996-8edd-e41be3804bc8",
      "vanilla": "b0f6d3ec-b9ec-4761-b280-6777d965deab",
      "strawberry": "b75fa302-8ff7-4f49-bf26-8f915e807222"
    }
 }

My suggestion would be to either go with a list of objects:

{
 ...
 "config": {
    "prompt": "42095857-6782-425d-809b-4226c4d53d4d",
    "choices": [
      {"tag": "chocolate", "resource": "66623eff-fd17-4996-8edd-e41be3804bc8"},
      {"tag": "vanilla", "resource": "b0f6d3ec-b9ec-4761-b280-6777d965deab"},
      {"tag": "strawberry", "resource": "b75fa302-8ff7-4f49-bf26-8f915e807222"}
    ]
 }

Or a list of tuples:

{
 ...
 "config": {
    "prompt": "42095857-6782-425d-809b-4226c4d53d4d",
    "choices": [
      ["chocolate", "66623eff-fd17-4996-8edd-e41be3804bc8"],
      ["vanilla", "b0f6d3ec-b9ec-4761-b280-6777d965deab"],
      ["strawberry", "b75fa302-8ff7-4f49-bf26-8f915e807222"]
    ]
 }

There may be other areas where the spec is implicitly assuming that the order of keys in JSON Objects is stable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.