Skip to content

Commit 5c4e541

Browse files
committed
Issue #39 feature/fixes
For #39 - [x] Labeller: Adds `==` operator for ID queries - [x] Labeller: Fixes offsetPage default value (should be 0)
1 parent 1221b34 commit 5c4e541

File tree

4 files changed

+131
-21
lines changed

4 files changed

+131
-21
lines changed

webtools/app.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
SQL3_DATABASE_TABLE = "en"
3434
SQL3_DATABASE = parent_dir / "train/data/training.sqlite3"
3535
MODEL_REQUIREMENTS = parent_dir / "requirements-dev.txt"
36+
RESERVED_LABELLER_SEARCH_CHARS = r"\*\*|\~\~|\=\="
3637

3738
# sqlite
3839
sqlite3.register_adapter(list, json.dumps)
@@ -50,19 +51,19 @@ def error_response(status: int, message: str = ""):
5051
"""Boilerplate for errors"""
5152
if status == 400:
5253
return jsonify(
53-
{"status": 400, "error": "Sorry, bad params", "message": None}
54+
{"status": 400, "error": "Sorry, bad params", "message": message}
5455
), 400
5556
elif status == 404:
5657
return jsonify(
57-
{"status": 404, "error": "Sorry, resource not found", "message": None}
58+
{"status": 404, "error": "Sorry, resource not found", "message": message}
5859
), 404
5960
elif status == 500:
6061
return jsonify(
6162
{"status": 404, "error": "Sorry, api failed", "message": message}
6263
), 500
6364
else:
6465
return jsonify(
65-
{"status": status, "error": "Sorry, something failed", "message": None}
66+
{"status": status, "error": "Sorry, something failed", "message": message}
6667
), 500
6768

6869

@@ -111,13 +112,13 @@ def parser():
111112

112113
try:
113114
sentence = data.get("sentence", "")
114-
discard_isolated_stop_words = data.get("discard_isolated_stop_words", False)
115-
expect_name_in_output = data.get("expect_name_in_output", False)
115+
discard_isolated_stop_words = data.get("discard_isolated_stop_words", True)
116+
expect_name_in_output = data.get("expect_name_in_output", True)
116117
string_units = data.get("string_units", False)
117118
imperial_units = data.get("imperial_units", False)
118-
foundation_foods = data.get("foundation_foods", False)
119+
foundation_foods = data.get("foundation_foods", True)
119120
optimistic_cache_reset = data.get("optimistic_cache_reset", False)
120-
separate_names = data.get("separate_names", False)
121+
separate_names = data.get("separate_names", True)
121122

122123
if optimistic_cache_reset:
123124
load_parser_model.cache_clear()
@@ -384,6 +385,12 @@ def labeller_bulk_upload():
384385
return error_response(status=404)
385386

386387

388+
def is_valid_dotnum_range(s: str) -> bool:
389+
"""Checks a str against the format "{digit}..{digit}"""
390+
391+
return bool(re.fullmatch(r"^\d*\.?\d*(?<!\.)\.\.(?!\.)\d*\.?\d*$", s))
392+
393+
387394
@app.route("/labeller/search", methods=["POST"])
388395
@cross_origin()
389396
def labeller_search():
@@ -410,12 +417,36 @@ def labeller_search():
410417
whole_word = data.get("wholeWord", False)
411418
case_sensitive = data.get("caseSensitive", False)
412419

420+
reserved_char_search = re.search(
421+
RESERVED_LABELLER_SEARCH_CHARS, sentence
422+
)
423+
reserved_char_match = (
424+
reserved_char_search.group() if reserved_char_search else None
425+
)
426+
427+
# reserve == for id search
428+
ids_reserved = []
429+
if reserved_char_match in ["=="]:
430+
ids_unique = map(str.strip, list(set(sentence[2:].split(","))))
431+
ids_actual = [
432+
ix
433+
for ix in ids_unique
434+
if ix.isdigit() or is_valid_dotnum_range(ix)
435+
]
436+
437+
for id in ids_actual:
438+
if is_valid_dotnum_range(id):
439+
start, stop = id.split("..")
440+
ids_reserved.extend(range(int(start), int(stop) + 1))
441+
elif id.isdigit():
442+
ids_reserved.append(int(id))
443+
413444
# preprocess for correct token comparison later
414445
sentence_preprocessed = PreProcessor(sentence).sentence
415446
# reserve ** or ~~ for wildcard, treat as empty string
416447
sentence_cleansed = (
417448
" "
418-
if re.search(r"\*\*|~~", sentence_preprocessed)
449+
if reserved_char_match in ["**", "~~"]
419450
else sentence_preprocessed
420451
)
421452

