Skip to content

Commit

Permalink
feature: 单元格补全策略调整
Browse files Browse the repository at this point in the history
  • Loading branch information
normal-wls committed Nov 30, 2023
1 parent 6362f17 commit cdee04c
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 23 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ decision_table = {
"rows": [ # 行对应的值
["[0..70]", '"boy"'],
["(70..80]", '"boy"'],
["(80..90]", '"boy"'], # 对于实际场景中,如果希望一行表示多列表达式,则直接用字符串类型进行描述,形如 'score in (80..90] and sex="boy"'
["(80..90]", '"boy"'], # 对于实际场景中,如果希望【单行表示多列表达式】,则直接用字符串类型进行描述,形如 'score in (80..90] and sex="boy"'
["(90..100]", '"boy"'],
["[0..60]", '"girl"'],
["(60..70]", '"girl"'],
Expand All @@ -47,6 +47,26 @@ decision_table = {
}
```

其中,输入表默认会将单元格中的表达式作为整体表达式进行真值判定,同时支持一些快捷填写方式(仅在未使用【单行表示多列表达式】的情况下):

| 快捷填写方式 | 补全后表达式(假设列名为 x) |
| --- | --- |
| ">=10" | "x>=10" |
| ">10" | "x>10" |
| "<=10" | "x<=10" |
| "<10" | "x<10" |
| "[1..2]" | "x in [1..2]" |
| "(1..2]" | "x in (1..2]" |
| '"boy"' | 'x="boy"' |
| "1" | "x=1" |
| "1.2" | "x=1.2" |
| "-1" | "x=-1" |
| "-2.3" | "x=-2.3" |
| "true" | "x=true" |
| "false" | "x=false" |
| "null" | "x=null" |


### 3. 调用函数进行决策

``` python
Expand Down
2 changes: 1 addition & 1 deletion bkflow_dmn/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-

__version__ = "0.1.1"
__version__ = "0.2.0"
58 changes: 40 additions & 18 deletions bkflow_dmn/data_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import re
from enum import Enum
from typing import List, Union

Expand Down Expand Up @@ -94,31 +95,52 @@ def hit_policy_value(self):
@property
def feel_exp_of_inputs(self):
col_ids = [col.id for col in self.inputs.cols]
operator_prefix = {
">": "",
"<": "",
"[": " in ",
"(": " in ",
}
or_operator = ","

result = []
for row in self.inputs.rows:
if isinstance(row, str):
result.append([row])
continue
row_exps = []
row_result = []
for idx, unit in enumerate(row):
pured_unit = unit.strip()
if pured_unit == "":
continue
unit_exps = []
vals = [val.strip() for val in pured_unit.split(or_operator) if val.strip()]
for val in vals:
if val[0] in operator_prefix:
unit_exps.append(f"{col_ids[idx]}{operator_prefix[val[0]]}{val}")
else:
unit_exps.append(f"{col_ids[idx]}={val}")
row_exps.append(f'({" or ".join(unit_exps)})' if len(unit_exps) > 1 else unit_exps[0])
result.append(row_exps)
handler = TableUnitHandler(unit_exp=pured_unit, col_id=col_ids[idx])
row_result.append(handler.get_handled_exp())
result.append(row_result)
return result


class TableUnitHandler:
OPERATOR_PATTERNS = {
r"^>(.*?)": "",
r"^<(.*?)": "",
r"[\[\(](.*?)\.\.(.*?)[\]\)]": " in ",
r"\"(.*?)\"": "=",
r"-?\d+(\.\d+)?": "=",
r"true": "=",
r"false": "=",
r"null": "=",
}
OR_OPERATOR = ","

def __init__(self, unit_exp: str, col_id: str):
self.exp = unit_exp
self.col_id = col_id

def get_handled_exp(self):
exp_splices = [exp.strip() for exp in self.exp.split(self.OR_OPERATOR)]
union_patterns = "|".join(self.OPERATOR_PATTERNS.keys())

# 不能进行自动补全的情况
if any([re.fullmatch(union_patterns, exp) is None for exp in exp_splices]):
return self.exp

handled_exp_splices = []
for exp in exp_splices:
for pattern, operator in self.OPERATOR_PATTERNS.items():
if re.fullmatch(pattern, exp):
handled_exp_splices.append(f"{self.col_id}{operator}{exp}")
break

return " or ".join(handled_exp_splices)
3 changes: 3 additions & 0 deletions release.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Release Notes

# 0.2.0
- 单元格补全策略调整

# 0.1.1
- 规则配置支持单行跨列规则

Expand Down
3 changes: 0 additions & 3 deletions tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
},
}


UNIQUE_SCORE_MULTI_ROW_FORMAT_TEST_TABLE = {
"title": "testing",
"hit_policy": "Unique",
Expand Down Expand Up @@ -62,7 +61,6 @@
},
}


UNIQUE_TEST_DATA = [
(UNIQUE_SCORE_TEST_TABLE, {"score": 10, "sex": "boy"}, [{"level": "bad"}]),
(UNIQUE_SCORE_TEST_TABLE, {"score": 80, "sex": "boy"}, [{"level": "good"}]),
Expand All @@ -78,7 +76,6 @@
(UNIQUE_SCORE_MULTI_ROW_FORMAT_TEST_TABLE, {"score": 84, "sex": "girl"}, [{"level": "good++"}]),
]


ANY_SCORE_TEST_TABLE = {
"title": "testing",
"hit_policy": "Any",
Expand Down
58 changes: 58 additions & 0 deletions tests/test_single_table_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

from bkflow_dmn.api import decide_single_table
from bkflow_dmn.data_model import SingleDecisionTable
from tests.data import (
UNIQUE_TEST_DATA,
ANY_TEST_DATA,
Expand Down Expand Up @@ -34,6 +35,63 @@ def test_single_table_decision_strict_mode(single_table, facts, expected):
assert result == expected


def test_single_table_inputs_autocomplete():
rows = [
[">=10"],
[">10"],
["[1..2]"],
["(1..2]"],
["<=10"],
["<10"],
['"boy"'],
["1"],
["-1"],
["1.2"],
["-2.3"],
["true"],
["false"],
["null"],
["1 and 1"],
["before(x, [2..3])"],
["< 10, > 20"],
["<10, >20"],
]
TEST_TABLE = {
"title": "testing",
"hit_policy": "Unique",
"inputs": {
"cols": [{"id": "input"}],
"rows": rows,
},
"outputs": {
"cols": [{"id": "output"}],
"rows": [[1]] * len(rows),
},
}
table = SingleDecisionTable(**TEST_TABLE)
auto_completed_inputs = table.feel_exp_of_inputs
assert auto_completed_inputs == [
["input>=10"],
["input>10"],
["input in [1..2]"],
["input in (1..2]"],
["input<=10"],
["input<10"],
['input="boy"'],
["input=1"],
["input=-1"],
["input=1.2"],
["input=-2.3"],
["input=true"],
["input=false"],
["input=null"],
["1 and 1"],
["before(x, [2..3])"],
["input< 10 or input> 20"],
["input<10 or input>20"],
]


@pytest.mark.parametrize(
"single_table, facts, expected",
test_data,
Expand Down

0 comments on commit cdee04c

Please sign in to comment.