bas 0.4.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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