@@ -456,8 +487,10 @@ def labeller_search():
456487
if label in labels
457488
]
458489
)
459-
if query.search(partial_sentence) or (
460-
partial_sentence == sentence_cleansed
490+
if (
491+
row["id"] in ids_reserved
492+
or query.search(partial_sentence)
493+
or partial_sentence == sentence_cleansed
461494
):
462495
indices.append(row["id"])
463496

@@ -468,7 +501,7 @@ def labeller_search():
468501
cursor.execute(
469502
f"""
470503
SELECT *
471-
FROM en
504+
FROM {SQL3_DATABASE_TABLE}
472505
WHERE id IN ({",".join(["?"] * len(batch))})
473506
""",
474507
(batch),
@@ -488,7 +521,7 @@ def labeller_search():
488521
cursor.execute(
489522
f"""
490523
SELECT COUNT(*)
491-
FROM en
524+
FROM {SQL3_DATABASE_TABLE}
492525
WHERE id IN ({",".join(["?"] * len(batch))})
493526
""",
494527
(batch),

webtools/src/components/PageTabLabeller/TextInputSubmit/TextInputSubmit.tsx

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@ import {
66
type ActionIconProps,
77
Box,
88
Checkbox,
9+
Code,
10+
Divider,
911
Flex,
1012
Group,
1113
Kbd,
1214
Loader,
1315
Menu,
1416
MultiSelect,
1517
Popover,
18+
Stack,
1619
Text,
1720
TextInput,
1821
type TextInputProps,
22+
Title,
1923
Transition,
2024
} from "@mantine/core";
2125
import { getHotkeyHandler, useDisclosure } from "@mantine/hooks";
@@ -26,7 +30,7 @@ import {
2630
IconQuestionMark,
2731
IconX,
2832
} from "@tabler/icons-react";
29-
import { useState } from "react";
33+
import { type ReactNode, useState } from "react";
3034
import { useShallow } from "zustand/react/shallow";
3135
// {{{INTERNAL}}}
3236
import {
@@ -35,6 +39,68 @@ import {
3539
useTabLabellerStore,
3640
} from "../../../domain";
3741

42+
interface ReservedChars {
43+
chars: {
44+
value: string;
45+
label: string;
46+
}[];
47+
description: ReactNode;
48+
}
49+
50+
const reservedCharacters: ReservedChars[] = [
51+
{
52+
chars: [
53+
{ value: "**", label: "(double asterick)" },
54+
{ value: "~~", label: "(double tilde)" },
55+
],
56+
description: (
57+
<span>
58+
Reserved for wildcard searches. Use this to search the ingredient
59+
database without a keyword. For example, input only <Code>**</Code>.
60+
</span>
61+
),
62+
},
63+
{
64+
chars: [
65+
{ value: "== n,n+1", label: "(double equals, comma separated numbers)" },
66+
],
67+
description: (
68+
<span>
69+
Reserved for ID matched searches. Use this to directly query the
70+
ingredient database row IDs. Supports double dot notation for ID ranges.
71+
For example the input <Code>== 1,2,10..14</Code> would be interpreted as{" "}
72+
<Code>== 1,2,10,11,12,13,14</Code>.
73+
</span>
74+
),
75+
},
76+
];
77+
78+
function ReservedCharDescriptor({
79+
reservedChars,
80+
}: {
81+
reservedChars: ReservedChars;
82+
}) {
83+
const { chars, description } = reservedChars;
84+
85+
return (
86+
<Stack gap="xs" px="xs" py="sm">
87+
<Stack gap="xs">
88+
{chars.map(({ label, value }) => (
89+
<Group key={`rchar-${label}`} gap={3}>
90+
<Kbd>{value}</Kbd>
91+
<Text variant="light" size="xs">
92+
{label}
93+
</Text>
94+
</Group>
95+
))}
96+
</Stack>
97+
<Text variant="light" size="sm">
98+
{description}
99+
</Text>
100+
</Stack>
101+
);
102+
}
103+
38104
function ActionIconQuestion(props: ActionIconProps) {
39105
const [opened, { close, open }] = useDisclosure(false);
40106

@@ -44,7 +110,7 @@ function ActionIconQuestion(props: ActionIconProps) {
44110
shadow="md"
45111
keepMounted={false}
46112
position="bottom-end"
47-
width={180}
113+
width={350}
48114
offset={8}
49115
>
50116
<Popover.Target>
@@ -59,11 +125,22 @@ function ActionIconQuestion(props: ActionIconProps) {
59125
</ActionIcon>
60126
</Popover.Target>
61127

62-
<Popover.Dropdown>
63-
<Text variant="light" size="sm">
64-
Use double tilde <Kbd>~~</Kbd> or double asterick <Kbd>**</Kbd> to
65-
search all records against your filters
66-
</Text>
128+
<Popover.Dropdown p={0}>
129+
<Box px="xs" py="md">
130+
<Title order={4} lh={1}>
131+
Search Operators
132+
</Title>
133+
</Box>
134+
<Divider />
135+
{reservedCharacters.map((reservedChar, i) => (
136+
<>
137+
<ReservedCharDescriptor
138+
key={`rchar-descriptor-${reservedChar.description}`}
139+
reservedChars={reservedChar}
140+
/>
141+
{i + 1 !== reservedCharacters.length && <Divider />}
142+
</>
143+
))}
67144
</Popover.Dropdown>
68145
</Popover>
69146
);

webtools/src/domain/store/buttonUploadLabellerStore.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export const useUploadNewLabellersStore = create(
259259
sentence: "~~",
260260
settings: { ...input.settings, sources: [source] },
261261
},
262-
offsetPage: 1,
262+
offsetPage: 0,
263263
};
264264
getLabellerSearchApi(newInput);
265265
updateInput(newInput.input);

webtools/src/domain/store/tabLabellerStore.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export const useTabLabellerStore = create(
322322
},
323323
getLabellerSearchApi: async (opts?: TabLabellerInputProvided) => {
324324
const input = opts?.input || get().input;
325-
const offset = opts?.offsetPage || 1;
325+
const offset = opts?.offsetPage || 0;
326326

327327
if (input && input.sentence.length !== 0) {
328328
set({ loading: true });

0 commit comments

Comments
 (0)