flow-spec's People
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.
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.
Settle on expression language
We use a library we built which is simple Excel inspired functions:
https://github.com/rapidpro/expressions
Our thoughts are basically just:
- no looping should be supported
- no complicated control structures
- but basic math / date / string functions are useful
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
- 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",
},
]
}
- 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 thename
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):
- Add ISNUMBER(), ISBOOL(), and ISSTRING() to support validation of data received in block exit tests
- Add NULL type, and the ability to test against NULL
- Add BETWEEN() function to simplify comparisons within a range
- Add CONTAINS() function to enable keyword matching within strings
- 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.
- 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.:
- Spec for Mobile Primitives Blocks
- Which blocks?
- Webhook (not necessarily a Mobile primtivie, but not core)
- ...
- Engine to run the Core Layer (Level 1)
- and console IO (Layer 2) --> Automated tests
- 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]
- 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
Understand USAID contractual license implications from Award
Review USAID policy around licensing and understand if there are steps that need to be taken to ensure the IP can be licensed open
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:
- 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)
- Do branches need labels and such (and associated localization)? From a UI perspective that sure is nice in that you can describe these tests.
- Should the branching block output something to the session state? Seems that might be useful if the user wants to save the result later.
Context: create standards for the reason for the error after an error exit
From @ggiraldez:
I'm not sure it's written in another part of the document, but for error exits we need a way to indicate the reason for the error. Eg. it may make sense to differentiate between a timeout vs. an invalid option in a multiple choice block.
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
Create parameters for early termination of open-ended responses and numeric responses in IVR
For the IVR mode, create standard parameters for:
- OpenResponseBlock: Termination of recordings when pressing one of a set of specified keys
- NumericResponseBlock: end entry of multiple digits when pressing one of a set of keys like # or *
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:
- Set a contact property (key, value from expression)
- 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
- Criteria for being part of the core team:
- 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
andlabel
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.)
- flow->destination_block can be null, e.g. at the end of a flow path (where there is no next block from an exit).
- Add "offline" mode in Flow intro docs. (This is referenced in Layer 4, but not on the Flow intro)
- The spec should use snake_case consistently
- Add first_block_id [string] to the Flow definition. This is more reliable than relying on block ordering to find the starting block.
- Block exits: The optional "default" exit should apply generally; this is only discussed on the Case block rather than in the Block definition.
- Add an optional exit_block_id to the Flow definition. The flow would jump to here on "fatal" errors within blocks.
- 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).
Expression Proposal: `Contains` - Checks the existence of value in a String
Type of Expression: String Function
Return Type: Boolean
Case Sensitive: Yes
Expression Example:
@contains('ghana', 'I live in ghana')
-> TRUE
Common Use Case:
This expression can be used to find the existence of specific words/characters in the flow block response
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:
- Announcing a custom block / extension in a flow specification file
- Namespacing these custom (contrib?) extensions in the block definitions
Some immediate follow up questions this:
- Would name spacing block types with
Contrib.VendorID.Name
suffice? - 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?
- 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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.