bas 0.4.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +68 -147
  4. data/lib/bas/bot/base.rb +74 -0
  5. data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
  6. data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
  7. data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
  8. data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
  9. data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
  10. data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
  11. data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
  12. data/lib/bas/bot/fetch_ptos_from_notion.rb +173 -0
  13. data/lib/bas/bot/format_birthdays.rb +97 -0
  14. data/lib/bas/bot/format_emails.rb +124 -0
  15. data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
  16. data/lib/bas/bot/garbage_collector.rb +85 -0
  17. data/lib/bas/bot/humanize_pto.rb +117 -0
  18. data/lib/bas/bot/notify_discord.rb +96 -0
  19. data/lib/bas/read/base.rb +10 -23
  20. data/lib/bas/read/default.rb +16 -0
  21. data/lib/bas/read/postgres.rb +44 -0
  22. data/lib/bas/read/types/response.rb +18 -0
  23. data/lib/bas/utils/discord/integration.rb +43 -0
  24. data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
  25. data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
  26. data/lib/bas/utils/imap/request.rb +76 -0
  27. data/lib/bas/utils/notion/request.rb +45 -0
  28. data/lib/bas/utils/openai/run_assistant.rb +99 -0
  29. data/lib/bas/utils/postgres/request.rb +50 -0
  30. data/lib/bas/version.rb +1 -1
  31. data/lib/bas/write/base.rb +12 -17
  32. data/lib/bas/write/postgres.rb +45 -0
  33. data/lib/bas/write/postgres_update.rb +49 -0
  34. data/lib/bas.rb +1 -3
  35. metadata +31 -68
  36. data/lib/bas/domain/birthday.rb +0 -25
  37. data/lib/bas/domain/email.rb +0 -34
  38. data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
  39. data/lib/bas/domain/issue.rb +0 -22
  40. data/lib/bas/domain/notification.rb +0 -23
  41. data/lib/bas/domain/pto.rb +0 -69
  42. data/lib/bas/domain/work_items_limit.rb +0 -25
  43. data/lib/bas/formatter/base.rb +0 -53
  44. data/lib/bas/formatter/birthday.rb +0 -38
  45. data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
  46. data/lib/bas/formatter/notification.rb +0 -34
  47. data/lib/bas/formatter/pto.rb +0 -89
  48. data/lib/bas/formatter/support_emails.rb +0 -73
  49. data/lib/bas/formatter/types/response.rb +0 -16
  50. data/lib/bas/formatter/work_items_limit.rb +0 -68
  51. data/lib/bas/process/base.rb +0 -39
  52. data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
  53. data/lib/bas/process/discord/implementation.rb +0 -71
  54. data/lib/bas/process/discord/types/response.rb +0 -22
  55. data/lib/bas/process/openai/base.rb +0 -72
  56. data/lib/bas/process/openai/helper.rb +0 -19
  57. data/lib/bas/process/openai/types/response.rb +0 -27
  58. data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
  59. data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
  60. data/lib/bas/process/slack/implementation.rb +0 -70
  61. data/lib/bas/process/slack/types/response.rb +0 -21
  62. data/lib/bas/process/types/response.rb +0 -16
  63. data/lib/bas/read/github/base.rb +0 -57
  64. data/lib/bas/read/github/types/response.rb +0 -27
  65. data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
  66. data/lib/bas/read/imap/base.rb +0 -70
  67. data/lib/bas/read/imap/types/response.rb +0 -27
  68. data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
  69. data/lib/bas/read/notion/base.rb +0 -52
  70. data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
  71. data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
  72. data/lib/bas/read/notion/helper.rb +0 -21
  73. data/lib/bas/read/notion/types/response.rb +0 -26
  74. data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
  75. data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
  76. data/lib/bas/read/notion/use_case/notification.rb +0 -28
  77. data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
  78. data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
  79. data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
  80. data/lib/bas/read/postgres/base.rb +0 -46
  81. data/lib/bas/read/postgres/helper.rb +0 -16
  82. data/lib/bas/read/postgres/types/response.rb +0 -42
  83. data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
  84. data/lib/bas/serialize/base.rb +0 -30
  85. data/lib/bas/serialize/github/issues.rb +0 -57
  86. data/lib/bas/serialize/imap/support_emails.rb +0 -56
  87. data/lib/bas/serialize/notion/birthday_today.rb +0 -68
  88. data/lib/bas/serialize/notion/notification.rb +0 -56
  89. data/lib/bas/serialize/notion/pto_today.rb +0 -75
  90. data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
  91. data/lib/bas/serialize/postgres/pto_today.rb +0 -47
  92. data/lib/bas/use_cases/types/config.rb +0 -20
  93. data/lib/bas/use_cases/use_case.rb +0 -42
  94. data/lib/bas/use_cases/use_cases.rb +0 -465
  95. data/lib/bas/write/logs/base.rb +0 -33
  96. data/lib/bas/write/logs/use_case/console_log.rb +0 -22
  97. data/lib/bas/write/notion/base.rb +0 -36
  98. data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
  99. data/lib/bas/write/notion/use_case/notification.rb +0 -38
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ require_relative "./base"
6
+ require_relative "../read/default"
7
+ require_relative "../utils/notion/request"
8
+ require_relative "../write/postgres"
9
+
10
+ module Bot
11
+ ##
12
+ # The Bot::FetchPtosFromNotion class serves as a bot implementation to read PTO's from a
13
+ # notion database and write them on a PostgresDB table with a specific format.
14
+ #
15
+ # <br>
16
+ # <b>Example</b>
17
+ #
18
+ # options = {
19
+ # process_options: {
20
+ # database_id: "notion database id",
21
+ # secret: "notion secret"
22
+ # },
23
+ # write_options: {
24
+ # connection: {
25
+ # host: "host",
26
+ # port: 5432,
27
+ # dbname: "bas",
28
+ # user: "postgres",
29
+ # password: "postgres"
30
+ # },
31
+ # db_table: "pto",
32
+ # tag: "FetchPtosFromNotion"
33
+ # }
34
+ # }
35
+ #
36
+ # bot = Bot::FetchPtosFromNotion.new(options)
37
+ # bot.execute
38
+ #
39
+ class FetchPtosFromNotion < Bot::Base
40
+ # Read function to execute the default Read component
41
+ #
42
+ def read
43
+ reader = Read::Default.new
44
+
45
+ reader.execute
46
+ end
47
+
48
+ # Process function to execute the Notion utility to fetch PTO's from the notion database
49
+ #
50
+ def process
51
+ response = Utils::Notion::Request.execute(params)
52
+
53
+ if response.code == 200
54
+ ptos_list = normalize_response(response.parsed_response["results"])
55
+
56
+ { success: { ptos: ptos_list } }
57
+ else
58
+ { error: { message: response.parsed_response, status_code: response.code } }
59
+ end
60
+ end
61
+
62
+ # Write function to execute the PostgresDB write component
63
+ #
64
+ def write
65
+ write = Write::Postgres.new(write_options, process_response)
66
+
67
+ write.execute
68
+ end
69
+
70
+ private
71
+
72
+ def params
73
+ {
74
+ endpoint: "databases/#{process_options[:database_id]}/query",
75
+ secret: process_options[:secret],
76
+ method: "post",
77
+ body:
78
+ }
79
+ end
80
+
81
+ def body
82
+ { filter: { "or": conditions } }
83
+ end
84
+
85
+ def conditions
86
+ [
87
+ today_condition,
88
+ { property: "StartDateTime", date: { this_week: {} } },
89
+ { property: "EndDateTime", date: { this_week: {} } },
90
+ { property: "StartDateTime", date: { next_week: {} } },
91
+ { property: "EndDateTime", date: { next_week: {} } }
92
+ ]
93
+ end
94
+
95
+ def today_condition
96
+ today = Time.now.utc.strftime("%F").to_s
97
+ {
98
+ "and": [
99
+ { property: "StartDateTime", date: { on_or_before: today } },
100
+ { property: "EndDateTime", date: { on_or_after: today } }
101
+ ]
102
+ }
103
+ end
104
+
105
+ def normalize_response(results)
106
+ return [] if results.nil?
107
+
108
+ results.map do |pto|
109
+ pto_fields = pto["properties"]
110
+
111
+ name = extract_description_field_value(pto_fields["Description"])
112
+ start_date = extract_date_field_value(pto_fields["StartDateTime"])
113
+ end_date = extract_date_field_value(pto_fields["EndDateTime"])
114
+
115
+ description(name, start_date, end_date)
116
+ end
117
+ end
118
+
119
+ def description(name, start_date, end_date)
120
+ start = start_description(start_date)
121
+ finish = end_description(end_date)
122
+
123
+ "#{name} will not be working between #{start} and #{finish}. And returns the #{returns(finish)}"
124
+ end
125
+
126
+ def start_description(date)
127
+ date[:from]
128
+ end
129
+
130
+ def end_description(date)
131
+ return date[:from] if date[:to].nil?
132
+
133
+ date[:to]
134
+ end
135
+
136
+ def returns(date)
137
+ date.include?("T") ? "#{date} in the afternoon" : next_work_day(date)
138
+ end
139
+
140
+ def next_work_day(date)
141
+ datetime = DateTime.parse(date)
142
+
143
+ return_day = case datetime.wday
144
+ when 5 then datetime + 3
145
+ when 6 then datetime + 2
146
+ else datetime + 1
147
+ end
148
+
149
+ return_day.strftime("%A %B %d of %Y").to_s
150
+ end
151
+
152
+ def extract_description_field_value(data)
153
+ names = data["title"].map { |name| name["plain_text"] }
154
+
155
+ names.join(" ")
156
+ end
157
+
158
+ def extract_date_field_value(date)
159
+ {
160
+ from: extract_start_date(date),
161
+ to: extract_end_date(date)
162
+ }
163
+ end
164
+
165
+ def extract_start_date(data)
166
+ data["date"]["start"]
167
+ end
168
+
169
+ def extract_end_date(data)
170
+ data["date"]["end"]
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::FormatBirthdays class serves as a bot implementation to read birthdays from a
10
+ # PostgresDB database, format them with a specific template, and write them on a PostgresDB
11
+ # table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # read_options: {
18
+ # connection: {
19
+ # host: "localhost",
20
+ # port: 5432,
21
+ # dbname: "bas",
22
+ # user: "postgres",
23
+ # password: "postgres"
24
+ # },
25
+ # db_table: "use_cases",
26
+ # tag: "FetchBirthdaysFromNotion"
27
+ # },
28
+ # process_options: {
29
+ # template: "birthday template message"
30
+ # },
31
+ # write_options: {
32
+ # connection: {
33
+ # host: "localhost",
34
+ # port: 5432,
35
+ # dbname: "bas",
36
+ # user: "postgres",
37
+ # password: "postgres"
38
+ # },
39
+ # db_table: "use_cases",
40
+ # tag: "FormatBirthdays"
41
+ # }
42
+ # }
43
+ #
44
+ # bot = Bot::FormatBirthdays.new(options)
45
+ # bot.execute
46
+ #
47
+ class FormatBirthdays < Bot::Base
48
+ BIRTHDAY_ATTRIBUTES = %w[name birthday_date].freeze
49
+
50
+ # read function to execute the PostgresDB Read component
51
+ #
52
+ def read
53
+ reader = Read::Postgres.new(read_options.merge(conditions))
54
+
55
+ reader.execute
56
+ end
57
+
58
+ # Process function to format the notification using a template
59
+ #
60
+ def process
61
+ return { success: { notification: "" } } if unprocessable_response
62
+
63
+ birthdays_list = read_response.data["birthdays"]
64
+
65
+ notification = birthdays_list.reduce("") do |payload, birthday|
66
+ "#{payload} #{build_template(BIRTHDAY_ATTRIBUTES, birthday)} \n"
67
+ end
68
+
69
+ { success: { notification: } }
70
+ end
71
+
72
+ # Write function to execute the PostgresDB write component
73
+ #
74
+ def write
75
+ write = Write::Postgres.new(write_options, process_response)
76
+
77
+ write.execute
78
+ end
79
+
80
+ private
81
+
82
+ def conditions
83
+ {
84
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
85
+ params: [false, read_options[:tag], "unprocessed"]
86
+ }
87
+ end
88
+
89
+ def build_template(attributes, instance)
90
+ template = process_options[:template]
91
+
92
+ attributes.reduce(template) do |formated_template, attribute|
93
+ formated_template.gsub("<#{attribute}>", instance[attribute].to_s)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::FormatEmails class serves as a bot implementation to read emails from a
10
+ # PostgresDB database, format them with a specific template, and write them on a PostgresDB
11
+ # table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # read_options: {
18
+ # connection: {
19
+ # host: "localhost",
20
+ # port: 5432,
21
+ # dbname: "bas",
22
+ # user: "postgres",
23
+ # password: "postgres"
24
+ # },
25
+ # db_table: "use_cases",
26
+ # tag: "FetchEmailsFromImap"
27
+ # },
28
+ # process_options: {
29
+ # template: "emails template message"
30
+ # },
31
+ # write_options: {
32
+ # connection: {
33
+ # host: "localhost",
34
+ # port: 5432,
35
+ # dbname: "bas",
36
+ # user: "postgres",
37
+ # password: "postgres"
38
+ # },
39
+ # db_table: "use_cases",
40
+ # tag: "FormatEmails"
41
+ # }
42
+ # }
43
+ #
44
+ # bot = Bot::FormatEmails.new(options)
45
+ # bot.execute
46
+ #
47
+ class FormatEmails < Bot::Base
48
+ EMAIL_ATTRIBUTES = %w[subject sender date].freeze
49
+ DEFAULT_TIME_ZONE = "+00:00"
50
+
51
+ # read function to execute the PostgresDB Read component
52
+ #
53
+ def read
54
+ reader = Read::Postgres.new(read_options.merge(conditions))
55
+
56
+ reader.execute
57
+ end
58
+
59
+ # Process function to format the notification using a template
60
+ #
61
+ def process
62
+ return { success: { notification: "" } } if unprocessable_response
63
+
64
+ emails_list = read_response.data["emails"]
65
+
66
+ notification = process_emails(emails_list).reduce("") do |payload, email|
67
+ "#{payload} #{build_template(EMAIL_ATTRIBUTES, email)} \n"
68
+ end
69
+
70
+ { success: { notification: } }
71
+ end
72
+
73
+ # Write function to execute the PostgresDB write component
74
+ #
75
+ def write
76
+ write = Write::Postgres.new(write_options, process_response)
77
+
78
+ write.execute
79
+ end
80
+
81
+ private
82
+
83
+ def conditions
84
+ {
85
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
86
+ params: [false, read_options[:tag], "unprocessed"]
87
+ }
88
+ end
89
+
90
+ def process_emails(emails)
91
+ emails.each do |email|
92
+ date = DateTime.parse(email["date"]).to_time
93
+ email["date"] = at_timezone(date)
94
+ end
95
+ emails.filter! { |email| email["date"] > time_window } unless process_options[:frequency].nil?
96
+
97
+ format_timestamp(emails)
98
+ end
99
+
100
+ def format_timestamp(emails)
101
+ emails.each { |email| email["date"] = email["date"].strftime("%F %r") }
102
+ end
103
+
104
+ def time_window
105
+ date_time = Time.now - (60 * 60 * process_options[:frequency])
106
+
107
+ at_timezone(date_time)
108
+ end
109
+
110
+ def at_timezone(date)
111
+ timezone = process_options[:timezone] || DEFAULT_TIME_ZONE
112
+
113
+ Time.at(date, in: timezone)
114
+ end
115
+
116
+ def build_template(attributes, instance)
117
+ template = process_options[:template]
118
+
119
+ attributes.reduce(template) do |formated_template, attribute|
120
+ formated_template.gsub("<#{attribute}>", instance[attribute].to_s)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::FormatWipLimitExceeded class serves as a bot implementation to read exceeded domain wip
10
+ # counts by limits from a PostgresDB database, format them with a specific template, and write them
11
+ # on a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # read_options: {
18
+ # connection: {
19
+ # host: "localhost",
20
+ # port: 5432,
21
+ # dbname: "bas",
22
+ # user: "postgres",
23
+ # password: "postgres"
24
+ # },
25
+ # db_table: "use_cases",
26
+ # tag: "CompareWipLimitCount"
27
+ # },
28
+ # process_options: {
29
+ # template: "exceeded wip limit template message"
30
+ # },
31
+ # write_options: {
32
+ # connection: {
33
+ # host: "localhost",
34
+ # port: 5432,
35
+ # dbname: "bas",
36
+ # user: "postgres",
37
+ # password: "postgres"
38
+ # },
39
+ # db_table: "use_cases",
40
+ # tag: "FormatWipLimitExceeded"
41
+ # }
42
+ # }
43
+ #
44
+ # bot = Bot::FormatWipLimitExceeded.new(options)
45
+ # bot.execute
46
+ #
47
+ class FormatWipLimitExceeded < Bot::Base
48
+ WIP_LIMIT_ATTRIBUTES = %w[domain exceeded].freeze
49
+
50
+ # read function to execute the PostgresDB Read component
51
+ #
52
+ def read
53
+ reader = Read::Postgres.new(read_options.merge(conditions))
54
+
55
+ reader.execute
56
+ end
57
+
58
+ # Process function to format the notification using a template
59
+ #
60
+ def process
61
+ return { success: { notification: "" } } if unprocessable_response
62
+
63
+ exceedded_limits_list = read_response.data["exceeded_domain_count"]
64
+
65
+ notification = exceedded_limits_list.reduce("") do |payload, exceedded_limit|
66
+ "#{payload} #{build_template(WIP_LIMIT_ATTRIBUTES, exceedded_limit)} \n"
67
+ end
68
+
69
+ { success: { notification: } }
70
+ end
71
+
72
+ # Write function to execute the PostgresDB write component
73
+ #
74
+ def write
75
+ write = Write::Postgres.new(write_options, process_response)
76
+
77
+ write.execute
78
+ end
79
+
80
+ private
81
+
82
+ def conditions
83
+ {
84
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
85
+ params: [false, read_options[:tag], "unprocessed"]
86
+ }
87
+ end
88
+
89
+ def build_template(attributes, instance)
90
+ template = process_options[:template]
91
+
92
+ attributes.reduce(template) do |formated_template, attribute|
93
+ formated_template.gsub("<#{attribute}>", instance[attribute].to_s)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/default"
5
+ require_relative "../write/postgres"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::GarbageCollector class serves as a bot implementation to archive bot records from a
10
+ # PostgresDB database table and write a response on a PostgresDB table with a specific format.
11
+ #
12
+ # <br>
13
+ # <b>Example</b>
14
+ #
15
+ # options = {
16
+ # process_options: {
17
+ # connection: {
18
+ # host: "localhost",
19
+ # port: 5432,
20
+ # dbname: "bas",
21
+ # user: "postgres",
22
+ # password: "postgres"
23
+ # },
24
+ # db_table: "use_cases"
25
+ # },
26
+ # write_options: {
27
+ # connection: {
28
+ # host: "localhost",
29
+ # port: 5432,
30
+ # dbname: "bas",
31
+ # user: "postgres",
32
+ # password: "postgres"
33
+ # },
34
+ # db_table: "use_cases"
35
+ # }
36
+ # }
37
+ #
38
+ # bot = Bot::GarbageCollector.new(options)
39
+ # bot.execute
40
+ #
41
+ class GarbageCollector < Bot::Base
42
+ SUCCESS_STATUS = "PGRES_COMMAND_OK"
43
+
44
+ # Read function to execute the default Read component
45
+ #
46
+ def read
47
+ reader = Read::Default.new
48
+
49
+ reader.execute
50
+ end
51
+
52
+ # Process function to update records in a PostgresDB database table
53
+ #
54
+ def process
55
+ response = Utils::Postgres::Request.execute(params)
56
+
57
+ if response.res_status == SUCCESS_STATUS
58
+ { success: { archived: true } }
59
+ else
60
+ { error: { message: response.result_error_message, status_code: response.res_status } }
61
+ end
62
+ end
63
+
64
+ # Write function to execute the PostgresDB write component
65
+ #
66
+ def write
67
+ write = Write::Postgres.new(write_options, process_response)
68
+
69
+ write.execute
70
+ end
71
+
72
+ private
73
+
74
+ def params
75
+ {
76
+ connection: process_options[:connection],
77
+ query:
78
+ }
79
+ end
80
+
81
+ def query
82
+ "UPDATE #{process_options[:db_table]} SET archived=true WHERE archived=false"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+ require_relative "../utils/openai/run_assistant"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::HumanizePto class serves as a bot implementation to read PTO's from a
11
+ # PostgresDb table, format them using an OpenAI Assistant with the OpenAI API, and
12
+ # write the response as a notification on a PostgresDB table.
13
+ #
14
+ # <br>
15
+ # <b>Example</b>
16
+ #
17
+ # options = {
18
+ # read_options: {
19
+ # connection: {
20
+ # host: "host",
21
+ # port: 5432,
22
+ # dbname: "bas",
23
+ # user: "postgres",
24
+ # password: "postgres"
25
+ # },
26
+ # db_table: "pto",
27
+ # tag: "FetchPtosFromNotion"
28
+ # },
29
+ # process_options: {
30
+ # secret: "openai secret key",
31
+ # assistant_id: "assistant_id",
32
+ # prompt: "optional additional prompt"
33
+ # },
34
+ # write_options: {
35
+ # connection: {
36
+ # host: "host",
37
+ # port: 5432,
38
+ # dbname: "bas",
39
+ # user: "postgres",
40
+ # password: "postgres"
41
+ # },
42
+ # db_table: "pto",
43
+ # tag: "HumanizePto"
44
+ # }
45
+ # }
46
+ #
47
+ # bot = Bot::HumanizePto.new(options)
48
+ # bot.execute
49
+ #
50
+ class HumanizePto < Bot::Base
51
+ DEFAULT_PROMPT = "{data}"
52
+
53
+ # read function to execute the PostgresDB Read component
54
+ #
55
+ def read
56
+ reader = Read::Postgres.new(read_options.merge(conditions))
57
+
58
+ reader.execute
59
+ end
60
+
61
+ # process function to execute the OpenaAI utility to process the PTO's
62
+ #
63
+ def process
64
+ return { success: { notification: "" } } if unprocessable_response
65
+
66
+ response = Utils::OpenAI::RunAssitant.execute(params)
67
+
68
+ if response.code != 200 || (!response["status"].nil? && response["status"] != "completed")
69
+ return error_response(response)
70
+ end
71
+
72
+ sucess_response(response)
73
+ end
74
+
75
+ # write function to execute the PostgresDB write component
76
+ #
77
+ def write
78
+ write = Write::Postgres.new(write_options, process_response)
79
+
80
+ write.execute
81
+ end
82
+
83
+ private
84
+
85
+ def conditions
86
+ {
87
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
88
+ params: [false, read_options[:tag], "unprocessed"]
89
+ }
90
+ end
91
+
92
+ def params
93
+ {
94
+ assistant_id: process_options[:assistant_id],
95
+ secret: process_options[:secret],
96
+ prompt: build_prompt
97
+ }
98
+ end
99
+
100
+ def build_prompt
101
+ prompt = process_options[:prompt] || DEFAULT_PROMPT
102
+ ptos_list = read_response.data["ptos"]
103
+
104
+ ptos_list_formatted_string = ptos_list.join("\n")
105
+
106
+ prompt.gsub("{data}", ptos_list_formatted_string)
107
+ end
108
+
109
+ def sucess_response(response)
110
+ { success: { notification: response.parsed_response["data"].first["content"].first["text"]["value"] } }
111
+ end
112
+
113
+ def error_response(response)
114
+ { error: { message: response.parsed_response, status_code: response.code } }
115
+ end
116
+ end
117
+ end