From b4475e7e3da5af2c9677fdc47a3687528c6f50b9 Mon Sep 17 00:00:00 2001 From: Ayrat Hudaygulov Date: Wed, 7 Aug 2024 20:07:19 +0100 Subject: [PATCH] added recurring retrain and additional marker (less than X messages) to the model --- .env.example | 4 +- src/VahterBanBot.Tests/ContainerTestBase.fs | 2 - src/VahterBanBot.Tests/test_seed.sql | 1026 +++++++++---------- src/VahterBanBot/Bot.fs | 4 +- src/VahterBanBot/DB.fs | 88 +- src/VahterBanBot/ML.fs | 105 +- src/VahterBanBot/Program.fs | 4 +- src/VahterBanBot/Types.fs | 4 +- src/VahterBanBot/UpdateChatAdmins.fs | 4 +- 9 files changed, 653 insertions(+), 588 deletions(-) diff --git a/.env.example b/.env.example index 062a3c9..0406b69 100644 --- a/.env.example +++ b/.env.example @@ -16,9 +16,11 @@ CLEANUP_OLD_MESSAGES=true CLEANUP_INTERVAL_SEC=86400 CLEANUP_OLD_LIMIT_SEC=259200 ML_ENABLED=false +ML_RETRAIN_INTERVAL_SEC=86400 ML_SEED= ML_SPAM_DELETION_ENABLED=false -ML_TRAIN_BEFORE_DATE=2021-01-01 +ML_TRAIN_INTERVAL_DAYS=30 +ML_TRAIN_CRITICAL_MSG_COUNT=5 ML_TRAINING_SET_FRACTION=0.2 ML_SPAM_THRESHOLD=0.5 ML_WARNING_THRESHOLD=0.0 diff --git a/src/VahterBanBot.Tests/ContainerTestBase.fs b/src/VahterBanBot.Tests/ContainerTestBase.fs index cc9a562..4e2e1de 100644 --- a/src/VahterBanBot.Tests/ContainerTestBase.fs +++ b/src/VahterBanBot.Tests/ContainerTestBase.fs @@ -93,8 +93,6 @@ type VahterTestContainers() = .WithEnvironment("DATABASE_URL", internalConnectionString) .WithEnvironment("CLEANUP_OLD_MESSAGES", "false") .WithEnvironment("ML_ENABLED", "true") - // seed data uses 2021-01-01 as a date for all messages - .WithEnvironment("ML_TRAIN_BEFORE_DATE", "2021-01-02T00:00:00Z") .WithEnvironment("ML_SEED", "42") .WithEnvironment("ML_SPAM_DELETION_ENABLED", "true") .WithEnvironment("ML_SPAM_THRESHOLD", "1.0") diff --git a/src/VahterBanBot.Tests/test_seed.sql b/src/VahterBanBot.Tests/test_seed.sql index f58af51..7d3a16d 100644 --- a/src/VahterBanBot.Tests/test_seed.sql +++ b/src/VahterBanBot.Tests/test_seed.sql @@ -16,531 +16,531 @@ VALUES (1001, 'a', NULL, NULL, NULL), (1010, 'j', NULL, NULL, NULL); INSERT INTO public.message(chat_id, message_id, user_id, created_at, text, raw_message) -VALUES (-666, 10001, 1001, '2021-01-01 00:00:00', 'a', '{}'), -- false positive user banned - (-666, 10002, 1001, '2021-01-01 00:00:01', 'aa', '{}'), - (-666, 10003, 1001, '2021-01-01 00:00:02', 'aaa', '{}'), - (-666, 10004, 1002, '2021-01-01 00:00:03', 'aaaa', '{}'), - (-666, 10005, 1002, '2021-01-01 00:00:04', 'aaaaa', '{}'), - (-666, 10006, 1003, '2021-01-01 00:00:05', 'aaaaaa', '{}'), - (-666, 10007, 1003, '2021-01-01 00:00:06', 'aaaaaaa', '{}'), - (-666, 10008, 1004, '2021-01-01 00:00:07', 'a', '{}'), -- false positive message banned - (-666, 10009, 1005, '2021-01-01 00:00:08', '1', '{}'), - (-666, 10010, 1005, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10001, 1001, '2021-01-01 00:00:00', 'a', '{}'), - (-42, 10002, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-42, 10003, 1001, '2021-01-01 00:00:02', 'a', '{}'), - (-42, 10004, 1002, '2021-01-01 00:00:03', 'a', '{}'), - (-42, 10005, 1002, '2021-01-01 00:00:04', 'a', '{}'), - (-42, 10006, 1003, '2021-01-01 00:00:05', 'a', '{}'), - (-42, 10007, 1003, '2021-01-01 00:00:06', 'a', '{}'), - (-42, 10008, 1004, '2021-01-01 00:00:07', '3', '{}'), -- false negative - (-42, 10009, 1006, '2021-01-01 00:00:08', '1', '{}'), +VALUES (-666, 10001, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), -- false positive user banned + (-666, 10002, 1001, now() - '1 day'::INTERVAL, 'aa', '{}'), + (-666, 10003, 1001, now() - '1 day'::INTERVAL, 'aaa', '{}'), + (-666, 10004, 1002, now() - '1 day'::INTERVAL, 'aaaa', '{}'), + (-666, 10005, 1002, now() - '1 day'::INTERVAL, 'aaaaa', '{}'), + (-666, 10006, 1003, now() - '1 day'::INTERVAL, 'aaaaaa', '{}'), + (-666, 10007, 1003, now() - '1 day'::INTERVAL, 'aaaaaaa', '{}'), + (-666, 10008, 1004, now() - '1 day'::INTERVAL, 'a', '{}'), -- false positive message banned + (-666, 10009, 1005, now() - '1 day'::INTERVAL, '1', '{}'), + (-666, 10010, 1005, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10001, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10002, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10003, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10004, 1002, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10005, 1002, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10006, 1003, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10007, 1003, now() - '1 day'::INTERVAL, 'a', '{}'), + (-42, 10008, 1004, now() - '1 day'::INTERVAL, '3', '{}'), -- false negative + (-42, 10009, 1006, now() - '1 day'::INTERVAL, '1', '{}'), -- to prevent small sample size, we'll copy the next line 100 times -- this is spam - (-42, 10010, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10011, 1006, '2021-01-01 00:00:09', '11', '{}'), - (-42, 10012, 1006, '2021-01-01 00:00:09', '111', '{}'), - (-42, 10013, 1006, '2021-01-01 00:00:09', '1111', '{}'), - (-42, 10014, 1006, '2021-01-01 00:00:09', '11111', '{}'), - (-42, 10015, 1006, '2021-01-01 00:00:09', '111111', '{}'), - (-42, 10016, 1006, '2021-01-01 00:00:09', '1111111', '{}'), - (-42, 10017, 1006, '2021-01-01 00:00:09', '11111111', '{}'), - (-42, 10018, 1006, '2021-01-01 00:00:09', '111111111', '{}'), - (-42, 10019, 1006, '2021-01-01 00:00:09', '1111111111', '{}'), - (-42, 10020, 1006, '2021-01-01 00:00:09', '11111111111', '{}'), - (-42, 10021, 1006, '2021-01-01 00:00:09', '111111111111', '{}'), - (-42, 10022, 1006, '2021-01-01 00:00:09', '1111111111111', '{}'), - (-42, 10023, 1006, '2021-01-01 00:00:09', '11111111111111', '{}'), - (-42, 10024, 1006, '2021-01-01 00:00:09', '111111111111111', '{}'), - (-42, 10025, 1006, '2021-01-01 00:00:09', '1111111111111111', '{}'), - (-42, 10026, 1006, '2021-01-01 00:00:09', '11111111111111111', '{}'), - (-42, 10027, 1006, '2021-01-01 00:00:09', '111111111111111111', '{}'), - (-42, 10028, 1006, '2021-01-01 00:00:09', '1111111111111111111', '{}'), - (-42, 10029, 1006, '2021-01-01 00:00:09', '11111111111111111111', '{}'), - (-42, 10030, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10031, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10032, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10033, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10034, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10035, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10036, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10037, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10038, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10039, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10040, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10041, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10042, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10043, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10044, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10045, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10046, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10047, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10048, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10049, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10050, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10051, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10052, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10053, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10054, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10055, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10056, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10057, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10058, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10059, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10060, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10061, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10062, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10063, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10064, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10065, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10066, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10067, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10068, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10069, 1006, '2021-01-01 00:00:09', '1', '{}'), - (-42, 10070, 1006, '2021-01-01 00:00:09', '2', '{}'), - (-42, 10071, 1006, '2021-01-01 00:00:09', '2', '{}'), - (-42, 10072, 1006, '2021-01-01 00:00:09', '2', '{}'), - (-42, 10073, 1006, '2021-01-01 00:00:09', '2', '{}'), - (-42, 10074, 1006, '2021-01-01 00:00:09', '22', '{}'), - (-42, 10075, 1006, '2021-01-01 00:00:09', '222', '{}'), - (-42, 10076, 1006, '2021-01-01 00:00:09', '2222', '{}'), - (-42, 10077, 1006, '2021-01-01 00:00:09', '22222', '{}'), - (-42, 10078, 1006, '2021-01-01 00:00:09', '222222', '{}'), - (-42, 10079, 1006, '2021-01-01 00:00:09', '2222222', '{}'), - (-42, 10080, 1006, '2021-01-01 00:00:09', '22222222', '{}'), - (-42, 10081, 1006, '2021-01-01 00:00:09', '222222222', '{}'), - (-42, 10082, 1006, '2021-01-01 00:00:09', '2222222222', '{}'), - (-42, 10083, 1006, '2021-01-01 00:00:09', '22222222222', '{}'), - (-42, 10084, 1006, '2021-01-01 00:00:09', '222222222222', '{}'), - (-42, 10085, 1006, '2021-01-01 00:00:09', '2222222222222', '{}'), - (-42, 10086, 1006, '2021-01-01 00:00:09', '22222222222222', '{}'), - (-42, 10087, 1006, '2021-01-01 00:00:09', '222222222222222', '{}'), - (-42, 10088, 1006, '2021-01-01 00:00:09', '2222222222222222', '{}'), - (-42, 10089, 1006, '2021-01-01 00:00:09', '22222222222222222', '{}'), - (-42, 10090, 1006, '2021-01-01 00:00:09', '222222222222222222', '{}'), - (-42, 10091, 1006, '2021-01-01 00:00:09', '2222222222222222222', '{}'), - (-42, 10092, 1006, '2021-01-01 00:00:09', '22222222222222222222', '{}'), - (-42, 10093, 1006, '2021-01-01 00:00:09', '222222222222222222222', '{}'), - (-42, 10094, 1006, '2021-01-01 00:00:09', '2222222222222222222222', '{}'), - (-42, 10095, 1006, '2021-01-01 00:00:09', '22222222222222222222222', '{}'), - (-42, 10096, 1006, '2021-01-01 00:00:09', '222222222222222222222222', '{}'), - (-42, 10097, 1006, '2021-01-01 00:00:09', '2222222222222222222222222', '{}'), - (-42, 10098, 1006, '2021-01-01 00:00:09', '22222222222222222222222222', '{}'), - (-42, 10099, 1006, '2021-01-01 00:00:09', '222222222222222222222222222', '{}'), + (-42, 10010, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10011, 1006, now() - '1 day'::INTERVAL, '11', '{}'), + (-42, 10012, 1006, now() - '1 day'::INTERVAL, '111', '{}'), + (-42, 10013, 1006, now() - '1 day'::INTERVAL, '1111', '{}'), + (-42, 10014, 1006, now() - '1 day'::INTERVAL, '11111', '{}'), + (-42, 10015, 1006, now() - '1 day'::INTERVAL, '111111', '{}'), + (-42, 10016, 1006, now() - '1 day'::INTERVAL, '1111111', '{}'), + (-42, 10017, 1006, now() - '1 day'::INTERVAL, '11111111', '{}'), + (-42, 10018, 1006, now() - '1 day'::INTERVAL, '111111111', '{}'), + (-42, 10019, 1006, now() - '1 day'::INTERVAL, '1111111111', '{}'), + (-42, 10020, 1006, now() - '1 day'::INTERVAL, '11111111111', '{}'), + (-42, 10021, 1006, now() - '1 day'::INTERVAL, '111111111111', '{}'), + (-42, 10022, 1006, now() - '1 day'::INTERVAL, '1111111111111', '{}'), + (-42, 10023, 1006, now() - '1 day'::INTERVAL, '11111111111111', '{}'), + (-42, 10024, 1006, now() - '1 day'::INTERVAL, '111111111111111', '{}'), + (-42, 10025, 1006, now() - '1 day'::INTERVAL, '1111111111111111', '{}'), + (-42, 10026, 1006, now() - '1 day'::INTERVAL, '11111111111111111', '{}'), + (-42, 10027, 1006, now() - '1 day'::INTERVAL, '111111111111111111', '{}'), + (-42, 10028, 1006, now() - '1 day'::INTERVAL, '1111111111111111111', '{}'), + (-42, 10029, 1006, now() - '1 day'::INTERVAL, '11111111111111111111', '{}'), + (-42, 10030, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10031, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10032, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10033, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10034, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10035, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10036, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10037, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10038, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10039, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10040, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10041, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10042, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10043, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10044, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10045, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10046, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10047, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10048, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10049, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10050, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10051, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10052, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10053, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10054, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10055, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10056, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10057, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10058, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10059, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10060, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10061, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10062, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10063, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10064, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10065, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10066, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10067, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10068, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10069, 1006, now() - '1 day'::INTERVAL, '1', '{}'), + (-42, 10070, 1006, now() - '1 day'::INTERVAL, '2', '{}'), + (-42, 10071, 1006, now() - '1 day'::INTERVAL, '2', '{}'), + (-42, 10072, 1006, now() - '1 day'::INTERVAL, '2', '{}'), + (-42, 10073, 1006, now() - '1 day'::INTERVAL, '2', '{}'), + (-42, 10074, 1006, now() - '1 day'::INTERVAL, '22', '{}'), + (-42, 10075, 1006, now() - '1 day'::INTERVAL, '222', '{}'), + (-42, 10076, 1006, now() - '1 day'::INTERVAL, '2222', '{}'), + (-42, 10077, 1006, now() - '1 day'::INTERVAL, '22222', '{}'), + (-42, 10078, 1006, now() - '1 day'::INTERVAL, '222222', '{}'), + (-42, 10079, 1006, now() - '1 day'::INTERVAL, '2222222', '{}'), + (-42, 10080, 1006, now() - '1 day'::INTERVAL, '22222222', '{}'), + (-42, 10081, 1006, now() - '1 day'::INTERVAL, '222222222', '{}'), + (-42, 10082, 1006, now() - '1 day'::INTERVAL, '2222222222', '{}'), + (-42, 10083, 1006, now() - '1 day'::INTERVAL, '22222222222', '{}'), + (-42, 10084, 1006, now() - '1 day'::INTERVAL, '222222222222', '{}'), + (-42, 10085, 1006, now() - '1 day'::INTERVAL, '2222222222222', '{}'), + (-42, 10086, 1006, now() - '1 day'::INTERVAL, '22222222222222', '{}'), + (-42, 10087, 1006, now() - '1 day'::INTERVAL, '222222222222222', '{}'), + (-42, 10088, 1006, now() - '1 day'::INTERVAL, '2222222222222222', '{}'), + (-42, 10089, 1006, now() - '1 day'::INTERVAL, '22222222222222222', '{}'), + (-42, 10090, 1006, now() - '1 day'::INTERVAL, '222222222222222222', '{}'), + (-42, 10091, 1006, now() - '1 day'::INTERVAL, '2222222222222222222', '{}'), + (-42, 10092, 1006, now() - '1 day'::INTERVAL, '22222222222222222222', '{}'), + (-42, 10093, 1006, now() - '1 day'::INTERVAL, '222222222222222222222', '{}'), + (-42, 10094, 1006, now() - '1 day'::INTERVAL, '2222222222222222222222', '{}'), + (-42, 10095, 1006, now() - '1 day'::INTERVAL, '22222222222222222222222', '{}'), + (-42, 10096, 1006, now() - '1 day'::INTERVAL, '222222222222222222222222', '{}'), + (-42, 10097, 1006, now() - '1 day'::INTERVAL, '2222222222222222222222222', '{}'), + (-42, 10098, 1006, now() - '1 day'::INTERVAL, '22222222222222222222222222', '{}'), + (-42, 10099, 1006, now() - '1 day'::INTERVAL, '222222222222222222222222222', '{}'), -- this is not spam - (-666, 10100, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10101, 1001, '2021-01-01 00:00:01', 'b', '{}'), - (-666, 10102, 1001, '2021-01-01 00:00:01', 'c', '{}'), - (-666, 10103, 1001, '2021-01-01 00:00:01', 'd', '{}'), - (-666, 10104, 1001, '2021-01-01 00:00:01', 'e', '{}'), - (-666, 10105, 1001, '2021-01-01 00:00:01', 'f', '{}'), - (-666, 10106, 1001, '2021-01-01 00:00:01', 'g', '{}'), - (-666, 10107, 1001, '2021-01-01 00:00:01', 'h', '{}'), - (-666, 10108, 1001, '2021-01-01 00:00:01', 'i', '{}'), - (-666, 10109, 1001, '2021-01-01 00:00:01', 'j', '{}'), - (-666, 10110, 1001, '2021-01-01 00:00:01', 'k', '{}'), - (-666, 10111, 1001, '2021-01-01 00:00:01', 'l', '{}'), - (-666, 10112, 1001, '2021-01-01 00:00:01', 'm', '{}'), - (-666, 10113, 1001, '2021-01-01 00:00:01', 'n', '{}'), - (-666, 10114, 1001, '2021-01-01 00:00:01', 'o', '{}'), - (-666, 10115, 1001, '2021-01-01 00:00:01', 'p', '{}'), - (-666, 10116, 1001, '2021-01-01 00:00:01', 'q', '{}'), - (-666, 10117, 1001, '2021-01-01 00:00:01', 'r', '{}'), - (-666, 10118, 1001, '2021-01-01 00:00:01', 's', '{}'), - (-666, 10119, 1001, '2021-01-01 00:00:01', 't', '{}'), - (-666, 10120, 1001, '2021-01-01 00:00:01', 'u', '{}'), - (-666, 10121, 1001, '2021-01-01 00:00:01', 'v', '{}'), - (-666, 10122, 1001, '2021-01-01 00:00:01', 'w', '{}'), - (-666, 10123, 1001, '2021-01-01 00:00:01', 'x', '{}'), - (-666, 10124, 1001, '2021-01-01 00:00:01', 'y', '{}'), - (-666, 10125, 1001, '2021-01-01 00:00:01', 'z', '{}'), - (-666, 10126, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10127, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10128, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10129, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10130, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10131, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10132, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10133, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10134, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10135, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10136, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10137, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10138, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10139, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10140, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10141, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10142, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10143, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10144, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10145, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10146, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10147, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10148, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10149, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10150, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10151, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10152, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10153, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10154, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10155, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10156, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10157, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10158, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10159, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10160, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10161, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10162, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10163, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10164, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10165, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10166, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10167, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10168, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10169, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10170, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10171, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10172, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10173, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10174, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10175, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10176, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10177, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10178, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10179, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10180, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10181, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10182, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10183, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10184, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10185, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10186, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10187, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10188, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10189, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10190, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10191, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10192, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10193, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10194, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10195, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10196, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10197, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10198, 1001, '2021-01-01 00:00:01', 'a', '{}'), - (-666, 10199, 1001, '2021-01-01 00:00:01', 'a', '{}'), + (-666, 10100, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10101, 1001, now() - '1 day'::INTERVAL, 'b', '{}'), + (-666, 10102, 1001, now() - '1 day'::INTERVAL, 'c', '{}'), + (-666, 10103, 1001, now() - '1 day'::INTERVAL, 'd', '{}'), + (-666, 10104, 1001, now() - '1 day'::INTERVAL, 'e', '{}'), + (-666, 10105, 1001, now() - '1 day'::INTERVAL, 'f', '{}'), + (-666, 10106, 1001, now() - '1 day'::INTERVAL, 'g', '{}'), + (-666, 10107, 1001, now() - '1 day'::INTERVAL, 'h', '{}'), + (-666, 10108, 1001, now() - '1 day'::INTERVAL, 'i', '{}'), + (-666, 10109, 1001, now() - '1 day'::INTERVAL, 'j', '{}'), + (-666, 10110, 1001, now() - '1 day'::INTERVAL, 'k', '{}'), + (-666, 10111, 1001, now() - '1 day'::INTERVAL, 'l', '{}'), + (-666, 10112, 1001, now() - '1 day'::INTERVAL, 'm', '{}'), + (-666, 10113, 1001, now() - '1 day'::INTERVAL, 'n', '{}'), + (-666, 10114, 1001, now() - '1 day'::INTERVAL, 'o', '{}'), + (-666, 10115, 1001, now() - '1 day'::INTERVAL, 'p', '{}'), + (-666, 10116, 1001, now() - '1 day'::INTERVAL, 'q', '{}'), + (-666, 10117, 1001, now() - '1 day'::INTERVAL, 'r', '{}'), + (-666, 10118, 1001, now() - '1 day'::INTERVAL, 's', '{}'), + (-666, 10119, 1001, now() - '1 day'::INTERVAL, 't', '{}'), + (-666, 10120, 1001, now() - '1 day'::INTERVAL, 'u', '{}'), + (-666, 10121, 1001, now() - '1 day'::INTERVAL, 'v', '{}'), + (-666, 10122, 1001, now() - '1 day'::INTERVAL, 'w', '{}'), + (-666, 10123, 1001, now() - '1 day'::INTERVAL, 'x', '{}'), + (-666, 10124, 1001, now() - '1 day'::INTERVAL, 'y', '{}'), + (-666, 10125, 1001, now() - '1 day'::INTERVAL, 'z', '{}'), + (-666, 10126, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10127, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10128, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10129, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10130, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10131, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10132, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10133, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10134, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10135, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10136, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10137, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10138, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10139, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10140, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10141, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10142, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10143, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10144, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10145, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10146, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10147, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10148, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10149, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10150, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10151, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10152, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10153, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10154, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10155, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10156, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10157, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10158, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10159, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10160, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10161, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10162, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10163, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10164, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10165, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10166, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10167, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10168, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10169, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10170, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10171, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10172, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10173, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10174, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10175, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10176, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10177, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10178, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10179, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10180, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10181, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10182, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10183, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10184, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10185, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10186, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10187, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10188, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10189, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10190, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10191, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10192, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10193, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10194, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10195, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10196, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10197, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10198, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), + (-666, 10199, 1001, now() - '1 day'::INTERVAL, 'a', '{}'), -- to enforce false-negative appearance of 3 - (-666, 10200, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10201, 1001, '2021-01-01 00:00:01', '33', '{}'), - (-666, 10202, 1001, '2021-01-01 00:00:01', '333', '{}'), - (-666, 10203, 1001, '2021-01-01 00:00:01', '3333', '{}'), - (-666, 10204, 1001, '2021-01-01 00:00:01', '33333', '{}'), - (-666, 10205, 1001, '2021-01-01 00:00:01', '333333', '{}'), - (-666, 10206, 1001, '2021-01-01 00:00:01', '3333333', '{}'), - (-666, 10207, 1001, '2021-01-01 00:00:01', '33333333', '{}'), - (-666, 10208, 1001, '2021-01-01 00:00:01', '333333333', '{}'), - (-666, 10209, 1001, '2021-01-01 00:00:01', '3333333333', '{}'), - (-666, 10210, 1001, '2021-01-01 00:00:01', '33333333333', '{}'), - (-666, 10211, 1001, '2021-01-01 00:00:01', '333333333333', '{}'), - (-666, 10212, 1001, '2021-01-01 00:00:01', '3333333333333', '{}'), - (-666, 10213, 1001, '2021-01-01 00:00:01', '33333333333333', '{}'), - (-666, 10214, 1001, '2021-01-01 00:00:01', '333333333333333', '{}'), - (-666, 10215, 1001, '2021-01-01 00:00:01', '3333333333333333', '{}'), - (-666, 10216, 1001, '2021-01-01 00:00:01', '33333333333333333', '{}'), - (-666, 10217, 1001, '2021-01-01 00:00:01', '333333333333333333', '{}'), - (-666, 10218, 1001, '2021-01-01 00:00:01', '3333333333333333333', '{}'), - (-666, 10219, 1001, '2021-01-01 00:00:01', '33333333333333333333', '{}'), - (-666, 10220, 1001, '2021-01-01 00:00:01', '333333333333333333333', '{}'), - (-666, 10221, 1001, '2021-01-01 00:00:01', '3333333333333333333333', '{}'), - (-666, 10222, 1001, '2021-01-01 00:00:01', '33333333333333333333333', '{}'), - (-666, 10223, 1001, '2021-01-01 00:00:01', '333333333333333333333333', '{}'), - (-666, 10224, 1001, '2021-01-01 00:00:01', '3333333333333333333333333', '{}'), - (-666, 10225, 1001, '2021-01-01 00:00:01', '33333333333333333333333333', '{}'), - (-666, 10226, 1001, '2021-01-01 00:00:01', '333333333333333333333333333', '{}'), - (-666, 10227, 1001, '2021-01-01 00:00:01', '3333333333333333333333333333', '{}'), - (-666, 10228, 1001, '2021-01-01 00:00:01', '33333333333333333333333333333', '{}'), - (-666, 10229, 1001, '2021-01-01 00:00:01', '333333333333333333333333333333', '{}'), - (-666, 10230, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10231, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10232, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10233, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10234, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10235, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10236, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10237, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10238, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10239, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10240, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10241, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10242, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10243, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10244, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10245, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10246, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10247, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10248, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10249, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10250, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10251, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10252, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10253, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10254, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10255, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10256, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10257, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10258, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10259, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10260, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10261, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10262, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10263, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10264, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10265, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10266, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10267, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10268, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10269, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10270, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10271, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10272, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10273, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10274, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10275, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10276, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10277, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10278, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10279, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10280, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10281, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10282, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10283, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10284, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10285, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10286, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10287, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10288, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10289, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10290, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10291, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10292, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10293, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10294, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10295, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10296, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10297, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10298, 1001, '2021-01-01 00:00:01', '3', '{}'), - (-666, 10299, 1001, '2021-01-01 00:00:01', '3', '{}'), + (-666, 10200, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10201, 1001, now() - '1 day'::INTERVAL, '33', '{}'), + (-666, 10202, 1001, now() - '1 day'::INTERVAL, '333', '{}'), + (-666, 10203, 1001, now() - '1 day'::INTERVAL, '3333', '{}'), + (-666, 10204, 1001, now() - '1 day'::INTERVAL, '33333', '{}'), + (-666, 10205, 1001, now() - '1 day'::INTERVAL, '333333', '{}'), + (-666, 10206, 1001, now() - '1 day'::INTERVAL, '3333333', '{}'), + (-666, 10207, 1001, now() - '1 day'::INTERVAL, '33333333', '{}'), + (-666, 10208, 1001, now() - '1 day'::INTERVAL, '333333333', '{}'), + (-666, 10209, 1001, now() - '1 day'::INTERVAL, '3333333333', '{}'), + (-666, 10210, 1001, now() - '1 day'::INTERVAL, '33333333333', '{}'), + (-666, 10211, 1001, now() - '1 day'::INTERVAL, '333333333333', '{}'), + (-666, 10212, 1001, now() - '1 day'::INTERVAL, '3333333333333', '{}'), + (-666, 10213, 1001, now() - '1 day'::INTERVAL, '33333333333333', '{}'), + (-666, 10214, 1001, now() - '1 day'::INTERVAL, '333333333333333', '{}'), + (-666, 10215, 1001, now() - '1 day'::INTERVAL, '3333333333333333', '{}'), + (-666, 10216, 1001, now() - '1 day'::INTERVAL, '33333333333333333', '{}'), + (-666, 10217, 1001, now() - '1 day'::INTERVAL, '333333333333333333', '{}'), + (-666, 10218, 1001, now() - '1 day'::INTERVAL, '3333333333333333333', '{}'), + (-666, 10219, 1001, now() - '1 day'::INTERVAL, '33333333333333333333', '{}'), + (-666, 10220, 1001, now() - '1 day'::INTERVAL, '333333333333333333333', '{}'), + (-666, 10221, 1001, now() - '1 day'::INTERVAL, '3333333333333333333333', '{}'), + (-666, 10222, 1001, now() - '1 day'::INTERVAL, '33333333333333333333333', '{}'), + (-666, 10223, 1001, now() - '1 day'::INTERVAL, '333333333333333333333333', '{}'), + (-666, 10224, 1001, now() - '1 day'::INTERVAL, '3333333333333333333333333', '{}'), + (-666, 10225, 1001, now() - '1 day'::INTERVAL, '33333333333333333333333333', '{}'), + (-666, 10226, 1001, now() - '1 day'::INTERVAL, '333333333333333333333333333', '{}'), + (-666, 10227, 1001, now() - '1 day'::INTERVAL, '3333333333333333333333333333', '{}'), + (-666, 10228, 1001, now() - '1 day'::INTERVAL, '33333333333333333333333333333', '{}'), + (-666, 10229, 1001, now() - '1 day'::INTERVAL, '333333333333333333333333333333', '{}'), + (-666, 10230, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10231, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10232, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10233, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10234, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10235, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10236, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10237, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10238, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10239, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10240, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10241, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10242, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10243, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10244, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10245, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10246, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10247, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10248, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10249, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10250, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10251, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10252, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10253, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10254, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10255, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10256, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10257, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10258, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10259, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10260, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10261, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10262, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10263, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10264, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10265, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10266, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10267, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10268, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10269, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10270, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10271, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10272, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10273, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10274, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10275, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10276, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10277, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10278, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10279, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10280, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10281, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10282, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10283, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10284, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10285, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10286, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10287, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10288, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10289, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10290, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10291, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10292, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10293, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10294, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10295, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10296, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10297, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10298, 1001, now() - '1 day'::INTERVAL, '3', '{}'), + (-666, 10299, 1001, now() - '1 day'::INTERVAL, '3', '{}'), -- to enforce false-negative appearance of 6 - (-666, 10300, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10301, 1001, '2021-01-01 00:00:01', '66', '{}'), - (-666, 10302, 1001, '2021-01-01 00:00:01', '666', '{}'), - (-666, 10303, 1001, '2021-01-01 00:00:01', '6666', '{}'), - (-666, 10304, 1001, '2021-01-01 00:00:01', '66666', '{}'), - (-666, 10305, 1001, '2021-01-01 00:00:01', '666666', '{}'), - (-666, 10306, 1001, '2021-01-01 00:00:01', '6666666', '{}'), - (-666, 10307, 1001, '2021-01-01 00:00:01', '66666666', '{}'), - (-666, 10308, 1001, '2021-01-01 00:00:01', '6666666666', '{}'), - (-666, 10309, 1001, '2021-01-01 00:00:01', '66666666666', '{}'), - (-666, 10310, 1001, '2021-01-01 00:00:01', '666666666666', '{}'), - (-666, 10311, 1001, '2021-01-01 00:00:01', '6666666666666', '{}'), - (-666, 10312, 1001, '2021-01-01 00:00:01', '66666666666666', '{}'), - (-666, 10313, 1001, '2021-01-01 00:00:01', '666666666666666', '{}'), - (-666, 10314, 1001, '2021-01-01 00:00:01', '6666666666666666', '{}'), - (-666, 10315, 1001, '2021-01-01 00:00:01', '66666666666666666', '{}'), - (-666, 10316, 1001, '2021-01-01 00:00:01', '666666666666666666', '{}'), - (-666, 10317, 1001, '2021-01-01 00:00:01', '6666666666666666666', '{}'), - (-666, 10318, 1001, '2021-01-01 00:00:01', '66666666666666666666', '{}'), - (-666, 10319, 1001, '2021-01-01 00:00:01', '666666666666666666666', '{}'), - (-666, 10320, 1001, '2021-01-01 00:00:01', '6666666666666666666666', '{}'), - (-666, 10321, 1001, '2021-01-01 00:00:01', '66666666666666666666666', '{}'), - (-666, 10322, 1001, '2021-01-01 00:00:01', '666666666666666666666666', '{}'), - (-666, 10323, 1001, '2021-01-01 00:00:01', '6666666666666666666666666', '{}'), - (-666, 10324, 1001, '2021-01-01 00:00:01', '66666666666666666666666666', '{}'), - (-666, 10325, 1001, '2021-01-01 00:00:01', '666666666666666666666666666', '{}'), - (-666, 10326, 1001, '2021-01-01 00:00:01', '6666666666666666666666666666', '{}'), - (-666, 10327, 1001, '2021-01-01 00:00:01', '66666666666666666666666666666', '{}'), - (-666, 10328, 1001, '2021-01-01 00:00:01', '666666666666666666666666666666', '{}'), - (-666, 10329, 1001, '2021-01-01 00:00:01', '6666666666666666666666666666666', '{}'), - (-666, 10330, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10331, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10332, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10333, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10334, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10335, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10336, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10337, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10338, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10339, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10340, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10341, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10342, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10343, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10344, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10345, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10346, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10347, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10348, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10349, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10350, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10351, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10352, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10353, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10354, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10355, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10356, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10357, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10358, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10359, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10360, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10361, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10362, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10363, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10364, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10365, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10366, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10367, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10368, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10369, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10370, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10371, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10372, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10373, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10374, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10375, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10376, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10377, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10378, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10379, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10380, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10381, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10382, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10383, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10384, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10385, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10386, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10387, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10388, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10389, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10390, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10391, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10392, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10393, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10394, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10395, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10396, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10397, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10398, 1001, '2021-01-01 00:00:01', '6', '{}'), - (-666, 10399, 1001, '2021-01-01 00:00:01', '6', '{}'), + (-666, 10300, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10301, 1001, now() - '1 day'::INTERVAL, '66', '{}'), + (-666, 10302, 1001, now() - '1 day'::INTERVAL, '666', '{}'), + (-666, 10303, 1001, now() - '1 day'::INTERVAL, '6666', '{}'), + (-666, 10304, 1001, now() - '1 day'::INTERVAL, '66666', '{}'), + (-666, 10305, 1001, now() - '1 day'::INTERVAL, '666666', '{}'), + (-666, 10306, 1001, now() - '1 day'::INTERVAL, '6666666', '{}'), + (-666, 10307, 1001, now() - '1 day'::INTERVAL, '66666666', '{}'), + (-666, 10308, 1001, now() - '1 day'::INTERVAL, '6666666666', '{}'), + (-666, 10309, 1001, now() - '1 day'::INTERVAL, '66666666666', '{}'), + (-666, 10310, 1001, now() - '1 day'::INTERVAL, '666666666666', '{}'), + (-666, 10311, 1001, now() - '1 day'::INTERVAL, '6666666666666', '{}'), + (-666, 10312, 1001, now() - '1 day'::INTERVAL, '66666666666666', '{}'), + (-666, 10313, 1001, now() - '1 day'::INTERVAL, '666666666666666', '{}'), + (-666, 10314, 1001, now() - '1 day'::INTERVAL, '6666666666666666', '{}'), + (-666, 10315, 1001, now() - '1 day'::INTERVAL, '66666666666666666', '{}'), + (-666, 10316, 1001, now() - '1 day'::INTERVAL, '666666666666666666', '{}'), + (-666, 10317, 1001, now() - '1 day'::INTERVAL, '6666666666666666666', '{}'), + (-666, 10318, 1001, now() - '1 day'::INTERVAL, '66666666666666666666', '{}'), + (-666, 10319, 1001, now() - '1 day'::INTERVAL, '666666666666666666666', '{}'), + (-666, 10320, 1001, now() - '1 day'::INTERVAL, '6666666666666666666666', '{}'), + (-666, 10321, 1001, now() - '1 day'::INTERVAL, '66666666666666666666666', '{}'), + (-666, 10322, 1001, now() - '1 day'::INTERVAL, '666666666666666666666666', '{}'), + (-666, 10323, 1001, now() - '1 day'::INTERVAL, '6666666666666666666666666', '{}'), + (-666, 10324, 1001, now() - '1 day'::INTERVAL, '66666666666666666666666666', '{}'), + (-666, 10325, 1001, now() - '1 day'::INTERVAL, '666666666666666666666666666', '{}'), + (-666, 10326, 1001, now() - '1 day'::INTERVAL, '6666666666666666666666666666', '{}'), + (-666, 10327, 1001, now() - '1 day'::INTERVAL, '66666666666666666666666666666', '{}'), + (-666, 10328, 1001, now() - '1 day'::INTERVAL, '666666666666666666666666666666', '{}'), + (-666, 10329, 1001, now() - '1 day'::INTERVAL, '6666666666666666666666666666666', '{}'), + (-666, 10330, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10331, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10332, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10333, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10334, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10335, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10336, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10337, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10338, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10339, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10340, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10341, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10342, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10343, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10344, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10345, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10346, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10347, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10348, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10349, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10350, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10351, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10352, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10353, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10354, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10355, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10356, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10357, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10358, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10359, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10360, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10361, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10362, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10363, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10364, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10365, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10366, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10367, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10368, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10369, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10370, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10371, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10372, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10373, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10374, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10375, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10376, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10377, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10378, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10379, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10380, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10381, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10382, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10383, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10384, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10385, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10386, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10387, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10388, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10389, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10390, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10391, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10392, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10393, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10394, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10395, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10396, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10397, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10398, 1001, now() - '1 day'::INTERVAL, '6', '{}'), + (-666, 10399, 1001, now() - '1 day'::INTERVAL, '6', '{}'), -- to enforce false-negative appearance of 7 - (-666, 10400, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10401, 1001, '2021-01-01 00:00:01', '77', '{}'), - (-666, 10402, 1001, '2021-01-01 00:00:01', '7777', '{}'), - (-666, 10403, 1001, '2021-01-01 00:00:01', '77777', '{}'), - (-666, 10404, 1001, '2021-01-01 00:00:01', '777777', '{}'), - (-666, 10405, 1001, '2021-01-01 00:00:01', '7777777', '{}'), - (-666, 10406, 1001, '2021-01-01 00:00:01', '77777777', '{}'), - (-666, 10407, 1001, '2021-01-01 00:00:01', '777777777', '{}'), - (-666, 10408, 1001, '2021-01-01 00:00:01', '7777777777', '{}'), - (-666, 10409, 1001, '2021-01-01 00:00:01', '77777777777', '{}'), - (-666, 10410, 1001, '2021-01-01 00:00:01', '777777777777', '{}'), - (-666, 10411, 1001, '2021-01-01 00:00:01', '7777777777777', '{}'), - (-666, 10412, 1001, '2021-01-01 00:00:01', '777777777777777', '{}'), - (-666, 10413, 1001, '2021-01-01 00:00:01', '7777777777777777', '{}'), - (-666, 10414, 1001, '2021-01-01 00:00:01', '77777777777777777', '{}'), - (-666, 10415, 1001, '2021-01-01 00:00:01', '777777777777777777', '{}'), - (-666, 10416, 1001, '2021-01-01 00:00:01', '77777777777777777777', '{}'), - (-666, 10417, 1001, '2021-01-01 00:00:01', '777777777777777777777', '{}'), - (-666, 10418, 1001, '2021-01-01 00:00:01', '7777777777777777777777', '{}'), - (-666, 10419, 1001, '2021-01-01 00:00:01', '77777777777777777777777', '{}'), - (-666, 10420, 1001, '2021-01-01 00:00:01', '777777777777777777777777', '{}'), - (-666, 10421, 1001, '2021-01-01 00:00:01', '7777777777777777777777777', '{}'), - (-666, 10422, 1001, '2021-01-01 00:00:01', '77777777777777777777777777', '{}'), - (-666, 10423, 1001, '2021-01-01 00:00:01', '777777777777777777777777777', '{}'), - (-666, 10424, 1001, '2021-01-01 00:00:01', '7777777777777777777777777777', '{}'), - (-666, 10425, 1001, '2021-01-01 00:00:01', '77777777777777777777777777777', '{}'), - (-666, 10426, 1001, '2021-01-01 00:00:01', '777777777777777777777777777777', '{}'), - (-666, 10427, 1001, '2021-01-01 00:00:01', '7777777777777777777777777777777', '{}'), - (-666, 10428, 1001, '2021-01-01 00:00:01', '77777777777777777777777777777777', '{}'), - (-666, 10429, 1001, '2021-01-01 00:00:01', '777777777777777777777777777777777', '{}'), - (-666, 10430, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10431, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10432, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10433, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10434, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10435, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10436, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10437, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10438, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10439, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10440, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10441, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10442, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10443, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10444, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10445, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10446, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10447, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10448, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10449, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10450, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10451, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10452, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10453, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10454, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10455, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10456, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10457, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10458, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10459, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10460, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10461, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10462, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10463, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10464, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10465, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10466, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10467, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10468, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10469, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10470, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10471, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10472, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10473, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10474, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10475, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10476, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10477, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10478, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10479, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10480, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10481, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10482, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10483, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10484, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10485, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10486, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10487, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10488, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10489, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10490, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10491, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10492, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10493, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10494, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10495, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10496, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10497, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10498, 1001, '2021-01-01 00:00:01', '7', '{}'), - (-666, 10499, 1001, '2021-01-01 00:00:01', '7', '{}'); + (-666, 10400, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10401, 1001, now() - '1 day'::INTERVAL, '77', '{}'), + (-666, 10402, 1001, now() - '1 day'::INTERVAL, '7777', '{}'), + (-666, 10403, 1001, now() - '1 day'::INTERVAL, '77777', '{}'), + (-666, 10404, 1001, now() - '1 day'::INTERVAL, '777777', '{}'), + (-666, 10405, 1001, now() - '1 day'::INTERVAL, '7777777', '{}'), + (-666, 10406, 1001, now() - '1 day'::INTERVAL, '77777777', '{}'), + (-666, 10407, 1001, now() - '1 day'::INTERVAL, '777777777', '{}'), + (-666, 10408, 1001, now() - '1 day'::INTERVAL, '7777777777', '{}'), + (-666, 10409, 1001, now() - '1 day'::INTERVAL, '77777777777', '{}'), + (-666, 10410, 1001, now() - '1 day'::INTERVAL, '777777777777', '{}'), + (-666, 10411, 1001, now() - '1 day'::INTERVAL, '7777777777777', '{}'), + (-666, 10412, 1001, now() - '1 day'::INTERVAL, '777777777777777', '{}'), + (-666, 10413, 1001, now() - '1 day'::INTERVAL, '7777777777777777', '{}'), + (-666, 10414, 1001, now() - '1 day'::INTERVAL, '77777777777777777', '{}'), + (-666, 10415, 1001, now() - '1 day'::INTERVAL, '777777777777777777', '{}'), + (-666, 10416, 1001, now() - '1 day'::INTERVAL, '77777777777777777777', '{}'), + (-666, 10417, 1001, now() - '1 day'::INTERVAL, '777777777777777777777', '{}'), + (-666, 10418, 1001, now() - '1 day'::INTERVAL, '7777777777777777777777', '{}'), + (-666, 10419, 1001, now() - '1 day'::INTERVAL, '77777777777777777777777', '{}'), + (-666, 10420, 1001, now() - '1 day'::INTERVAL, '777777777777777777777777', '{}'), + (-666, 10421, 1001, now() - '1 day'::INTERVAL, '7777777777777777777777777', '{}'), + (-666, 10422, 1001, now() - '1 day'::INTERVAL, '77777777777777777777777777', '{}'), + (-666, 10423, 1001, now() - '1 day'::INTERVAL, '777777777777777777777777777', '{}'), + (-666, 10424, 1001, now() - '1 day'::INTERVAL, '7777777777777777777777777777', '{}'), + (-666, 10425, 1001, now() - '1 day'::INTERVAL, '77777777777777777777777777777', '{}'), + (-666, 10426, 1001, now() - '1 day'::INTERVAL, '777777777777777777777777777777', '{}'), + (-666, 10427, 1001, now() - '1 day'::INTERVAL, '7777777777777777777777777777777', '{}'), + (-666, 10428, 1001, now() - '1 day'::INTERVAL, '77777777777777777777777777777777', '{}'), + (-666, 10429, 1001, now() - '1 day'::INTERVAL, '777777777777777777777777777777777', '{}'), + (-666, 10430, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10431, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10432, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10433, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10434, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10435, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10436, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10437, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10438, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10439, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10440, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10441, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10442, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10443, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10444, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10445, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10446, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10447, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10448, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10449, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10450, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10451, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10452, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10453, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10454, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10455, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10456, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10457, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10458, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10459, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10460, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10461, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10462, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10463, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10464, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10465, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10466, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10467, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10468, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10469, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10470, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10471, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10472, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10473, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10474, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10475, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10476, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10477, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10478, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10479, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10480, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10481, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10482, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10483, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10484, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10485, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10486, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10487, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10488, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10489, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10490, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10491, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10492, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10493, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10494, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10495, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10496, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10497, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10498, 1001, now() - '1 day'::INTERVAL, '7', '{}'), + (-666, 10499, 1001, now() - '1 day'::INTERVAL, '7', '{}'); INSERT INTO public.banned(id, message_id, message_text, banned_user_id, banned_at, banned_in_chat_id, banned_in_chat_username, banned_by) -VALUES (100001, 10001, 'a', 1001, '2021-01-01 00:00:00', -666, 'pro.hell', 34), - (100002, 10008, 'a', 1004, '2021-01-01 00:00:07', -666, 'pro.hell', 69), - (100003, 10009, '1', 1005, '2021-01-01 00:00:08', -666, 'pro.hell', 34), - (100004, 10010, '2', 1006, '2021-01-01 00:00:09', -42, 'dotnetru', 69); +VALUES (100001, 10001, 'a', 1001, now() - '1 day'::INTERVAL, -666, 'pro.hell', 34), + (100002, 10008, 'a', 1004, now() - '1 day'::INTERVAL, -666, 'pro.hell', 69), + (100003, 10009, '1', 1005, now() - '1 day'::INTERVAL, -666, 'pro.hell', 34), + (100004, 10010, '2', 1006, now() - '1 day'::INTERVAL, -42, 'dotnetru', 69); INSERT INTO public.false_positive_users(user_id) VALUES (1001); diff --git a/src/VahterBanBot/Bot.fs b/src/VahterBanBot/Bot.fs index b93547a..fbd5626 100644 --- a/src/VahterBanBot/Bot.fs +++ b/src/VahterBanBot/Bot.fs @@ -478,7 +478,9 @@ let justMessage %mlActivity.SetTag("skipPrediction", shouldBeSkipped) if not shouldBeSkipped then - match ml.Predict message.Text with + let! usrMsgCount = DB.countUniqueUserMsg message.From.Id + + match ml.Predict(message.Text, usrMsgCount) with | Some prediction -> %mlActivity.SetTag("spamScoreMl", prediction.Score) diff --git a/src/VahterBanBot/DB.fs b/src/VahterBanBot/DB.fs index c380314..4530e91 100644 --- a/src/VahterBanBot/DB.fs +++ b/src/VahterBanBot/DB.fs @@ -171,20 +171,24 @@ let getUserById (userId: int64): Task = return users |> Seq.tryHead } -type SpamOrHam = - { [] - text: string - [] - spam: bool } +type SpamOrHamDb = + { text: string + spam: bool + less_than_n_messages: bool + created_at: DateTime } -let mlData(criticalDate: DateTime) : Task = +let mlData (criticalMsgCount: int) (criticalDate: DateTime) : Task = task { use conn = new NpgsqlConnection(connString) //language=postgresql let sql = """ -WITH really_banned AS (SELECT * +WITH less_than_n_messages AS (SELECT u.id, COUNT(DISTINCT m.text) < @criticalMsgCount AS less_than_n_messages + FROM "user" u + LEFT JOIN message m ON u.id = m.user_id + GROUP BY u.id), + really_banned AS (SELECT * FROM banned b -- known false positive spam messages WHERE NOT EXISTS(SELECT 1 FROM false_positive_users fpu WHERE fpu.user_id = b.banned_user_id) @@ -192,34 +196,45 @@ WITH really_banned AS (SELECT * FROM false_positive_messages fpm WHERE fpm.text = b.message_text) AND b.message_text IS NOT NULL - AND b.banned_at <= @criticalDate), - spam_or_ham AS (SELECT DISTINCT COALESCE(m.text, re_id.message_text) AS text, - CASE - -- known false negative spam messages - WHEN (EXISTS(SELECT 1 - FROM false_negative_messages fnm - WHERE fnm.chat_id = m.chat_id - AND fnm.message_id = m.message_id) - -- known banned spam messages by bot, and not marked as false positive - OR EXISTS(SELECT 1 - FROM banned_by_bot bbb - WHERE bbb.banned_in_chat_id = m.chat_id - AND bbb.message_id = m.message_id)) - THEN TRUE - WHEN re_id.banned_user_id IS NULL AND re_text.banned_user_id IS NULL - THEN FALSE - ELSE TRUE - END AS spam - FROM (SELECT * FROM message WHERE text IS NOT NULL AND created_at <= @criticalDate) m - FULL OUTER JOIN really_banned re_id - ON m.message_id = re_id.message_id AND m.chat_id = re_id.banned_in_chat_id - LEFT JOIN really_banned re_text ON m.text = re_text.message_text) + AND b.banned_at >= @criticalDate), + spam_or_ham AS (SELECT x.text, + x.spam, + x.less_than_n_messages, + MAX(x.created_at) AS created_at + FROM (SELECT DISTINCT COALESCE(m.text, re_id.message_text) AS text, + CASE + -- known false negative spam messages + WHEN (EXISTS(SELECT 1 + FROM false_negative_messages fnm + WHERE fnm.chat_id = m.chat_id + AND fnm.message_id = m.message_id) + -- known banned spam messages by bot, and not marked as false positive + OR EXISTS(SELECT 1 + FROM banned_by_bot bbb + WHERE bbb.banned_in_chat_id = m.chat_id + AND bbb.message_id = m.message_id)) + THEN TRUE + WHEN re_id.banned_user_id IS NULL AND re_text.banned_user_id IS NULL + THEN FALSE + ELSE TRUE + END AS spam, + COALESCE(l.less_than_n_messages, TRUE) AS less_than_n_messages, + COALESCE(re_id.banned_at, re_text.banned_at, m.created_at) AS created_at + FROM (SELECT * + FROM message + WHERE text IS NOT NULL + AND created_at >= @criticalDate) m + FULL OUTER JOIN really_banned re_id + ON m.message_id = re_id.message_id AND m.chat_id = re_id.banned_in_chat_id + LEFT JOIN really_banned re_text ON m.text = re_text.message_text + LEFT JOIN less_than_n_messages l ON m.user_id = l.id) x + GROUP BY text, spam, less_than_n_messages) SELECT * FROM spam_or_ham -ORDER BY RANDOM(); +ORDER BY created_at; """ - let! data = conn.QueryAsync(sql, {| criticalDate = criticalDate |}) + let! data = conn.QueryAsync(sql, {| criticalDate = criticalDate; criticalMsgCount = criticalMsgCount |}) return Array.ofSeq data } @@ -289,3 +304,14 @@ let deleteCallback (id: Guid): Task = let! _ = conn.QueryAsync(sql, {| id = id |}) return () } + +let countUniqueUserMsg (userId: int64): Task = + task { + use conn = new NpgsqlConnection(connString) + + //language=postgresql + let sql = "SELECT COUNT(DISTINCT text) FROM message WHERE user_id = @userId" + + let! result = conn.QuerySingleAsync(sql, {| userId = userId |}) + return result + } diff --git a/src/VahterBanBot/ML.fs b/src/VahterBanBot/ML.fs index 38efdb8..86fc7f5 100644 --- a/src/VahterBanBot/ML.fs +++ b/src/VahterBanBot/ML.fs @@ -3,6 +3,7 @@ module VahterBanBot.ML open System open System.Diagnostics open System.Text +open System.Threading open System.Threading.Tasks open Microsoft.Extensions.Hosting open Microsoft.Extensions.Logging @@ -15,6 +16,13 @@ open VahterBanBot.DB open VahterBanBot.Types open VahterBanBot.Utils +[] +type SpamOrHam = + { text: string + spam: bool + lessThanNMessagesF: single + createdAt: DateTime } + [] type Prediction = { Score: single @@ -44,45 +52,66 @@ type MachineLearning( sb.ToString() let mutable predictionEngine: PredictionEngine option = None + let mutable timer: Timer = null - let trainModel() = task { - // switch to thread pool - do! Task.Yield() - - let sw = Stopwatch.StartNew() + let trainModel _ = task { + try + // switch to thread pool + do! Task.Yield() + logger.LogInformation "Training model..." + + let sw = Stopwatch.StartNew() - let mlContext = MLContext(botConf.MlSeed) + let mlContext = MLContext(botConf.MlSeed) + + let trainDate = DateTime.UtcNow - botConf.MlTrainInterval + let! rawData = DB.mlData botConf.MlTrainCriticalMsgCount trainDate + + let data = + rawData + |> Array.map (fun x -> + { text = x.text + spam = x.spam + createdAt = x.created_at + lessThanNMessagesF = if x.less_than_n_messages then 1.0f else 0.0f } + ) + + let dataView = mlContext.Data.LoadFromEnumerable data + let trainTestSplit = mlContext.Data.TrainTestSplit(dataView, testFraction = botConf.MlTrainingSetFraction) + let trainingData = trainTestSplit.TrainSet + let testData = trainTestSplit.TestSet + + let dataProcessPipeline = + mlContext.Transforms.Text + .FeaturizeText(outputColumnName = "TextFeaturized", inputColumnName = "text") + .Append(mlContext.Transforms.Concatenate(outputColumnName = "Features", inputColumnNames = [|"TextFeaturized"; "lessThanNMessagesF";|])) + .Append(mlContext.BinaryClassification.Trainers.LbfgsLogisticRegression(labelColumnName = "spam", featureColumnName = "Features")) - let! data = DB.mlData botConf.MlTrainBeforeDate - - let dataView = mlContext.Data.LoadFromEnumerable data - let trainTestSplit = mlContext.Data.TrainTestSplit(dataView, testFraction = botConf.MlTrainingSetFraction) - let trainingData = trainTestSplit.TrainSet - let testData = trainTestSplit.TestSet - - let dataProcessPipeline = mlContext.Transforms.Text.FeaturizeText(outputColumnName = "Features", inputColumnName = "text") - let trainer = mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName = "spam", featureColumnName = "Features") - let trainingPipeline = dataProcessPipeline.Append(trainer) - - let trainedModel = trainingPipeline.Fit(trainingData) - predictionEngine <- Some(mlContext.Model.CreatePredictionEngine(trainedModel)) - - let predictions = trainedModel.Transform(testData) - let metrics = mlContext.BinaryClassification.Evaluate(data = predictions, labelColumnName = "spam", scoreColumnName = "Score") - - sw.Stop() - - let metricsStr = metricsToString metrics sw.Elapsed - logger.LogInformation metricsStr - do! telegramClient.SendTextMessageAsync(ChatId(botConf.LogsChannelId), metricsStr, parseMode = ParseMode.Markdown) - |> taskIgnore + let trainedModel = dataProcessPipeline.Fit(trainingData) + predictionEngine <- Some(mlContext.Model.CreatePredictionEngine(trainedModel)) + + let predictions = trainedModel.Transform(testData) + let metrics = mlContext.BinaryClassification.Evaluate(data = predictions, labelColumnName = "spam", scoreColumnName = "Score") + + sw.Stop() + + let metricsStr = metricsToString metrics sw.Elapsed + logger.LogInformation metricsStr + do! telegramClient.SendTextMessageAsync(ChatId(botConf.LogsChannelId), metricsStr, parseMode = ParseMode.Markdown) + |> taskIgnore + with ex -> + logger.LogError(ex, "Error training model") } - member _.Predict(text: string) = + member _.Predict(text: string, userMsgCount: int) = try match predictionEngine with | Some predictionEngine -> - predictionEngine.Predict({ text = text; spam = false }) + predictionEngine.Predict + { text = text + spam = false + lessThanNMessagesF = if userMsgCount < botConf.MlTrainCriticalMsgCount then 1.0f else 0.0f + createdAt = DateTime.UtcNow } |> Some | None -> logger.LogInformation "Model not trained yet" @@ -94,11 +123,15 @@ type MachineLearning( interface IHostedService with member this.StartAsync _ = task { if botConf.MlEnabled then - try - logger.LogInformation "Training model..." + if botConf.MlRetrainInterval.IsSome then + // recurring + timer <- new Timer(TimerCallback(trainModel >> ignore), null, TimeSpan.Zero, botConf.MlRetrainInterval.Value) + else + // once do! trainModel() - with ex -> - logger.LogError(ex, "Error training model") } - member this.StopAsync _ = Task.CompletedTask + member this.StopAsync _ = + match timer with + | null -> Task.CompletedTask + | timer -> timer.DisposeAsync().AsTask() diff --git a/src/VahterBanBot/Program.fs b/src/VahterBanBot/Program.fs index 4950a6e..2539a99 100644 --- a/src/VahterBanBot/Program.fs +++ b/src/VahterBanBot/Program.fs @@ -52,9 +52,11 @@ let botConf = UpdateChatAdminsInterval = getEnvOrWith "UPDATE_CHAT_ADMINS_INTERVAL_SEC" None (int >> TimeSpan.FromSeconds >> Some) UpdateChatAdmins = getEnvOr "UPDATE_CHAT_ADMINS" "false" |> bool.Parse MlEnabled = getEnvOr "ML_ENABLED" "false" |> bool.Parse + MlRetrainInterval = getEnvOrWith "ML_RETRAIN_INTERVAL_SEC" None (int >> TimeSpan.FromSeconds >> Some) MlSeed = getEnvOrWith "ML_SEED" (Nullable()) (int >> Nullable) MlSpamDeletionEnabled = getEnvOr "ML_SPAM_DELETION_ENABLED" "false" |> bool.Parse - MlTrainBeforeDate = getEnvOrWith "ML_TRAIN_BEFORE_DATE" DateTime.UtcNow (DateTimeOffset.Parse >> _.UtcDateTime) + MlTrainInterval = getEnvOr "ML_TRAIN_INTERVAL_DAYS" "30" |> int |> TimeSpan.FromDays + MlTrainCriticalMsgCount = getEnvOr "ML_TRAIN_CRITICAL_MSG_COUNT" "5" |> int MlTrainingSetFraction = getEnvOr "ML_TRAINING_SET_FRACTION" "0.2" |> float MlSpamThreshold = getEnvOr "ML_SPAM_THRESHOLD" "0.5" |> single MlWarningThreshold = getEnvOr "ML_WARNING_THRESHOLD" "0.0" |> single diff --git a/src/VahterBanBot/Types.fs b/src/VahterBanBot/Types.fs index c1dd352..7776a53 100644 --- a/src/VahterBanBot/Types.fs +++ b/src/VahterBanBot/Types.fs @@ -26,9 +26,11 @@ type BotConfiguration = UpdateChatAdminsInterval: TimeSpan option UpdateChatAdmins: bool MlEnabled: bool + MlRetrainInterval: TimeSpan option MlSeed: Nullable MlSpamDeletionEnabled: bool - MlTrainBeforeDate: DateTime + MlTrainInterval: TimeSpan + MlTrainCriticalMsgCount: int MlTrainingSetFraction: float MlSpamThreshold: single MlWarningThreshold: single diff --git a/src/VahterBanBot/UpdateChatAdmins.fs b/src/VahterBanBot/UpdateChatAdmins.fs index e65ec39..9a29b80 100644 --- a/src/VahterBanBot/UpdateChatAdmins.fs +++ b/src/VahterBanBot/UpdateChatAdmins.fs @@ -31,8 +31,8 @@ type UpdateChatAdmins( do! Task.Delay 100 for admin in admins do - %result.Add admin.User.Id - %sb.AppendJoin(",", $"{prependUsername admin.User.Username} ({admin.User.Id})") + if result.Add admin.User.Id then + %sb.AppendJoin(",", $"{prependUsername admin.User.Username} ({admin.User.Id})") localAdmins <- result logger.LogInformation (sb.ToString()) }