summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2023-05-18 06:44:43 +0000
committerCoprDistGit <infra@openeuler.org>2023-05-18 06:44:43 +0000
commit478322089baf58fe3f707f5bc05eb4d0f49f13d5 (patch)
tree305c0f338486c8bda5ae838b5355fec31d6fed3f
parent44e18bfd39003f5486e489ee7cb86a0aecc74af2 (diff)
automatic import of python-ruleau
-rw-r--r--.gitignore1
-rw-r--r--python-ruleau.spec945
-rw-r--r--sources1
3 files changed, 947 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..23ea78e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/ruleau-0.7.1.tar.gz
diff --git a/python-ruleau.spec b/python-ruleau.spec
new file mode 100644
index 0000000..b15cdf2
--- /dev/null
+++ b/python-ruleau.spec
@@ -0,0 +1,945 @@
+%global _empty_manifest_terminate_build 0
+Name: python-ruleau
+Version: 0.7.1
+Release: 1
+Summary: A python rules engine
+License: BSD 3-Clause
+URL: https://gitlab.com/unai-ltd/unai-decision/ruleau-core
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/97/89/3a927d0967c2a4e2add06135a975ff4cb5ad3bcb7f205bf672b262a2a5c5/ruleau-0.7.1.tar.gz
+BuildArch: noarch
+
+Requires: python3-flatdict
+Requires: python3-Jinja2
+Requires: python3-jsonpath-ng
+Requires: python3-regex
+Requires: python3-requests
+Requires: python3-pyjwt
+
+%description
+# Ruleau
+
+A Python Rules Engine library
+
+## Using the library
+
+A username and password is required. This can be passed directly to the ApiAdapter (i.e. via the CLI) or these can
+be set in the environment variables.
+
+```text
+RULEAU_USERNAME=myusername
+RULEAU_PASSWORD=mypassword
+```
+
+```python
+from ruleau import execute, rule, ApiAdapter, Process
+
+# create a rule
+@rule(rule_id="rul_1", name="Is adult")
+def over_18(_, payload):
+ return "age" in payload and payload["age"] >= 18
+
+# create a payload (the answers to the rule's questions)
+payload = {"age": 17}
+
+# execute the rule against the payload
+result = execute(over_18, payload, Process.create_process_from_rule(over_18))
+
+# integrate with the backend web API with password and username in env
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+)
+# or pass directly to ApiAdapter:
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/", username=myusername, password=mypassword
+)
+
+# sync the process
+# Usually only needed once. For efficiency, it is advisable to keep these lines out of any loop.
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+# send the results
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+# result.result will be False due to applicant being 17
+
+# if the rule for this case is overriden in the backend
+# then running again will return True
+
+```
+
+### Organisational Data
+
+Optionally a Process can be assigned as set of "tags" called Organisational Data which can be used
+to filter or categorise rules and results.
+
+Each Process must have these specified up front by setting the Organisational Scheme like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000",
+)
+
+org_scheme: List[OrganisationalScheme] = [
+ OrganisationalScheme(
+ id="location", # ID Used to refer to the tag when organisational data is posted
+ display_name="Location", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="string", # Either a `string`, `integer`, `float` or `date` type
+ ),
+ OrganisationalScheme(
+ id="internal_id", # ID Used to refer to the tag when organisational data is posted
+ display_name="ID", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="integer", # Either a `string`, `integer`, `float` or `date` type
+ )
+]
+
+api_adapter.publish_organisational_scheme(over_18, org_scheme)
+```
+
+Once set data can be provided when a ruleset is executed by updating the API Adapter using its
+`with_organisational_data` method like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+).with_organisational_data([
+ {"key": "location", "value": "Bristol"},
+ {"key": "internal_id", "value": 5}
+])
+
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+```
+
+Optionally we can also set the order by which Organisational Data appears on the UI by posting
+to the UI Layout Metadata endpoint as in this example:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # Defines the order the tags will appear on the cases page
+ "case_org_data_order": [{"id": "internal_id"}, {"id": "location"}],
+ # Defines the order the tags will appear on the overrides page
+ "override_org_data_order": [{"id": "department"}],
+ # See "Case Payload UI Specification" for details
+ "data_payload_presentation": None,
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+### Case Payload UI Specification
+
+In the Ruleau UI we display the case payload as it was executed for a case, and allow users to override the data
+with values when said case is next reexecuted.
+
+By default we work out the structure of case payload automatically to display it in the UI, this will perform basic
+validation such as checking if the data override provided for a case matches the type in the original payload.
+
+However you can use UI Layout Metadata to publish a JSON Schema specification that defines how case payloads for a
+process should be displayed and validated, allowing for customisation of what overrides can be specified for the data.
+
+In order to do this, please see the below example setting a JSON Schema for a case:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # See "Organisational Data" for details
+ "case_org_data_order": [],
+ # See "Organisational Data" for details
+ "override_org_data_order": [],
+ # X
+ "data_payload_presentation": {
+ "type": "object",
+ "properties": {
+ "street_address": { "type": "string" },
+ "city": { "type": "string" },
+ "state": { "type": "string" }
+ },
+ "required": ["street_address", "city", "state"]
+ },
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+The above example tells the UI a case payload for this process is expected to have 3 fields:
+
+* street_address
+* city
+* state
+
+All 3 of them are required text fields, and can be overridden on a case by case basis in the UI.
+
+### JSON Schema Rules & Limitations
+
+As a JSON Schema could define any shape of data, even one wildly different from the case payloads posted for a process,
+we have to impose set of rules & limitations to ensure they are compatible with the payloads displayed on the UI.
+
+1. On a case payload please always provide all the keys, even if null, rather than excluding the keys with no values
+ * For example please use `{"a": null, "b": "abc"}` over `{"b": "abc"}` if `a` is optional
+2. A custom JSON schema must only define one type for a given property
+ * We don't support multiple types in a single field
+ * Lists containing both strings and numbers aren't allowed
+ * Nor are properties that can be either a string or number, for example
+3. Everything in the custom JSON schema must be in the payload
+ * No fields should exist in the schema that aren't on the payloads
+ * For example if the schema specifies a field `a`, that field should be present in all case payloads, even if null
+4. However not everything in the payload has to be in the JSON schema
+ * For example the payload may have fields `a`, `b` and `c`, however you only wish field `b` to be editable
+ * In that case, adding just the `b` field to the Schema means that is the only field the UI will allow data overrides for
+5. If a custom JSON schema isn't defined, and a field is always `null` in a case, it won't be shown on the data overrides UI
+ * This is as we are not able to generate a JSON schema from a `null` field (we cannot tell what type is required)
+ * This can be resolved by using a custom JSON schema and specifying what the field should be when not `null`
+ * In the example below the payload has a `null` `wind_sensor` field, by specifying it in the schema it can still be edited in the UI
+
+Please see below for an example of a custom JSON schema and the expected case payload:
+
+**Case Payload**
+
+```json
+{
+ "case_id": "001",
+ "temperature": 27,
+ "wind_speed": 0.4,
+ "humidity": 30,
+ "cloud_cover": 20,
+ "precipitation": 0,
+ "last_checks": [],
+ "pending_checks": [],
+ "sensor_status": {
+ "wind_sensor": null,
+ "cloud_reader": null,
+ "rain_indicator": null
+ }
+}
+```
+
+**JSON Schema**
+
+```json
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https:///www.unai.com/schemas/unit_test_schema.json",
+ "type": "object",
+ "properties": {
+ "case_id": { "type": "string", "title": "Case Id" },
+ "humidity": { "type": "number", "title": "Humidity" },
+ "wind_speed": { "type": "number", "title": "Wind Speed" },
+ "cloud_cover": { "type": "number", "title": "Cloud Cover" },
+ "last_checks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "date": { "type": "string", "title": "Date" },
+ "checked_by": { "type": "string", "title": "Checked By" }
+ },
+ "required": [ "date", "checked_by" ]
+ },
+ "title": "Last Checks"
+ },
+ "temperature": { "type": "number", "title": "Temperature" },
+ "precipitation": { "type": "number", "title": "Precipitation" },
+ "sensor_status": {
+ "type": "object",
+ "properties": {
+ "wind_sensor": { "type": "string", "title": "Wind Sensor" },
+ "cloud_reader": { "type": "string", "title": "Cloud Reader" },
+ "rain_indicator": { "type": "string", "title": "Rain Indicator" }
+ },
+ "title": "Sensor Status"
+ },
+ "pending_checks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "Pending Checks"
+ }
+ }
+}
+```
+
+### Testing Rules
+
+Rules should be tested using [doctest](https://docs.python.org/3/library/doctest.html).
+
+Example of these tests can be found in the [Kitchen Sink example](https://gitlab.com/unai-ltd/unai-decision/ruleau-core/-/tree/develop/examples/kitchen_sink/rules.py).
+
+### Generating Documentation
+
+Documentation for the rules can be generated using the `ruleau-docs` command.
+
+The usage is as follows:
+```
+ruleau-docs [--output-dir=<argument>] filename
+```
+
+For example for a file of rules called `rules.py` run:
+```
+ruleau-docs rules.py
+```
+
+## Building & Testing the Library
+
+### Pre-requisites
+
+* [Python 3.9+](https://www.python.org/downloads/)
+
+Package requirements installed by running:
+
+```shell
+pip install -r requirements-dev.txt
+```
+
+### Running the Tests
+
+To run all unit tests in the project use the following command:
+
+```shell
+pytest
+```
+
+
+%package -n python3-ruleau
+Summary: A python rules engine
+Provides: python-ruleau
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-ruleau
+# Ruleau
+
+A Python Rules Engine library
+
+## Using the library
+
+A username and password is required. This can be passed directly to the ApiAdapter (i.e. via the CLI) or these can
+be set in the environment variables.
+
+```text
+RULEAU_USERNAME=myusername
+RULEAU_PASSWORD=mypassword
+```
+
+```python
+from ruleau import execute, rule, ApiAdapter, Process
+
+# create a rule
+@rule(rule_id="rul_1", name="Is adult")
+def over_18(_, payload):
+ return "age" in payload and payload["age"] >= 18
+
+# create a payload (the answers to the rule's questions)
+payload = {"age": 17}
+
+# execute the rule against the payload
+result = execute(over_18, payload, Process.create_process_from_rule(over_18))
+
+# integrate with the backend web API with password and username in env
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+)
+# or pass directly to ApiAdapter:
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/", username=myusername, password=mypassword
+)
+
+# sync the process
+# Usually only needed once. For efficiency, it is advisable to keep these lines out of any loop.
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+# send the results
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+# result.result will be False due to applicant being 17
+
+# if the rule for this case is overriden in the backend
+# then running again will return True
+
+```
+
+### Organisational Data
+
+Optionally a Process can be assigned as set of "tags" called Organisational Data which can be used
+to filter or categorise rules and results.
+
+Each Process must have these specified up front by setting the Organisational Scheme like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000",
+)
+
+org_scheme: List[OrganisationalScheme] = [
+ OrganisationalScheme(
+ id="location", # ID Used to refer to the tag when organisational data is posted
+ display_name="Location", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="string", # Either a `string`, `integer`, `float` or `date` type
+ ),
+ OrganisationalScheme(
+ id="internal_id", # ID Used to refer to the tag when organisational data is posted
+ display_name="ID", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="integer", # Either a `string`, `integer`, `float` or `date` type
+ )
+]
+
+api_adapter.publish_organisational_scheme(over_18, org_scheme)
+```
+
+Once set data can be provided when a ruleset is executed by updating the API Adapter using its
+`with_organisational_data` method like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+).with_organisational_data([
+ {"key": "location", "value": "Bristol"},
+ {"key": "internal_id", "value": 5}
+])
+
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+```
+
+Optionally we can also set the order by which Organisational Data appears on the UI by posting
+to the UI Layout Metadata endpoint as in this example:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # Defines the order the tags will appear on the cases page
+ "case_org_data_order": [{"id": "internal_id"}, {"id": "location"}],
+ # Defines the order the tags will appear on the overrides page
+ "override_org_data_order": [{"id": "department"}],
+ # See "Case Payload UI Specification" for details
+ "data_payload_presentation": None,
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+### Case Payload UI Specification
+
+In the Ruleau UI we display the case payload as it was executed for a case, and allow users to override the data
+with values when said case is next reexecuted.
+
+By default we work out the structure of case payload automatically to display it in the UI, this will perform basic
+validation such as checking if the data override provided for a case matches the type in the original payload.
+
+However you can use UI Layout Metadata to publish a JSON Schema specification that defines how case payloads for a
+process should be displayed and validated, allowing for customisation of what overrides can be specified for the data.
+
+In order to do this, please see the below example setting a JSON Schema for a case:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # See "Organisational Data" for details
+ "case_org_data_order": [],
+ # See "Organisational Data" for details
+ "override_org_data_order": [],
+ # X
+ "data_payload_presentation": {
+ "type": "object",
+ "properties": {
+ "street_address": { "type": "string" },
+ "city": { "type": "string" },
+ "state": { "type": "string" }
+ },
+ "required": ["street_address", "city", "state"]
+ },
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+The above example tells the UI a case payload for this process is expected to have 3 fields:
+
+* street_address
+* city
+* state
+
+All 3 of them are required text fields, and can be overridden on a case by case basis in the UI.
+
+### JSON Schema Rules & Limitations
+
+As a JSON Schema could define any shape of data, even one wildly different from the case payloads posted for a process,
+we have to impose set of rules & limitations to ensure they are compatible with the payloads displayed on the UI.
+
+1. On a case payload please always provide all the keys, even if null, rather than excluding the keys with no values
+ * For example please use `{"a": null, "b": "abc"}` over `{"b": "abc"}` if `a` is optional
+2. A custom JSON schema must only define one type for a given property
+ * We don't support multiple types in a single field
+ * Lists containing both strings and numbers aren't allowed
+ * Nor are properties that can be either a string or number, for example
+3. Everything in the custom JSON schema must be in the payload
+ * No fields should exist in the schema that aren't on the payloads
+ * For example if the schema specifies a field `a`, that field should be present in all case payloads, even if null
+4. However not everything in the payload has to be in the JSON schema
+ * For example the payload may have fields `a`, `b` and `c`, however you only wish field `b` to be editable
+ * In that case, adding just the `b` field to the Schema means that is the only field the UI will allow data overrides for
+5. If a custom JSON schema isn't defined, and a field is always `null` in a case, it won't be shown on the data overrides UI
+ * This is as we are not able to generate a JSON schema from a `null` field (we cannot tell what type is required)
+ * This can be resolved by using a custom JSON schema and specifying what the field should be when not `null`
+ * In the example below the payload has a `null` `wind_sensor` field, by specifying it in the schema it can still be edited in the UI
+
+Please see below for an example of a custom JSON schema and the expected case payload:
+
+**Case Payload**
+
+```json
+{
+ "case_id": "001",
+ "temperature": 27,
+ "wind_speed": 0.4,
+ "humidity": 30,
+ "cloud_cover": 20,
+ "precipitation": 0,
+ "last_checks": [],
+ "pending_checks": [],
+ "sensor_status": {
+ "wind_sensor": null,
+ "cloud_reader": null,
+ "rain_indicator": null
+ }
+}
+```
+
+**JSON Schema**
+
+```json
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https:///www.unai.com/schemas/unit_test_schema.json",
+ "type": "object",
+ "properties": {
+ "case_id": { "type": "string", "title": "Case Id" },
+ "humidity": { "type": "number", "title": "Humidity" },
+ "wind_speed": { "type": "number", "title": "Wind Speed" },
+ "cloud_cover": { "type": "number", "title": "Cloud Cover" },
+ "last_checks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "date": { "type": "string", "title": "Date" },
+ "checked_by": { "type": "string", "title": "Checked By" }
+ },
+ "required": [ "date", "checked_by" ]
+ },
+ "title": "Last Checks"
+ },
+ "temperature": { "type": "number", "title": "Temperature" },
+ "precipitation": { "type": "number", "title": "Precipitation" },
+ "sensor_status": {
+ "type": "object",
+ "properties": {
+ "wind_sensor": { "type": "string", "title": "Wind Sensor" },
+ "cloud_reader": { "type": "string", "title": "Cloud Reader" },
+ "rain_indicator": { "type": "string", "title": "Rain Indicator" }
+ },
+ "title": "Sensor Status"
+ },
+ "pending_checks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "Pending Checks"
+ }
+ }
+}
+```
+
+### Testing Rules
+
+Rules should be tested using [doctest](https://docs.python.org/3/library/doctest.html).
+
+Example of these tests can be found in the [Kitchen Sink example](https://gitlab.com/unai-ltd/unai-decision/ruleau-core/-/tree/develop/examples/kitchen_sink/rules.py).
+
+### Generating Documentation
+
+Documentation for the rules can be generated using the `ruleau-docs` command.
+
+The usage is as follows:
+```
+ruleau-docs [--output-dir=<argument>] filename
+```
+
+For example for a file of rules called `rules.py` run:
+```
+ruleau-docs rules.py
+```
+
+## Building & Testing the Library
+
+### Pre-requisites
+
+* [Python 3.9+](https://www.python.org/downloads/)
+
+Package requirements installed by running:
+
+```shell
+pip install -r requirements-dev.txt
+```
+
+### Running the Tests
+
+To run all unit tests in the project use the following command:
+
+```shell
+pytest
+```
+
+
+%package help
+Summary: Development documents and examples for ruleau
+Provides: python3-ruleau-doc
+%description help
+# Ruleau
+
+A Python Rules Engine library
+
+## Using the library
+
+A username and password is required. This can be passed directly to the ApiAdapter (i.e. via the CLI) or these can
+be set in the environment variables.
+
+```text
+RULEAU_USERNAME=myusername
+RULEAU_PASSWORD=mypassword
+```
+
+```python
+from ruleau import execute, rule, ApiAdapter, Process
+
+# create a rule
+@rule(rule_id="rul_1", name="Is adult")
+def over_18(_, payload):
+ return "age" in payload and payload["age"] >= 18
+
+# create a payload (the answers to the rule's questions)
+payload = {"age": 17}
+
+# execute the rule against the payload
+result = execute(over_18, payload, Process.create_process_from_rule(over_18))
+
+# integrate with the backend web API with password and username in env
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+)
+# or pass directly to ApiAdapter:
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/", username=myusername, password=mypassword
+)
+
+# sync the process
+# Usually only needed once. For efficiency, it is advisable to keep these lines out of any loop.
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+# send the results
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+# result.result will be False due to applicant being 17
+
+# if the rule for this case is overriden in the backend
+# then running again will return True
+
+```
+
+### Organisational Data
+
+Optionally a Process can be assigned as set of "tags" called Organisational Data which can be used
+to filter or categorise rules and results.
+
+Each Process must have these specified up front by setting the Organisational Scheme like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000",
+)
+
+org_scheme: List[OrganisationalScheme] = [
+ OrganisationalScheme(
+ id="location", # ID Used to refer to the tag when organisational data is posted
+ display_name="Location", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="string", # Either a `string`, `integer`, `float` or `date` type
+ ),
+ OrganisationalScheme(
+ id="internal_id", # ID Used to refer to the tag when organisational data is posted
+ display_name="ID", # Label used on the UI when being displayed
+ display_default=True, # If true, will appear on the UI by default, otherwise is hidden
+ type="integer", # Either a `string`, `integer`, `float` or `date` type
+ )
+]
+
+api_adapter.publish_organisational_scheme(over_18, org_scheme)
+```
+
+Once set data can be provided when a ruleset is executed by updating the API Adapter using its
+`with_organisational_data` method like so:
+
+```python
+api_adapter = ApiAdapter(
+ base_url="http://localhost:8000/"
+).with_organisational_data([
+ {"key": "location", "value": "Bristol"},
+ {"key": "internal_id", "value": 5}
+])
+
+process = Process.create_process_from_rule(over_18)
+api_adapter.sync_process(process)
+
+result = execute(over_18, payload, process, api_adapter=api_adapter, case_id="ca_1280")
+```
+
+Optionally we can also set the order by which Organisational Data appears on the UI by posting
+to the UI Layout Metadata endpoint as in this example:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # Defines the order the tags will appear on the cases page
+ "case_org_data_order": [{"id": "internal_id"}, {"id": "location"}],
+ # Defines the order the tags will appear on the overrides page
+ "override_org_data_order": [{"id": "department"}],
+ # See "Case Payload UI Specification" for details
+ "data_payload_presentation": None,
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+### Case Payload UI Specification
+
+In the Ruleau UI we display the case payload as it was executed for a case, and allow users to override the data
+with values when said case is next reexecuted.
+
+By default we work out the structure of case payload automatically to display it in the UI, this will perform basic
+validation such as checking if the data override provided for a case matches the type in the original payload.
+
+However you can use UI Layout Metadata to publish a JSON Schema specification that defines how case payloads for a
+process should be displayed and validated, allowing for customisation of what overrides can be specified for the data.
+
+In order to do this, please see the below example setting a JSON Schema for a case:
+
+```python
+ui_layout_metadata: UiLayoutMetadata = {
+ # See "Organisational Data" for details
+ "case_org_data_order": [],
+ # See "Organisational Data" for details
+ "override_org_data_order": [],
+ # X
+ "data_payload_presentation": {
+ "type": "object",
+ "properties": {
+ "street_address": { "type": "string" },
+ "city": { "type": "string" },
+ "state": { "type": "string" }
+ },
+ "required": ["street_address", "city", "state"]
+ },
+}
+
+api_adapter.publish_ui_layout_metadata(
+ payload_has_patient_rule, ui_layout_metadata
+)
+```
+
+The above example tells the UI a case payload for this process is expected to have 3 fields:
+
+* street_address
+* city
+* state
+
+All 3 of them are required text fields, and can be overridden on a case by case basis in the UI.
+
+### JSON Schema Rules & Limitations
+
+As a JSON Schema could define any shape of data, even one wildly different from the case payloads posted for a process,
+we have to impose set of rules & limitations to ensure they are compatible with the payloads displayed on the UI.
+
+1. On a case payload please always provide all the keys, even if null, rather than excluding the keys with no values
+ * For example please use `{"a": null, "b": "abc"}` over `{"b": "abc"}` if `a` is optional
+2. A custom JSON schema must only define one type for a given property
+ * We don't support multiple types in a single field
+ * Lists containing both strings and numbers aren't allowed
+ * Nor are properties that can be either a string or number, for example
+3. Everything in the custom JSON schema must be in the payload
+ * No fields should exist in the schema that aren't on the payloads
+ * For example if the schema specifies a field `a`, that field should be present in all case payloads, even if null
+4. However not everything in the payload has to be in the JSON schema
+ * For example the payload may have fields `a`, `b` and `c`, however you only wish field `b` to be editable
+ * In that case, adding just the `b` field to the Schema means that is the only field the UI will allow data overrides for
+5. If a custom JSON schema isn't defined, and a field is always `null` in a case, it won't be shown on the data overrides UI
+ * This is as we are not able to generate a JSON schema from a `null` field (we cannot tell what type is required)
+ * This can be resolved by using a custom JSON schema and specifying what the field should be when not `null`
+ * In the example below the payload has a `null` `wind_sensor` field, by specifying it in the schema it can still be edited in the UI
+
+Please see below for an example of a custom JSON schema and the expected case payload:
+
+**Case Payload**
+
+```json
+{
+ "case_id": "001",
+ "temperature": 27,
+ "wind_speed": 0.4,
+ "humidity": 30,
+ "cloud_cover": 20,
+ "precipitation": 0,
+ "last_checks": [],
+ "pending_checks": [],
+ "sensor_status": {
+ "wind_sensor": null,
+ "cloud_reader": null,
+ "rain_indicator": null
+ }
+}
+```
+
+**JSON Schema**
+
+```json
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https:///www.unai.com/schemas/unit_test_schema.json",
+ "type": "object",
+ "properties": {
+ "case_id": { "type": "string", "title": "Case Id" },
+ "humidity": { "type": "number", "title": "Humidity" },
+ "wind_speed": { "type": "number", "title": "Wind Speed" },
+ "cloud_cover": { "type": "number", "title": "Cloud Cover" },
+ "last_checks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "date": { "type": "string", "title": "Date" },
+ "checked_by": { "type": "string", "title": "Checked By" }
+ },
+ "required": [ "date", "checked_by" ]
+ },
+ "title": "Last Checks"
+ },
+ "temperature": { "type": "number", "title": "Temperature" },
+ "precipitation": { "type": "number", "title": "Precipitation" },
+ "sensor_status": {
+ "type": "object",
+ "properties": {
+ "wind_sensor": { "type": "string", "title": "Wind Sensor" },
+ "cloud_reader": { "type": "string", "title": "Cloud Reader" },
+ "rain_indicator": { "type": "string", "title": "Rain Indicator" }
+ },
+ "title": "Sensor Status"
+ },
+ "pending_checks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "Pending Checks"
+ }
+ }
+}
+```
+
+### Testing Rules
+
+Rules should be tested using [doctest](https://docs.python.org/3/library/doctest.html).
+
+Example of these tests can be found in the [Kitchen Sink example](https://gitlab.com/unai-ltd/unai-decision/ruleau-core/-/tree/develop/examples/kitchen_sink/rules.py).
+
+### Generating Documentation
+
+Documentation for the rules can be generated using the `ruleau-docs` command.
+
+The usage is as follows:
+```
+ruleau-docs [--output-dir=<argument>] filename
+```
+
+For example for a file of rules called `rules.py` run:
+```
+ruleau-docs rules.py
+```
+
+## Building & Testing the Library
+
+### Pre-requisites
+
+* [Python 3.9+](https://www.python.org/downloads/)
+
+Package requirements installed by running:
+
+```shell
+pip install -r requirements-dev.txt
+```
+
+### Running the Tests
+
+To run all unit tests in the project use the following command:
+
+```shell
+pytest
+```
+
+
+%prep
+%autosetup -n ruleau-0.7.1
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-ruleau -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Thu May 18 2023 Python_Bot <Python_Bot@openeuler.org> - 0.7.1-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..873ca58
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+9aa8d909174d5d112d2818b01f97fbcd ruleau-0.7.1.tar.gz