├── ArrayFunctions.js
├── Automate_Approvals
├── Automated Narrative Report
├── Big Query and Gemini Narrative Reporting.js
├── Data to Narrative with Input.js
├── EndtoEndAutomatedDecks.js
├── Formatting.js
├── GPT_4o_Supplier_Analysis.ipynb
├── Gemini_Functions.js
├── GettingandSettingData.js
├── Intro.js
├── Ranges.js
├── Sheets to Slides.js
├── YOuTube Comment Code with Likes.js
├── YouTube_Comments.js
└── automatereporting.js
/ArrayFunctions.js:
--------------------------------------------------------------------------------
1 | function basicArrayFunctions(){
2 | let fruits = ["Apple", "Banana", "Orange"];
3 | Logger.log(fruits);
4 | let array_len = fruits.length;
5 | Logger.log("Number of fruits: " + array_len);
6 |
7 | // pop() removes the last element and returns it
8 | let removedFruit = fruits.pop();
9 | Logger.log("Removed fruit: " + removedFruit); // Logs: "Removed fruit: Orange"
10 | Logger.log("Fruits after pop: " + fruits); // Logs: "Fruits after pop: Apple,Banana"
11 |
12 | // Add a new elements
13 | fruits.push("Grape");
14 | Logger.log("Fruits unsorted: " + fruits);
15 | fruits.sort(); // Sort Function
16 | Logger.log("Fruits sorted: " + fruits);
17 | fruits.reverse(); // Reverse Sort Functions
18 | Logger.log("Fruits reversed: " + fruits);
19 |
20 | }
21 |
22 | // Function the Arrays of Arrays pulled from a Google Sheet
23 | function sheetArrayFunctions() {
24 | let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Raw Data");
25 | let data = sheet.getDataRange().getValues(); // dynamically detects the data range and returns the value
26 | Logger.log(data);
27 | let header = data.shift(); // Shift Removed the first element from an Array and Returns it
28 | Logger.log(header);
29 | Logger.log(data);
30 |
31 | // Filter Function
32 | let fruitsOnly = data.filter(
33 | row => row[1] === "Fruit");
34 | Logger.log(fruitsOnly);
35 |
36 | // Map Function to each row
37 | let updatedData = fruitsOnly.map(row => {
38 | row[2] *= 1.10; // Increase price by 10%
39 | return row;
40 | });
41 | Logger.log(updatedData);
42 |
43 | // ForEach - Perform Operation on each row
44 | updatedData.forEach(row => row[1] = "fruits");
45 |
46 | // Insert header row back at the start
47 | updatedData.unshift(header);
48 |
49 | let ss = SpreadsheetApp.getActiveSpreadsheet();
50 | let reportSheet = ss.getSheetByName("Fruit Report");
51 | if (!reportSheet) {
52 | reportSheet = ss.insertSheet("Fruit Report");
53 | }
54 |
55 | // Clear old data and write the new data
56 | reportSheet.clear();
57 | reportSheet.getRange(1, 1, updatedData.length, updatedData[0].length).setValues(updatedData);
58 |
59 | Logger.log("Report generated successfully!");
60 | }
61 |
62 | /**
63 | * Sample of Data
64 | *
65 | Product Name Category Price Stock Supplier
66 | Apple Fruit 1 50 FreshFarms Ltd
67 | Banana Fruit 0.8 100 TropicalCo
68 | Bell Pepper Vegetable 1.4 25 FreshFarms Ltd
69 | Blueberry Fruit 2.5 20 FreshFarms Ltd
70 | Broccoli Vegetable 1.2 50 GreenLeaf Farms
71 | Carrots Vegetable 0.9 80 GreenLeaf Farms
72 | Dish Soap Household 1.5 40 HomeEssentials Inc
73 | *
74 |
75 | */
76 |
--------------------------------------------------------------------------------
/Automate_Approvals:
--------------------------------------------------------------------------------
1 | function sendApprovalRequests() {
2 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
3 | var data = sheet.getDataRange().getValues();
4 |
5 | for (var i = 1; i < data.length; i++) {
6 | var row = data[i];
7 | var status = row[4]; // Status column (E)
8 |
9 | if (status === "Pending") {
10 | var requestId = row[0];
11 | var requester = row[1];
12 | var item = row[2];
13 | var amount = row[3];
14 | var approverEmail = row[5];
15 | var pdfLink = row[7]; // PDF Link column (H)
16 |
17 | var subject = "Approval Request ID: " + requestId + " - " + new Date().getTime();
18 |
19 | var message = "Hello,\n\nYou have a new approval request from " + requester + ":\n\n" +
20 | "Item: " + item + "\n" +
21 | "Amount: " + amount + "\n\n";
22 |
23 | if (pdfLink) {
24 | message += "Please review the following document for more details:\n" + pdfLink + "\n\n";
25 | }
26 |
27 | message += "Please reply with 'Approved' or 'Rejected' in the first line of your response.\n\n" +
28 | "Thank you.";
29 |
30 | MailApp.sendEmail({
31 | to: approverEmail,
32 | subject: subject,
33 | body: message
34 | });
35 |
36 | // Update status to 'Sent'
37 | sheet.getRange(i+1, 5).setValue("Sent");
38 | // Store the unique subject to track the thread later in column G (index 7)
39 | sheet.getRange(i+1, 7).setValue(subject);
40 | }
41 | }
42 | }
43 |
44 |
45 |
46 | function checkForApprovals() {
47 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
48 | var data = sheet.getDataRange().getValues();
49 |
50 | for (var i = 1; i < data.length; i++) {
51 | var row = data[i];
52 | var status = row[4]; // Status column (E)
53 | var approverEmail = row[5];
54 | var subjectStored = row[6]; // Email Subject column (G)
55 |
56 | if (status === "Sent" && subjectStored) {
57 | var threads = GmailApp.search('subject:"' + subjectStored + '"');
58 | if (threads.length > 0) {
59 | var thread = threads[0];
60 | var messages = thread.getMessages();
61 | for (var k = 1; k < messages.length; k++) { // Start from k=1 to skip the sent message
62 | var message = messages[k];
63 | var sender = message.getFrom();
64 | if (sender.includes(approverEmail)) {
65 | var body = message.getPlainBody().trim();
66 | var firstLine = body.split('\n')[0].trim().toLowerCase();
67 |
68 | if (firstLine === "approved" || firstLine === "rejected") {
69 | // Update the status in the sheet
70 | sheet.getRange(i+1, 5).setValue(firstLine.charAt(0).toUpperCase() + firstLine.slice(1));
71 | // Optionally, mark the thread as completed
72 | thread.markRead();
73 | thread.moveToArchive();
74 | break;
75 | }
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Automated Narrative Report:
--------------------------------------------------------------------------------
1 | project = '**Enter Your GCP project here**';
2 |
3 | function auth() {
4 | cache = CacheService.getUserCache();
5 | token = ScriptApp.getOAuthToken();
6 | cache.put("token", token);
7 | }
8 |
9 | function fetchBigQueryData() {
10 | var projectId = project;
11 | var query = 'SELECT category,SUM(CASE WHEN Year_Created = 2021 THEN count ELSE 0 END) AS Count_2021, SUM(CASE WHEN Year_Created = 2022 THEN count ELSE 0 END) AS Count_2022,SUM(CASE WHEN Year_Created = 2023 THEN count ELSE 0 END) AS Count_2023,FROM(SELECT category,SOURCE,EXTRACT(YEAR FROM created_date) AS Year_Created,COUNT(unique_key) AS count FROM `bigquery-public-data.san_francisco_311.311_service_requests`WHERE EXTRACT(YEAR FROM created_date) IN (2021, 2022, 2023) GROUP BY ALL) GROUP BY ALL ORDER BY category; '
12 |
13 | var request = {
14 | query: query,
15 | useLegacySql: false
16 | };
17 |
18 | var queryResults = BigQuery.Jobs.query(request, projectId);
19 | var jobId = queryResults.jobReference.jobId;
20 |
21 | var results = BigQuery.Jobs.getQueryResults(projectId, jobId);
22 | var rows = results.rows;
23 |
24 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RawData');
25 | if (!sheet) {
26 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('RawData');
27 | } else {
28 | sheet.clear();
29 | }
30 |
31 | // Set headers
32 | var headers = results.schema.fields.map(field => field.name);
33 | sheet.appendRow(headers);
34 | var headerRange = sheet.getRange(1, 1, 1, headers.length);
35 | headerRange.setBackground('#000000').setFontColor('#FFFFFF').setFontWeight('bold');
36 |
37 | // Set Rows
38 | for (var i = 0; i < rows.length; i++) {
39 | var row = rows[i].f.map(cell => cell.v);
40 | sheet.appendRow(row);
41 | }
42 | }
43 |
44 | function formatReport() {
45 | auth();
46 | var ss = SpreadsheetApp.getActiveSpreadsheet();
47 | var rawSheet = ss.getSheetByName('RawData');
48 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
49 | reportSheet.clear();
50 |
51 | var rawData = rawSheet.getDataRange().getValues();
52 |
53 | cache = CacheService.getUserCache();
54 | token = cache.get("token");
55 | if (token == "") return "ERROR";
56 | Logger.log(`Token = ${token}`);
57 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-pro:generateContent`
58 | data = {
59 | contents: {
60 | role: "USER",
61 | parts: { "text": "You are a public policy expert. I providing you with three years of 311 service request data from San Francisco. Write a report commenting on improvements and negative movements from the period in question, include the absolute and percentage movement of categories in the discussion, do not include a table. Finish with a five point plan of where we need to put resources. Focus only on the data provided and double check calculations" + rawData}
62 | },
63 | generation_config: {
64 | temperature: 0.3,
65 | topP: 1,
66 | maxOutputTokens: 1000
67 | }
68 | }
69 | const options = {
70 | method: "post",
71 | contentType: 'application/json',
72 | headers: {
73 | Authorization: `Bearer ${token}`,
74 | },
75 | payload: JSON.stringify(data)
76 | };
77 |
78 | const response = UrlFetchApp.fetch(url, options);
79 | if (response.getResponseCode() == 200) {
80 | json = JSON.parse(response.getContentText());
81 | answer = json.candidates[0].content.parts[0].text;
82 | Logger.log(answer);
83 |
84 | // Format the Markdown in Sheets
85 | var lines = answer.split('\n');
86 | var rowIndex = 1;
87 |
88 | lines.forEach(function(line) {
89 | var cell = reportSheet.getRange(rowIndex, 1);
90 | var text = line.trim();
91 |
92 | if (text.startsWith('### ')) {
93 | // Handle H3 titles
94 | text = text.replace('### ', '');
95 | cell.setValue(text)
96 | .setFontWeight('bold')
97 | .setFontSize(14)
98 | .setBackground('#efefef');
99 | } else if (text.startsWith('## ')) {
100 | // Handle H2 titles
101 | text = text.replace('## ', '');
102 | cell.setValue(text)
103 | .setFontWeight('bold')
104 | .setFontSize(16)
105 | .setBackground('#efefef');
106 | } else if (text.startsWith('**')) {
107 | // Handle H2 titles
108 | text = text.replace('**', '');
109 | text = text.replace(':**', '');
110 | cell.setValue(text)
111 | .setFontWeight('bold')
112 | .setFontSize(12)
113 | .setBackground('#efefef');
114 | } else if (text.startsWith('# ')) {
115 | // Handle H1 titles
116 | text = text.replace('# ', '');
117 | cell.setValue(text)
118 | .setFontWeight('bold')
119 | .setFontSize(18)
120 | .setBackground('#CFE2F3');
121 | } else {
122 | // Handle regular text with potential bold sections
123 | var richTextBuilder = SpreadsheetApp.newRichTextValue().setText(text);
124 | var regex = /\*\*(.*?)\*\*/g;
125 | var match;
126 | var lastIndex = 0;
127 |
128 | while ((match = regex.exec(text)) !== null) {
129 | richTextBuilder.setTextStyle(match.index, match.index + match[0].length,
130 | SpreadsheetApp.newTextStyle().setBold(true).build());
131 | lastIndex = match.index + match[0].length;
132 | }
133 |
134 | // Remove markdown bold indicators
135 | text = text.replace(/\*\*/g, '');
136 | richTextBuilder.setText(text);
137 |
138 | cell.setRichTextValue(richTextBuilder.build());
139 | }
140 |
141 | cell.setWrap(true);
142 | rowIndex++;
143 | });
144 |
145 | // Set column width and enable text wrapping
146 | reportSheet.setColumnWidth(1, 1100);
147 | reportSheet.getRange(1, 1, rowIndex, 1).setWrap(true);
148 |
149 | // Add some indent to the left edge of the page
150 | reportSheet.insertColumnBefore(1);
151 | reportSheet.setColumnWidth(1, 50); // Add a narrow column for left margin
152 |
153 | // Autofit rows
154 | reportSheet.autoResizeRows(1, rowIndex);
155 |
156 | return answer;
157 | }
158 | Logger.log("ERROR");
159 | }
160 |
161 | function onOpen() {
162 | var ui = SpreadsheetApp.getUi();
163 | ui.createMenu('Run Report')
164 | .addItem('Refresh Data', 'fetchBigQueryData')
165 | .addItem('Refresh Report and Data', 'generateReport')
166 | .addItem('Refresh Report', 'formatReport')
167 | .addItem('Authenticate', 'auth')
168 | .addToUi();
169 | }
170 |
171 | function generateReport() {
172 | auth();
173 | fetchBigQueryData();
174 | formatReport();
175 | }
176 |
--------------------------------------------------------------------------------
/Big Query and Gemini Narrative Reporting.js:
--------------------------------------------------------------------------------
1 |
2 | // Code.js
3 |
4 | project = 'ADD Google Cloud PROJECT NAME HERE';
5 |
6 | function auth() {
7 | cache = CacheService.getUserCache();
8 | token = ScriptApp.getOAuthToken();
9 | cache.put("token", token);
10 | }
11 |
12 | function fetchBigQueryData() {
13 | var projectId = project;
14 | var query = 'SELECT category,SUM(CASE WHEN Year_Created = 2021 THEN count ELSE 0 END) AS Count_2021, SUM(CASE WHEN Year_Created = 2022 THEN count ELSE 0 END) AS Count_2022,SUM(CASE WHEN Year_Created = 2023 THEN count ELSE 0 END) AS Count_2023,FROM(SELECT category,SOURCE,EXTRACT(YEAR FROM created_date) AS Year_Created,COUNT(unique_key) AS count FROM `bigquery-public-data.san_francisco_311.311_service_requests`WHERE EXTRACT(YEAR FROM created_date) IN (2021, 2022, 2023) GROUP BY ALL) GROUP BY ALL ORDER BY category; '
15 |
16 | var request = {
17 | query: query,
18 | useLegacySql: false
19 | };
20 |
21 | var queryResults = BigQuery.Jobs.query(request, projectId);
22 | var jobId = queryResults.jobReference.jobId;
23 |
24 | var results = BigQuery.Jobs.getQueryResults(projectId, jobId);
25 | var rows = results.rows;
26 |
27 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RawData');
28 | if (!sheet) {
29 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('RawData');
30 | } else {
31 | sheet.clear();
32 | }
33 |
34 | // Set headers
35 | var headers = results.schema.fields.map(field => field.name);
36 | sheet.appendRow(headers);
37 | var headerRange = sheet.getRange(1, 1, 1, headers.length);
38 | headerRange.setBackground('#000000').setFontColor('#FFFFFF').setFontWeight('bold');
39 |
40 | // Set Rows
41 | var values = rows.map(row => row.f.map(cell => cell.v));
42 | // Get the next empty row in the sheet
43 | var lastRow = sheet.getLastRow();
44 | // Append all rows at once
45 | if (values.length > 0) {
46 | sheet.getRange(lastRow + 1, 1, values.length, values[0].length).setValues(values);
47 | }
48 | }
49 |
50 | function formatReport() {
51 | auth();
52 | var ss = SpreadsheetApp.getActiveSpreadsheet();
53 | var rawSheet = ss.getSheetByName('RawData');
54 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
55 | reportSheet.clear();
56 |
57 | var rawData = rawSheet.getDataRange().getValues();
58 |
59 | cache = CacheService.getUserCache();
60 | token = cache.get("token");
61 | if (token == "") return "ERROR";
62 | Logger.log(`Token = ${token}`);
63 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-pro:generateContent`
64 | data = {
65 | contents: {
66 | role: "USER",
67 | parts: { "text": "You are a public policy expert. I providing you with three years of 311 service request data from San Francisco. Write a report commenting on improvements and negative movements from the period in question, include the absolute and percentage movement of categories in the discussion, do not include a table. Finish with a five point plan of where we need to put resources. Focus only on the data provided and double check calculations" + rawData}
68 | },
69 | generation_config: {
70 | temperature: 0.3,
71 | topP: 1,
72 | maxOutputTokens: 1000
73 | }
74 | }
75 | const options = {
76 | method: "post",
77 | contentType: 'application/json',
78 | headers: {
79 | Authorization: `Bearer ${token}`,
80 | },
81 | payload: JSON.stringify(data)
82 | };
83 |
84 | const response = UrlFetchApp.fetch(url, options);
85 | if (response.getResponseCode() == 200) {
86 | json = JSON.parse(response.getContentText());
87 | answer = json.candidates[0].content.parts[0].text;
88 | Logger.log(answer);
89 |
90 | // Format the Markdown in Sheets
91 | var lines = answer.split('\n');
92 | var rowIndex = 1;
93 |
94 | lines.forEach(function(line) {
95 | var cell = reportSheet.getRange(rowIndex, 1);
96 | var text = line.trim();
97 |
98 | if (text.startsWith('### ')) {
99 | // Handle H3 titles
100 | text = text.replace('### ', '');
101 | cell.setValue(text)
102 | .setFontWeight('bold')
103 | .setFontSize(14)
104 | .setBackground('#efefef');
105 | } else if (text.startsWith('## ')) {
106 | // Handle H2 titles
107 | text = text.replace('## ', '');
108 | cell.setValue(text)
109 | .setFontWeight('bold')
110 | .setFontSize(16)
111 | .setBackground('#efefef');
112 | }
113 | else if (text.startsWith('* ')) {
114 | // Handle H2 titles
115 | text = text.replace('* ', '');
116 | text = text.replace('**', '');
117 | text = text.replace('**', '');
118 | cell.setValue(text)
119 | }
120 | else if (text.startsWith('**')) {
121 | // Handle H2 titles
122 | text = text.replace('**', '');
123 | text = text.replace('**', '');
124 | cell.setValue(text)
125 | .setFontWeight('bold')
126 | .setFontSize(12)
127 | .setBackground('#efefef');
128 | } else if (text.startsWith('# ')) {
129 | // Handle H1 titles
130 | text = text.replace('# ', '');
131 | cell.setValue(text)
132 | .setFontWeight('bold')
133 | .setFontSize(18)
134 | .setBackground('#CFE2F3');
135 | } else {
136 | // Handle regular text with potential bold sections
137 | var richTextBuilder = SpreadsheetApp.newRichTextValue().setText(text);
138 | var regex = /\*\*(.*?)\*\*/g;
139 | var match;
140 | var lastIndex = 0;
141 |
142 | while ((match = regex.exec(text)) !== null) {
143 | richTextBuilder.setTextStyle(match.index, match.index + match[0].length,
144 | SpreadsheetApp.newTextStyle().setBold(true).build());
145 | lastIndex = match.index + match[0].length;
146 | }
147 |
148 | // Remove markdown bold indicators
149 | text = text.replace(/\*\*/g, '');
150 | richTextBuilder.setText(text);
151 |
152 | cell.setRichTextValue(richTextBuilder.build());
153 |
154 | }
155 |
156 | cell.setWrap(true);
157 | rowIndex++;
158 | });
159 |
160 | // Set column width and enable text wrapping
161 | reportSheet.setColumnWidth(1, 1100);
162 | reportSheet.getRange(1, 1, rowIndex, 1).setWrap(true);
163 |
164 | // Add some indent to the left edge of the page
165 | reportSheet.insertColumnBefore(1);
166 | reportSheet.setColumnWidth(1, 50); // Add a narrow column for left margin
167 |
168 | // Autofit rows
169 | reportSheet.autoResizeRows(1, rowIndex);
170 |
171 | return answer;
172 | }
173 | Logger.log("ERROR");
174 | }
175 |
176 | function onOpen() {
177 | var ui = SpreadsheetApp.getUi();
178 | ui.createMenu('Run Report')
179 | .addItem('Refresh Data', 'fetchBigQueryData')
180 | .addItem('Refresh Report and Data', 'generateReport')
181 | .addItem('Refresh Report', 'formatReport')
182 | .addItem('Authenticate', 'auth')
183 | .addToUi();
184 | }
185 |
186 | function generateReport() {
187 | auth();
188 | fetchBigQueryData();
189 | formatReport();
190 | }
191 |
192 |
193 | // appscript.json
194 |
195 | {
196 | "timeZone": "Europe/Dublin",
197 | "dependencies": {
198 | "enabledAdvancedServices": [
199 | {
200 | "userSymbol": "BigQuery",
201 | "serviceId": "bigquery",
202 | "version": "v2"
203 | }
204 | ]
205 | },
206 | "exceptionLogging": "STACKDRIVER",
207 | "oauthScopes": [
208 | "https://www.googleapis.com/auth/spreadsheets.currentonly",
209 | "https://www.googleapis.com/auth/script.external_request",
210 | "https://www.googleapis.com/auth/cloud-platform",
211 | "https://www.googleapis.com/auth/documents"
212 | ],
213 | "runtimeVersion": "V8"
214 | }
215 |
--------------------------------------------------------------------------------
/Data to Narrative with Input.js:
--------------------------------------------------------------------------------
1 | project = 'ENTER GOOGLE CLOUD PROJECT ID HERE';
2 |
3 | function auth() {
4 | cache = CacheService.getUserCache();
5 | token = ScriptApp.getOAuthToken();
6 | cache.put("token", token);
7 | }
8 |
9 | function pullVariables() {
10 | var ss = SpreadsheetApp.getActiveSpreadsheet();
11 | var sheet = ss.getSheetByName("Input");
12 |
13 | if (!sheet) {
14 | throw new Error("Sheet 'Input' not found");
15 | }
16 |
17 | var values = sheet.getRange("C5:C9").getValues().flat();
18 | var validationRange = sheet.getRange("E5:E9");
19 |
20 | var validationValues = [];
21 | var validationBackgrounds = [];
22 | var errorMessage = "";
23 |
24 | for (var i = 0; i < 5; i++) {
25 | if (values[i]) {
26 | validationValues.push(["Valid input provided"]);
27 | validationBackgrounds.push(["#b6d7a8"]); // Light green
28 | } else {
29 | validationValues.push(["Error: Input not provided"]);
30 | validationBackgrounds.push(["#ea9999"]); // Light red
31 | errorMessage += `Input Field ${i + 1} is empty. `;
32 | }
33 | }
34 |
35 | validationRange.setValues(validationValues);
36 | validationRange.setBackgrounds(validationBackgrounds);
37 |
38 | if (errorMessage) {
39 | throw new Error(errorMessage);
40 | }
41 |
42 | return {
43 | var1: values[0],
44 | var2: values[1],
45 | var3: values[2],
46 | var4: values[3],
47 | var5: values[4]
48 | };
49 | }
50 |
51 |
52 | function fetchBigQueryData() {
53 | var input = pullVariables()
54 | Logger.log(input);
55 | var projectId = project;
56 |
57 | var query = `SELECT extract(year from date) as year, category_name, item_description, vendor_name, COUNT(*) as num_trans, ROUND(SUM(sale_dollars),2) as sales_in_usd, ROUND(SUM(state_bottle_cost * bottles_sold),2) as cost FROM \`bigquery-public-data.iowa_liquor_sales.sales\` Where EXTRACT(year FROM date) IN (${input.var1}, ${input.var2}) AND category_name = '${input.var3}' AND city = '${input.var4}' Group by ALL Order BY 5 desc;`
58 |
59 | var request = {
60 | query: query,
61 | useLegacySql: false
62 | };
63 |
64 | var queryResults = BigQuery.Jobs.query(request, projectId);
65 | var jobId = queryResults.jobReference.jobId;
66 |
67 | var results = BigQuery.Jobs.getQueryResults(projectId, jobId);
68 | var rows = results.rows;
69 |
70 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RawData');
71 | if (!sheet) {
72 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('RawData');
73 | } else {
74 | sheet.clear();
75 | }
76 |
77 | // Set headers
78 | var headers = results.schema.fields.map(field => field.name);
79 | sheet.appendRow(headers);
80 | var headerRange = sheet.getRange(1, 1, 1, headers.length);
81 | headerRange.setBackground('#000000').setFontColor('#FFFFFF').setFontWeight('bold');
82 |
83 | // Set Rows
84 | var values = rows.map(row => row.f.map(cell => cell.v));
85 | // Get the next empty row in the sheet
86 | var lastRow = sheet.getLastRow();
87 | // Append all rows at once
88 | if (values.length > 0) {
89 | sheet.getRange(lastRow + 1, 1, values.length, values[0].length).setValues(values);
90 | }
91 | }
92 |
93 | function formatReport() {
94 | auth();
95 | var input = pullVariables()
96 | var ss = SpreadsheetApp.getActiveSpreadsheet();
97 | var rawSheet = ss.getSheetByName('RawData');
98 | var aggSheet = ss.getSheetByName('Pivot');
99 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
100 | reportSheet.clear();
101 |
102 | var rawData = rawSheet.getDataRange().getValues(); // Unpivoted Data - Not Using below
103 | var aggData = aggSheet.getDataRange().getValues(); // Pivot Data
104 |
105 | cache = CacheService.getUserCache();
106 | token = cache.get("token");
107 | if (token == "") return "ERROR";
108 | Logger.log(`Token = ${token}`);
109 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-flash:generateContent`
110 | data = {
111 | contents: {
112 | role: "USER",
113 | parts: { "text": `${input.var5}` + ' Only reffer to the data provided do not make anything up. Full Data:' + aggData}
114 | },
115 | generation_config: {
116 | temperature: 0.3,
117 | topP: 1,
118 | maxOutputTokens: 1000
119 | }
120 | }
121 | const options = {
122 | method: "post",
123 | contentType: 'application/json',
124 | headers: {
125 | Authorization: `Bearer ${token}`,
126 | },
127 | payload: JSON.stringify(data)
128 | };
129 |
130 | const response = UrlFetchApp.fetch(url, options);
131 | if (response.getResponseCode() == 200) {
132 | json = JSON.parse(response.getContentText());
133 | answer = json.candidates[0].content.parts[0].text;
134 | Logger.log(answer);
135 |
136 | // Format the Markdown in Sheets
137 | var lines = answer.split('\n');
138 | var rowIndex = 1;
139 |
140 | lines.forEach(function(line) {
141 | var cell = reportSheet.getRange(rowIndex, 1);
142 | var text = line.trim();
143 |
144 | if (text.startsWith('### ')) {
145 | // Handle H3 titles
146 | text = text.replace('### ', '');
147 | cell.setValue(text)
148 | .setFontWeight('bold')
149 | .setFontSize(14)
150 | .setBackground('#083763') // Dark Blue 3 background
151 | .setFontColor('#FFFFFF'); // Set text color to white
152 | } else if (text.startsWith('## ')) {
153 | // Handle H2 titles
154 | text = text.replace('## ', '');
155 | cell.setValue(text)
156 | .setFontWeight('bold')
157 | .setFontSize(16)
158 | .setBackground('#083763') // Dark Blue 3 background
159 | .setFontColor('#FFFFFF'); // Set text color to white
160 | } else if (text.startsWith('* ')) {
161 | // Handle bullet points
162 | text = text.replace('* ', '');
163 | text = text.replace(/\*\*/g, ''); // Remove all '**'
164 | cell.setValue(text);
165 | } else if (text.startsWith('**')) {
166 | // Handle bold text
167 | text = text.replace(/\*\*/g, ''); // Remove all '**'
168 | cell.setValue(text)
169 | .setFontWeight('bold')
170 | .setFontSize(12)
171 | .setBackground('#083763') // Dark Blue 3 background
172 | .setFontColor('#FFFFFF'); // Set text color to white
173 | } else if (text.startsWith('# ')) {
174 | // Handle H1 titles
175 | text = text.replace('# ', '');
176 | cell.setValue(text)
177 | .setFontWeight('bold')
178 | .setFontSize(18)
179 | .setBackground('#CFE2F3');
180 | } else {
181 | // Handle regular text with potential bold sections
182 | var richTextBuilder = SpreadsheetApp.newRichTextValue().setText(text);
183 | var regex = /\*\*(.*?)\*\*/g;
184 | var match;
185 | var lastIndex = 0;
186 |
187 | while ((match = regex.exec(text)) !== null) {
188 | richTextBuilder.setTextStyle(match.index, match.index + match[1].length,
189 | SpreadsheetApp.newTextStyle().setBold(true).build());
190 | lastIndex = match.index + match[0].length;
191 | }
192 |
193 | // Remove markdown bold indicators
194 | text = text.replace(/\*\*/g, '');
195 | richTextBuilder.setText(text);
196 |
197 | cell.setRichTextValue(richTextBuilder.build());
198 | }
199 | rowIndex++;
200 | });
201 |
202 | // Set column width and enable text wrapping
203 | reportSheet.setColumnWidth(1, 1250);
204 | reportSheet.getRange(1, 1, rowIndex, 1).setWrap(true);
205 |
206 | // Add some indent to the left edge of the page
207 | reportSheet.insertColumnBefore(1);
208 | reportSheet.setColumnWidth(1, 50); // Add a narrow column for left margin
209 |
210 | // Autofit rows
211 | reportSheet.autoResizeRows(1, rowIndex);
212 | return answer;
213 | }
214 | Logger.log("ERROR");
215 | }
216 |
217 | function onOpen() {
218 | var ui = SpreadsheetApp.getUi();
219 | ui.createMenu('Run Report')
220 | .addItem('Refresh Data', 'fetchBigQueryData')
221 | .addItem('Refresh Report and Data', 'generateReport')
222 | .addItem('Refresh Report', 'formatReport')
223 | .addItem('Authenticate', 'auth')
224 | .addToUi();
225 | }
226 |
227 | function generateReport() {
228 | auth();
229 | fetchBigQueryData();
230 | formatReport();
231 | }
232 |
--------------------------------------------------------------------------------
/EndtoEndAutomatedDecks.js:
--------------------------------------------------------------------------------
1 | // Appscript.json
2 |
3 | {
4 | "timeZone": "Europe/Dublin",
5 | "dependencies": {
6 | "enabledAdvancedServices": [{
7 | "userSymbol": "BigQuery",
8 | "serviceId": "bigquery",
9 | "version": "v2"
10 | }]
11 | },
12 | "exceptionLogging": "STACKDRIVER",
13 | "oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/documents", "https://www.googleapis.com/auth/presentations"],
14 | "runtimeVersion": "V8"
15 | }
16 |
17 |
18 |
19 |
20 | // Script.JS
21 |
22 | project = 'GoogleCloudProjectNameHERE';
23 |
24 | function auth() {
25 | cache = CacheService.getUserCache();
26 | token = ScriptApp.getOAuthToken();
27 | cache.put("token", token);
28 | }
29 |
30 | function pullVariables() {
31 | var ss = SpreadsheetApp.getActiveSpreadsheet();
32 | var sheet = ss.getSheetByName("Input");
33 |
34 | if (!sheet) {
35 | throw new Error("Sheet 'Input' not found");
36 | }
37 |
38 | var values = sheet.getRange("C5:C11").getValues().flat();
39 | var validationRange = sheet.getRange("E5:E11");
40 |
41 | var validationValues = [];
42 | var validationBackgrounds = [];
43 | var errorMessage = "";
44 |
45 | for (var i = 0; i < 7; i++) {
46 | if (values[i]) {
47 | validationValues.push(["Valid input provided"]);
48 | validationBackgrounds.push(["#b6d7a8"]); // Light green
49 | } else {
50 | validationValues.push(["Error: Input not provided"]);
51 | validationBackgrounds.push(["#ea9999"]); // Light red
52 | errorMessage += `Input Field ${i + 1} is empty. `;
53 | }
54 | }
55 |
56 | validationRange.setValues(validationValues);
57 | validationRange.setBackgrounds(validationBackgrounds);
58 |
59 | if (errorMessage) {
60 | throw new Error(errorMessage);
61 | }
62 |
63 | return {
64 | var1: values[0],
65 | var2: values[1],
66 | var3: values[2],
67 | var4: values[3],
68 | var5: values[4],
69 | var6: values[5],
70 | var7: values[6]
71 | };
72 | }
73 |
74 |
75 | function fetchBigQueryData() {
76 | var input = pullVariables()
77 | Logger.log(input);
78 | var projectId = project;
79 |
80 | var query = `SELECT extract(year from date) as year, category_name, item_description, vendor_name, COUNT(*) as num_trans, ROUND(SUM(sale_dollars),2) as sales_in_usd, ROUND(SUM(state_bottle_cost * bottles_sold),2) as cost FROM \`bigquery-public-data.iowa_liquor_sales.sales\` Where EXTRACT(year FROM date) IN (${input.var1}, ${input.var2}) AND category_name IN ('${input.var3}','${input.var4}','${input.var5}') AND city = '${input.var6}' Group by ALL Order BY 5 desc;`
81 |
82 | var request = {
83 | query: query,
84 | useLegacySql: false
85 | };
86 |
87 | var queryResults = BigQuery.Jobs.query(request, projectId);
88 | var jobId = queryResults.jobReference.jobId;
89 |
90 | var results = BigQuery.Jobs.getQueryResults(projectId, jobId);
91 | var rows = results.rows;
92 |
93 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RawData');
94 | if (!sheet) {
95 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('RawData');
96 | } else {
97 | sheet.clear();
98 | }
99 |
100 | // Set headers
101 | var headers = results.schema.fields.map(field => field.name);
102 | sheet.appendRow(headers);
103 | var headerRange = sheet.getRange(1, 1, 1, headers.length);
104 | headerRange.setBackground('#000000').setFontColor('#FFFFFF').setFontWeight('bold');
105 |
106 | // Set Rows
107 | var values = rows.map(row => row.f.map(cell => cell.v));
108 | // Get the next empty row in the sheet
109 | var lastRow = sheet.getLastRow();
110 | // Append all rows at once
111 | if (values.length > 0) {
112 | sheet.getRange(lastRow + 1, 1, values.length, values[0].length).setValues(values);
113 | }
114 | }
115 |
116 | function formatReport() {
117 | auth();
118 | var input = pullVariables()
119 | var ss = SpreadsheetApp.getActiveSpreadsheet();
120 | var aggSheet = ss.getSheetByName('Category');
121 | var aggSheet2 = ss.getSheetByName('Vendor');
122 | var reportSheet = ss.getSheetByName('Output') || ss.insertSheet('Output');
123 | reportSheet.clear();
124 |
125 | var aggData = aggSheet.getDataRange().getValues(); // Pivot Data
126 | var aggData2 = aggSheet2.getDataRange().getValues(); // Pivot Data
127 |
128 | cache = CacheService.getUserCache();
129 | token = cache.get("token");
130 | if (token == "") return "ERROR";
131 | Logger.log(`Token = ${token}`);
132 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-pro:generateContent`
133 | data = {
134 | contents: {
135 | role: "USER",
136 | parts: { "text": `${input.var7}` + ' Only reffer to the data provided do not make anything up. This is the Aggregated data:'+ aggData2 + '---- This is the Full Data:' + aggData}
137 | },
138 | generation_config: {
139 | temperature: 0.3,
140 | topP: 1,
141 | maxOutputTokens: 1000
142 | }
143 | }
144 | const options = {
145 | method: "post",
146 | contentType: 'application/json',
147 | headers: {
148 | Authorization: `Bearer ${token}`,
149 | },
150 | payload: JSON.stringify(data)
151 | };
152 |
153 | const response = UrlFetchApp.fetch(url, options);
154 | if (response.getResponseCode() == 200) {
155 | json = JSON.parse(response.getContentText());
156 | answer = json.candidates[0].content.parts[0].text;
157 | Logger.log(answer);
158 | var commentary = reportSheet.getRange("J3");
159 | commentary.setValue(answer);
160 | commentary.setWrap(true);
161 | }
162 | else{
163 | Logger.log("ERROR");
164 | }
165 | }
166 |
167 | function onOpen() {
168 | var ui = SpreadsheetApp.getUi();
169 | ui.createMenu('Run Report')
170 | .addItem('Refresh Data', 'fetchBigQueryData')
171 | .addItem('Refresh Report and Transfer to Chart', 'generateReport')
172 | .addItem('Refresh Report', 'formatReport')
173 | .addItem('Authenticate', 'auth')
174 | .addItem('Transfer Charts to Deck','transferCharts')
175 | .addToUi();
176 | }
177 |
178 |
179 | function transferCharts() {
180 | // Open the Google Slides deck where you want to transfer the charts
181 | var slidesDeck = SlidesApp.openById("1zS2VBQwungFdf-pktG1qdNiHzmeXPlgTO18CKe903bU");
182 |
183 | // Open the 'Output' sheet from the active Google Sheet
184 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Output');
185 |
186 | // Get the first slide
187 | var slide = slidesDeck.getSlides()[0];
188 |
189 | // Clear existing images and text boxes on the slide except the header icon
190 | var elements = slide.getPageElements();
191 | elements.forEach(function(element) {
192 | var topPosition = element.getTop();
193 |
194 | if (element.getPageElementType() === SlidesApp.PageElementType.IMAGE) {
195 | // Remove images that are below the header (assuming header is within top 150 pixels)
196 | if (topPosition > 99) {
197 | element.remove();
198 | }
199 | } else if (element.getPageElementType() === SlidesApp.PageElementType.SHAPE) {
200 | // Remove text boxes that are below the header
201 | if (topPosition > 99) {
202 | element.remove();
203 | }
204 | }
205 | });
206 |
207 | // Get the chart from the sheet and insert it into the slide
208 | var charts = sheet.getCharts();
209 | if (charts.length > 0) {
210 | var chart = charts[0]; // Get the first chart (assuming it's the only one)
211 |
212 | // Get the chart as a blob image
213 | var image = chart.getAs('image/png');
214 |
215 | // Insert the image into the slide
216 | slide.insertImage(image).setLeft(30)
217 | .setTop(100)
218 | .setWidth(400)
219 | .setHeight(250);;
220 | } else {
221 | Logger.log('No charts found on the sheet.');
222 | }
223 |
224 | // Get the narrative from cell J3
225 | var narrative = sheet.getRange('J3').getValue();
226 |
227 | // Insert the narrative into the slide as a text box
228 | if (narrative) {
229 | var textBox = slide.insertTextBox(narrative);
230 | // Adjust the position and size of the text box as needed
231 | textBox.setLeft(450);
232 | textBox.setTop(140);
233 | textBox.setWidth(200);
234 | textBox.setHeight(150);
235 |
236 | // Optional: Set text style
237 | var textRange = textBox.getText();
238 | textRange.getTextStyle().setFontSize(8);
239 | } else {
240 | Logger.log('No narrative found in cell J3.');
241 | }
242 |
243 | // Show a confirmation message
244 | SpreadsheetApp.getUi().alert('Chart and Narrative Transferred to Slide 1');
245 | }
246 |
247 | function generateReport() {
248 | auth();
249 | fetchBigQueryData();
250 | formatReport();
251 | transferCharts();
252 | }
253 |
--------------------------------------------------------------------------------
/Formatting.js:
--------------------------------------------------------------------------------
1 | function onOpen() {
2 | // Add a custom menu to the spreadsheet.
3 | SpreadsheetApp.getUi()
4 | .createMenu('Formatting')
5 | .addSubMenu(SpreadsheetApp.getUi().createMenu('Apply Style')
6 | .addItem('Company Style', 'applyCompanyStyle')
7 | .addItem('McDonalds Style', 'applyMcDonaldsStyle')
8 | .addItem('Coca-Cola Style', 'applyCocaColaStyle')
9 | .addItem('Remove Formatting', 'removeFormatting'))
10 | .addToUi();
11 | }
12 |
13 | function applyCompanyStyle() {
14 | // Get the active spreadsheet and sheet
15 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
16 | const sheet = spreadsheet.getActiveSheet();
17 |
18 | // Get the last row and column to dynamically select the data range
19 | const lastRow = sheet.getLastRow();
20 | const lastColumn = sheet.getLastColumn();
21 |
22 | // Define the header range (assuming headers are in the first row)
23 | const headerRange = sheet.getRange(1, 1, 1, lastColumn);
24 |
25 | // Apply header formatting
26 | headerRange.setBackground('#e0e0e0'); // Light gray background
27 | headerRange.setFontWeight('bold');
28 | headerRange.setFontFamily('Roboto');
29 | headerRange.setFontColor('#333333'); // Dark gray text
30 | headerRange.setHorizontalAlignment('left');
31 |
32 | // Define the data range (excluding headers)
33 | const dataRange = sheet.getRange(2, 1, lastRow - 1, lastColumn);
34 |
35 | // Apply data formatting
36 | dataRange.setFontFamily('Roboto');
37 | dataRange.setFontColor('#555555'); // Medium gray text
38 | dataRange.setBackground('white');
39 |
40 | // Example of formatting a specific column (e.g., Sales column)
41 | const salesColumnIndex = 5; // Assuming "Sales" is the 3rd column
42 | const salesColumnRange = sheet.getRange(2, salesColumnIndex, lastRow - 1, 1);
43 | salesColumnRange.setNumberFormat('$#,##0.0'); // Currency format
44 |
45 | // Auto-resize columns for better readability
46 | sheet.autoResizeColumns(1, lastColumn);
47 |
48 | Logger.log('Google style formatting applied!');
49 | }
50 |
51 | function applyMcDonaldsStyle() {
52 | // Get the active spreadsheet and sheet
53 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
54 | const sheet = spreadsheet.getActiveSheet();
55 |
56 | // Get the last row and column
57 | const lastRow = sheet.getLastRow();
58 | const lastColumn = sheet.getLastColumn();
59 |
60 | // Define the header range
61 | const headerRange = sheet.getRange(1, 1, 1, lastColumn);
62 |
63 | // Apply header formatting
64 | headerRange.setBackground('#FFC72C'); // McDonald's Yellow
65 | headerRange.setFontWeight('bold');
66 | headerRange.setFontFamily('Arial'); // A commonly available bold font
67 | headerRange.setFontColor('#D9001B'); // McDonald's Red
68 | headerRange.setHorizontalAlignment('center');
69 |
70 | // Apply red alternating row background for data
71 | for (let i = 2; i <= lastRow; i++) {
72 | const rowRange = sheet.getRange(i, 1, 1, lastColumn);
73 | if (i % 2 === 0) { // Even rows
74 | rowRange.setBackground('#FFE0B2'); // Lighter Yellow
75 | } else { // Odd rows
76 | rowRange.setBackground('#FFF3E0'); // Very light orange/beige
77 | }
78 | rowRange.setFontFamily('Arial');
79 | rowRange.setFontColor('#000000'); // Black text
80 | }
81 |
82 | // Example of formatting a specific column (e.g., Sales column)
83 | const salesColumnIndex = 5; // Assuming "Sales" is the 3rd column
84 | const salesColumnRange = sheet.getRange(2, salesColumnIndex, lastRow - 1, 1);
85 | salesColumnRange.setNumberFormat('$#,##0'); // Currency format
86 |
87 | // Auto-resize columns
88 | sheet.autoResizeColumns(1, lastColumn);
89 |
90 | Logger.log('McDonald\'s style formatting applied!');
91 | }
92 |
93 | function applyCocaColaStyle() {
94 | // Get the active spreadsheet and sheet
95 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
96 | const sheet = spreadsheet.getActiveSheet();
97 |
98 | // Get the last row and column
99 | const lastRow = sheet.getLastRow();
100 | const lastColumn = sheet.getLastColumn();
101 |
102 | // Define the header range
103 | const headerRange = sheet.getRange(1, 1, 1, lastColumn);
104 |
105 | // Apply header formatting - Coca-Cola Red and White
106 | headerRange.setBackground('#CC0000'); // Coca-Cola Red (approximate)
107 | headerRange.setFontWeight('bold');
108 | headerRange.setFontFamily('Arial'); // A clean, readable font
109 | headerRange.setFontColor('white');
110 | headerRange.setHorizontalAlignment('center');
111 |
112 | // Apply alternating row backgrounds - White and Very Light Gray
113 | for (let i = 2; i <= lastRow; i++) {
114 | const rowRange = sheet.getRange(i, 1, 1, lastColumn);
115 | if (i % 2 === 0) { // Even rows
116 | rowRange.setBackground('white');
117 | } else { // Odd rows
118 | rowRange.setBackground('#f0f0f0'); // Very light gray
119 | }
120 | rowRange.setFontFamily('Arial');
121 | rowRange.setFontColor('black');
122 | }
123 |
124 | // Example of formatting a specific column (e.g., Sales column)
125 | const salesColumnIndex = 5; // Assuming "Sales" is the 3rd column
126 | const salesColumnRange = sheet.getRange(2, salesColumnIndex, lastRow - 1, 1);
127 | salesColumnRange.setNumberFormat('$#,##0.00'); // Currency format
128 |
129 | // Auto-resize columns
130 | sheet.autoResizeColumns(1, lastColumn);
131 |
132 | Logger.log('Coca-Cola style formatting applied!');
133 | }
134 |
135 | function removeFormatting() {
136 | // Get the active spreadsheet and sheet
137 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
138 | const sheet = spreadsheet.getActiveSheet();
139 |
140 | // Get the range of all cells that contain data
141 | const lastRow = sheet.getLastRow();
142 | const lastColumn = sheet.getLastColumn();
143 | const range = sheet.getRange(1, 1, lastRow, lastColumn);
144 |
145 | // Clear all formatting from the range
146 | range.clearFormat();
147 |
148 | Logger.log('Formatting removed from the sheet.');
149 | }
150 |
151 |
--------------------------------------------------------------------------------
/GPT_4o_Supplier_Analysis.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "include_colab_link": true
8 | },
9 | "kernelspec": {
10 | "name": "python3",
11 | "display_name": "Python 3"
12 | },
13 | "language_info": {
14 | "name": "python"
15 | }
16 | },
17 | "cells": [
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {
21 | "id": "view-in-github",
22 | "colab_type": "text"
23 | },
24 | "source": [
25 | "
"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "source": [
31 | "# get the openai secret key\n",
32 | "import pandas\n",
33 | "import getpass\n",
34 | "secret_key = getpass.getpass('Please enter your openai key: ')\n",
35 | "from IPython.display import display, Markdown"
36 | ],
37 | "metadata": {
38 | "id": "c1rYBN0yrbWN"
39 | },
40 | "execution_count": null,
41 | "outputs": []
42 | },
43 | {
44 | "cell_type": "code",
45 | "source": [
46 | "!pip install openai"
47 | ],
48 | "metadata": {
49 | "id": "JuZBdwmro4-B"
50 | },
51 | "execution_count": null,
52 | "outputs": []
53 | },
54 | {
55 | "cell_type": "code",
56 | "source": [
57 | "%pip install alpha_vantage"
58 | ],
59 | "metadata": {
60 | "id": "XX9820Ks7zB1"
61 | },
62 | "execution_count": null,
63 | "outputs": []
64 | },
65 | {
66 | "cell_type": "code",
67 | "source": [
68 | "import requests\n",
69 | "import json\n",
70 | "from IPython.display import display, Markdown\n",
71 | "\n",
72 | "api_key = '6605TQULF0O9MKET'\n",
73 | "ticker = 'DIS' # Example ticker\n",
74 | "\n",
75 | "# Function to fetch financial data\n",
76 | "def fetch_financial_data(function, ticker, api_key):\n",
77 | " url = f\"https://www.alphavantage.co/query?function={function}&symbol={ticker}&apikey={api_key}\"\n",
78 | " response = requests.get(url)\n",
79 | " if response.status_code == 200:\n",
80 | " return response.json()\n",
81 | " else:\n",
82 | " print(f\"Error fetching {function} data.\")\n",
83 | " return None\n",
84 | "\n",
85 | "# Fetch income statement\n",
86 | "income_statement = fetch_financial_data(\"INCOME_STATEMENT\", ticker, api_key)\n",
87 | "\n",
88 | "\n",
89 | "\n",
90 | "# Fetch balance sheet\n",
91 | "balance_sheet = fetch_financial_data(\"BALANCE_SHEET\", ticker, api_key)"
92 | ],
93 | "metadata": {
94 | "id": "u7xujhsgxTDR"
95 | },
96 | "execution_count": null,
97 | "outputs": []
98 | },
99 | {
100 | "cell_type": "code",
101 | "source": [
102 | "# Assuming you've already fetched the balance sheet data as 'balance_sheet'\n",
103 | "quarterly_income_reports = income_statement['quarterlyReports'][:4]\n",
104 | "quarterly_balance_reports = balance_sheet['quarterlyReports'][:4]\n",
105 | "\n",
106 | "formatted_data = {\"financialData\": []}\n",
107 | "\n",
108 | "# Iterate through the income reports and balance reports simultaneously\n",
109 | "for income_report, balance_report in zip(quarterly_income_reports, quarterly_balance_reports):\n",
110 | " # Filter out entries with values \"None\" or an empty string for income statement\n",
111 | " formatted_income_report = {k: v for k, v in income_report.items() if v not in [\"None\", \"\"]}\n",
112 | " # Filter out entries with values \"None\" or an empty string for balance sheet\n",
113 | " formatted_balance_report = {k: v for k, v in balance_report.items() if v not in [\"None\", \"\"]}\n",
114 | " # Combine the data from both reports\n",
115 | " formatted_report = {**formatted_income_report, **formatted_balance_report}\n",
116 | " # Append the combined report to the financialData list\n",
117 | " formatted_data[\"financialData\"].append(formatted_report)\n",
118 | "\n",
119 | "# Convert the combined data to a JSON string\n",
120 | "out_string = json.dumps(formatted_data, indent=4)\n",
121 | "print(out_string)"
122 | ],
123 | "metadata": {
124 | "id": "YcTyS-IhCvmu"
125 | },
126 | "execution_count": null,
127 | "outputs": []
128 | },
129 | {
130 | "cell_type": "code",
131 | "source": [
132 | "from openai import OpenAI\n",
133 | "client = OpenAI(api_key=secret_key)\n",
134 | "\n",
135 | "def get_response(prompt):\n",
136 | " # Create a request to the chat completions endpoint\n",
137 | " response = client.chat.completions.create(\n",
138 | " model=\"gpt-4o\",\n",
139 | " # Assign the role and content for the message\n",
140 | " messages=\n",
141 | " [\n",
142 | " {\n",
143 | " \"role\": \"system\", \"content\": \"\"\"\n",
144 | "\n",
145 | " Role\n",
146 | " -------\n",
147 | " You are a CPA Employed in the Finance Function, tasked with assessing supplier risk.\n",
148 | "\n",
149 | " Task\n",
150 | " ------\n",
151 | " Analyze the provided 4 quarters of financial data.\n",
152 | " Compare the financials between the four quarters, noting any significant movements or trends.\n",
153 | " Evaluate the implications of these movements for the company's financial health and operational efficiency.\n",
154 | "\n",
155 | " From the perspective of a company reciving services from this company highlight key risks in growing sales and getting paid on time strictly from\n",
156 | " what you have gathered from the financials.\n",
157 | "\n",
158 | " Decide whether the team need to investigate this supplier further for risk backed up the financials.\n",
159 | "\n",
160 | " Output\n",
161 | " ------\n",
162 | " Ensure the analysis reflects the specific relevant movements across the quarters,\n",
163 | " highlighting significant changes relevant to their viability as a major supplier.\n",
164 | " The output should strictly adhere to the data provided, avoiding assumptions or inferences not directly supported by the numbers.\n",
165 | " Im printing in markdown in python ensure summary tables readible and numeric text is legible\n",
166 | " Do not include any currency symbols, use 22.5M USD instead of $22.5M, display figures in millions and use thousand seperators where appropriate ie 23,100M\n",
167 | "\n",
168 | "\n",
169 | " Data\n",
170 | " -----\n",
171 | "\n",
172 | " \"\"\"\n",
173 | " },\n",
174 | " {\n",
175 | " \"role\": \"user\", \"content\": prompt\n",
176 | " }\n",
177 | " ],\n",
178 | " temperature = 0.4,\n",
179 | " max_tokens=3000)\n",
180 | " return response.choices[0].message.content"
181 | ],
182 | "metadata": {
183 | "id": "w309wtpY89Vr"
184 | },
185 | "execution_count": null,
186 | "outputs": []
187 | },
188 | {
189 | "cell_type": "code",
190 | "source": [
191 | "output = get_response(out_string)"
192 | ],
193 | "metadata": {
194 | "id": "tIq-E_8eC-f4"
195 | },
196 | "execution_count": 36,
197 | "outputs": []
198 | },
199 | {
200 | "cell_type": "code",
201 | "source": [
202 | "display(Markdown(output))"
203 | ],
204 | "metadata": {
205 | "id": "zLZY5-ZmttQM",
206 | "colab": {
207 | "base_uri": "https://localhost:8080/",
208 | "height": 1000
209 | },
210 | "outputId": "6d60fec8-9b71-4c39-da2d-1db0d6fc2b6b"
211 | },
212 | "execution_count": 37,
213 | "outputs": [
214 | {
215 | "output_type": "display_data",
216 | "data": {
217 | "text/plain": [
218 | ""
219 | ],
220 | "text/markdown": "# Financial Analysis of Supplier\n\n## Quarterly Financial Data Summary\n\nBelow is a summary of the key financial metrics for the supplier over the last four quarters:\n\n| Metric | 2024-03-31 | 2023-12-31 | 2023-09-30 | 2023-06-30 |\n|---------------------------------|------------|------------|------------|------------|\n| **Total Revenue (M)** | 22,083 | 23,549 | 21,241 | 22,330 |\n| **Gross Profit (M)** | 15,910 | 19,415 | 16,069 | 15,461 |\n| **Operating Income (M)** | 3,845 | 3,876 | 2,976 | 3,559 |\n| **Net Income (M)** | -20 | 1,911 | 264 | -460 |\n| **Total Assets (M)** | 195,110 | 197,774 | 205,579 | 203,783 |\n| **Total Liabilities (M)** | 95,858 | 97,053 | 106,302 | 106,173 |\n| **Total Shareholder Equity (M)**| 99,252 | 100,721 | 99,277 | 97,610 |\n| **Cash and Cash Equivalents (M)**| 6,635 | 7,192 | 14,182 | 11,458 |\n| **Current Net Receivables (M)** | 12,026 | 14,115 | 12,330 | 13,112 |\n| **Current Liabilities (M)** | 32,874 | 31,033 | 31,139 | 28,234 |\n| **Current Debt (M)** | 6,789 | 6,087 | 4,367 | 2,645 |\n\n## Analysis of Financial Movements\n\n### Revenue and Profitability\n- **Total Revenue**: There was a noticeable decline from 23,549M in Q4 2023 to 22,083M in Q1 2024. This represents a drop of approximately 6.2%.\n- **Gross Profit**: Gross profit has decreased significantly from 19,415M in Q4 2023 to 15,910M in Q1 2024, a reduction of about 18%.\n- **Operating Income**: Operating income remained relatively stable between Q4 2023 and Q1 2024, with a slight decrease from 3,876M to 3,845M.\n- **Net Income**: Net income fluctuated significantly, with a notable drop from 1,911M in Q4 2023 to a loss of 20M in Q1 2024. This indicates potential issues in managing costs or other non-operating expenses.\n\n### Asset and Liability Management\n- **Total Assets**: Total assets decreased from 205,579M in Q3 2023 to 195,110M in Q1 2024, indicating a reduction in the asset base.\n- **Total Liabilities**: Total liabilities decreased from 106,302M in Q3 2023 to 95,858M in Q1 2024, which is a positive sign of reducing debt or other liabilities.\n- **Total Shareholder Equity**: Shareholder equity has remained relatively stable, with a slight decrease from 100,721M in Q4 2023 to 99,252M in Q1 2024.\n\n### Liquidity and Solvency\n- **Cash and Cash Equivalents**: There was a significant reduction in cash and cash equivalents from 14,182M in Q3 2023 to 6,635M in Q1 2024, which could impact the company's liquidity.\n- **Current Net Receivables**: Current net receivables decreased from 14,115M in Q4 2023 to 12,026M in Q1 2024, indicating potential collection issues or reduced sales.\n- **Current Liabilities**: Current liabilities increased from 28,234M in Q2 2023 to 32,874M in Q1 2024, which could strain the company's short-term financial health.\n- **Current Debt**: Current debt increased from 2,645M in Q2 2023 to 6,789M in Q1 2024, indicating higher short-term borrowing.\n\n## Key Risks and Recommendations\n\n### Risks\n1. **Revenue Decline**: The decline in total revenue and gross profit indicates potential challenges in maintaining sales growth or managing cost of goods sold.\n2. **Net Income Volatility**: The significant fluctuations in net income, including a loss in Q1 2024, raise concerns about the company's profitability and cost management.\n3. **Liquidity Concerns**: The substantial reduction in cash and cash equivalents could impact the company's ability to meet short-term obligations and invest in growth opportunities.\n4. **Increased Current Liabilities and Debt**: The increase in current liabilities and short-term debt could strain the company's liquidity and operational efficiency.\n\n### Recommendations\n- **Further Investigation**: Given the significant movements in revenue, profitability, and liquidity metrics, it is recommended to investigate this supplier further to assess the underlying causes and potential risks.\n- **Monitor Receivables**: Closely monitor the supplier's receivables and payment patterns to ensure timely payments and reduce the risk of bad debts.\n- **Evaluate Financial Stability**: Assess the supplier's financial stability and ability to meet short-term obligations, considering the increase in current liabilities and debt.\n\nBased on the analysis, further investigation into the supplier's financial health and operational efficiency is warranted to mitigate potential risks in growing sales and ensuring timely payments."
221 | },
222 | "metadata": {}
223 | }
224 | ]
225 | }
226 | ]
227 | }
--------------------------------------------------------------------------------
/Gemini_Functions.js:
--------------------------------------------------------------------------------
1 |
2 | // appsscript.json
3 |
4 | {
5 | "timeZone": "Europe/Dublin",
6 | "dependencies": {
7 | },
8 | "exceptionLogging": "STACKDRIVER",
9 | "oauthScopes": [
10 | "https://www.googleapis.com/auth/spreadsheets.currentonly",
11 | "https://www.googleapis.com/auth/script.external_request",
12 | "https://www.googleapis.com/auth/cloud-platform"
13 | ],
14 | "runtimeVersion": "V8"
15 | }
16 |
17 |
18 |
19 |
20 |
21 |
22 | // code.js
23 |
24 |
25 |
26 | project = 'REPLACE WITH PROJECT ID';
27 |
28 | function auth() {
29 | cache = CacheService.getUserCache();
30 | token = ScriptApp.getOAuthToken();
31 | cache.put("token", token);
32 | }
33 |
34 | function askGemini(inputText) {
35 | cache = CacheService.getUserCache();
36 | token = cache.get("token");
37 | if (token == "") return "ERROR";
38 | Logger.log(`Token = ${token}`);
39 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.0-pro:generateContent`
40 | data = {
41 | contents: {
42 | role: "USER",
43 | parts: { "text": inputText }
44 | },
45 | generation_config: {
46 | temperature: 0.3,
47 | topP: 1,
48 | maxOutputTokens: 256
49 | }
50 | }
51 | const options = {
52 | method: "post",
53 | contentType: 'application/json',
54 | headers: {
55 | Authorization: `Bearer ${token}`,
56 | },
57 | payload: JSON.stringify(data)
58 | };
59 |
60 | const response = UrlFetchApp.fetch(url, options);
61 | if (response.getResponseCode() == 200) {
62 | json = JSON.parse(response.getContentText());
63 | answer = json.candidates[0].content.parts[0].text;
64 | return answer;
65 | }
66 | return "ERROR";
67 | }
68 |
69 |
70 | function translate(inputText) {
71 | cache = CacheService.getUserCache();
72 | token = cache.get("token");
73 | if (token == "") return "ERROR";
74 | Logger.log(`Token = ${token}`);
75 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.0-pro:generateContent`
76 | textWithInstruction = "translate to Spanish: " + inputText;
77 | data = {
78 | contents: {
79 | role: "USER",
80 | parts: { "text": textWithInstruction }
81 | },
82 | generation_config: {
83 | temperature: 0.3,
84 | topP: 1,
85 | maxOutputTokens: 256
86 | }
87 | }
88 | const options = {
89 | method: "post",
90 | contentType: 'application/json',
91 | headers: {
92 | Authorization: `Bearer ${token}`,
93 | },
94 | payload: JSON.stringify(data)
95 | };
96 |
97 | const response = UrlFetchApp.fetch(url, options);
98 | if (response.getResponseCode() == 200) {
99 | json = JSON.parse(response.getContentText());
100 | answer = json.candidates[0].content.parts[0].text;
101 | return answer;
102 | }
103 | return "ERROR";
104 | }
105 |
106 |
107 | function report(inputText) {
108 | cache = CacheService.getUserCache();
109 | token = cache.get("token");
110 | if (token == "") return "ERROR";
111 | Logger.log(`Token = ${token}`);
112 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.0-pro:generateContent`
113 | prompt = "Role: You are a financial analyst and you are required to summarise the key insights of given numerical tables.Task: Step 1: List important, but no more than five, highlights from the figures provided in the given table.Step 2: Write a paragraph about the main movers of net income comparing each year from the figures provided. (For a dataset with three years of figures compare Year 1 to Year 2 and Year 2 to Year 3) Further Instructions: Please write in a professional and business-neutral tone similar to the financial times.The summary should only be based on the information presented in the table and only contain facts form that table."
114 | text = prompt + inputText;
115 | data = {
116 | contents: {
117 | role: "USER",
118 | parts: { "text": text }
119 | },
120 | generation_config: {
121 | temperature: 0.3,
122 | topP: 1,
123 | maxOutputTokens: 2000
124 | }
125 | }
126 | const options = {
127 | method: "post",
128 | contentType: 'application/json',
129 | headers: {
130 | Authorization: `Bearer ${token}`,
131 | },
132 | payload: JSON.stringify(data)
133 | };
134 |
135 | const response = UrlFetchApp.fetch(url, options);
136 | if (response.getResponseCode() == 200) {
137 | json = JSON.parse(response.getContentText());
138 | answer = json.candidates[0].content.parts[0].text;
139 | return answer;
140 | }
141 | return "ERROR";
142 | }
143 |
144 | function reported(cellRange) {
145 | const sheet = SpreadsheetApp.getActiveSheet();
146 | // Get the values from the cell range
147 | const values = sheet.getRange(cellRange).getValues();
148 |
149 | // Check if the range is empty or doesn't have headers
150 | if (!values || !values.length || !values[0].length) {
151 | return "ERROR: Empty range or missing headers";
152 | }
153 |
154 | // Build the markdown table header row
155 | let markdownTable = "|";
156 | for (const header of values[0]) {
157 | markdownTable += ` ${header} |`;
158 | }
159 | markdownTable += "\n";
160 |
161 | // Add a separator line
162 | markdownTable += "|";
163 | for (let i = 0; i < values[0].length; i++) {
164 | markdownTable += " --- |";
165 | }
166 | markdownTable += "\n";
167 |
168 | // Build the remaining rows
169 | for (let i = 1; i < values.length; i++) {
170 | markdownTable += "|";
171 | for (const value of values[i]) {
172 | markdownTable += ` ${value} |`;
173 | }
174 | markdownTable += "\n";
175 | }
176 |
177 | // Call the original translate function with the markdown table
178 | return report(markdownTable);
179 | }
180 |
--------------------------------------------------------------------------------
/GettingandSettingData.js:
--------------------------------------------------------------------------------
1 | function onOpen() {
2 | var ui = SpreadsheetApp.getUi();
3 | ui.createMenu('Custom Functions')
4 | .addItem('Copy Sheet to New Sheet', 'copyToSheet')
5 | .addItem('Copy Selection to New Sheet', 'copySelectionToSheet')
6 | .addItem('Copy Sheet from Other Sheet', 'copyFromOther')
7 | .addToUi();
8 | }
9 |
10 | function copySelectionToSheet(){
11 | const ss = SpreadsheetApp.getActiveSpreadsheet();
12 | const sheet = ss.getActiveSheet();
13 | const range = sheet.getActiveRange();
14 | const data = range.getValues();
15 | const newSheet = ss.insertSheet();
16 | const newRange = newSheet.getRange(1,1,range.getNumRows(),range.getNumColumns());
17 | newRange.setValues(data);
18 | }
19 |
20 | function copyToSheet(){
21 | const ss = SpreadsheetApp.getActiveSpreadsheet();
22 | const sheet = ss.getSheets()[0];
23 | const lastColumn = sheet.getLastColumn();
24 | const lastRow = sheet.getLastRow();
25 | const range = sheet.getRange(1, 1, lastRow, lastColumn); // Define Full Range with Last Row and Last Column
26 | let data = range.getValues(); // Get all values
27 | Logger.log(data);
28 | for (var i = 1; i < data.length; i++) {
29 | if (data[i][6] == 'ARS') {
30 | data[i][6] = 'Argentine Peso';
31 | }
32 | }
33 | const newSheet = ss.insertSheet();
34 | const newRange = newSheet.getRange(1,1,range.getNumRows(),range.getNumColumns());
35 | newRange.setValues(data);
36 | }
37 |
38 | function copyFromOther(){
39 | const id = "1eVS5x6R70XPbqMfBPfyzktdg2AeVTrPMnkfj1gXTHS4";
40 | const s = SpreadsheetApp.openById(id);
41 | const sheet = s.getSheets()[0];
42 | const lastColumn = sheet.getLastColumn();
43 | const lastRow = sheet.getLastRow();
44 | const range = sheet.getRange(1, 1, lastRow, lastColumn); // Define Full Range with Last Row and Last Column
45 | const data = range.getValues(); // Get all values
46 | Logger.log(data);
47 | const ss = SpreadsheetApp.getActiveSpreadsheet();
48 | const newSheet = ss.insertSheet();
49 | const newRange = newSheet.getRange(1,1,range.getNumRows(),range.getNumColumns());
50 | newRange.setValues(data);
51 | }
52 |
--------------------------------------------------------------------------------
/Intro.js:
--------------------------------------------------------------------------------
1 | function onOpen() {
2 | var ui = SpreadsheetApp.getUi();
3 | ui.createMenu('Adams Menu')
4 | .addItem('Insert the date', 'insertDate')
5 | .addToUi();
6 | }
7 |
8 | function insertDate() {
9 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
10 | var cell = sheet.getRange('B2');
11 | cell.setValue(new Date());
12 | }
13 |
--------------------------------------------------------------------------------
/Ranges.js:
--------------------------------------------------------------------------------
1 | function logDataRange() {
2 | let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
3 | let range = sheet.getRange(1,1,11,5);
4 | let data = range.getValues();
5 |
6 | Logger.log(data)
7 | Logger.log(data[1][0]);
8 | }
9 |
10 | function updatePrices(){
11 | let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
12 | let range = sheet.getRange(1,1,11,5);
13 | let data = range.getValues();
14 |
15 | for(let i = 1; i < data.length; i++){
16 | data[i][2] = data[i][2] * 1.5;
17 | data[i][0] = "[New Price] " + data[i][0];
18 | }
19 |
20 | let dataRange2 = sheet.getRange(14,1,11,5);
21 | dataRange2.setValues(data);
22 | Logger.log("Successful Run!")
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Sheets to Slides.js:
--------------------------------------------------------------------------------
1 | function transferSpecificChartsToFirstSlide() {
2 | // Open the Google Slides deck where you want to transfer the charts
3 | var slidesDeck = SlidesApp.openById("1Ewx4_F84z4BIciNeOg3HpMMRAUs2NHz9_jG4Qp-NIeY");
4 |
5 | // Open the active Google Sheet
6 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
7 |
8 | // Get all the charts in the sheet
9 | var charts = sheet.getCharts();
10 |
11 | // Ensure there are enough charts
12 | if (charts.length < 4) {
13 | SpreadsheetApp.getUi().alert('Not enough charts found in the sheet.');
14 | return;
15 | }
16 |
17 | // Get the first slide
18 | var slide = slidesDeck.getSlides()[0];
19 |
20 | // Clear existing images on the slide except the header icon
21 | var elements = slide.getPageElements();
22 | elements.forEach(function(element) {
23 | if (element.getPageElementType() === SlidesApp.PageElementType.IMAGE) {
24 | var position = element.getLeft();
25 | var size = element.getWidth();
26 |
27 | // Assuming the header icon is smaller and positioned at the top, we keep it
28 | if (position > 100 || size > 100) { // Adjust these thresholds based on your actual icon's position and size
29 | element.remove();
30 | }
31 | }
32 | });
33 |
34 | // Define which chart goes where and its exact size on the slide
35 | var chartMappings = [
36 | { chartIndex: 2, left: 50, top: 100, width: 150, height: 100 }, // -> Top left
37 | { chartIndex: 1, left: 350, top: 100, width: 150, height: 100 }, // -> Top right
38 | { chartIndex: 0, left: 50, top: 200, width: 250, height: 150 }, // -> Bottom left
39 | { chartIndex: 3, left: 350, top: 200, width: 250, height: 150 } // -> Bottom right
40 | ];
41 |
42 | /**
43 | * This code iterates over each chart mapping in the chartMappings array. For each mapping, it:
44 | Retrieves the corresponding chart from the charts array.
45 | Converts the chart into a PNG image.
46 | Inserts the image onto the slide at a specific position (left, top) and size (width, height) as defined in the chartMappings array.
47 | The result is that each chart is placed on the slide exactly where and how you want it, based on the parameters defined in chartMappings.
48 | */
49 |
50 | chartMappings.forEach(function(mapping) {
51 | var chart = charts[mapping.chartIndex];
52 | var chartImage = chart.getAs('image/png');
53 |
54 | // Insert the chart image into the slide at the specified position and size
55 | slide.insertImage(chartImage)
56 | .setLeft(mapping.left)
57 | .setTop(mapping.top)
58 | .setWidth(mapping.width)
59 | .setHeight(mapping.height);
60 | });
61 |
62 | // Show a confirmation message
63 | SpreadsheetApp.getUi().alert('Charts Transferred to Slide 1');
64 | }
65 |
--------------------------------------------------------------------------------
/YOuTube Comment Code with Likes.js:
--------------------------------------------------------------------------------
1 | function getYouTubeComments() {
2 | const apiKey = 'XXXXXX'; // Replace with your API key
3 | const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
4 |
5 | // Read and trim the video ID from cell B3
6 | let videoId = sheet.getRange('B3').getValue().trim(); // Remove any whitespace and ensure no extra characters
7 |
8 | // Ensure videoId does not contain any extra characters like backticks or quotes
9 | videoId = videoId.replace(/[`'"]/g, ""); // Removes any backticks, single or double quotes if present
10 |
11 | const maxResults = 100; // Number of comments to fetch in one API call (max 100)
12 | let nextPageToken = ''; // Used for pagination
13 | let comments = [];
14 |
15 | // Clear content from row 5 downwards
16 | sheet.getRange('4:5000').clearContent(); // Adjust the range as needed
17 |
18 | do {
19 | // Construct the API URL
20 | let url = `https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&videoId=${videoId}&maxResults=${maxResults}&key=${apiKey}&pageToken=${nextPageToken}`;
21 |
22 | // Fetch the data from YouTube API
23 | let response = UrlFetchApp.fetch(url);
24 | let result = JSON.parse(response.getContentText());
25 |
26 | // Extract comments and push to the array
27 | result.items.forEach(item => {
28 | let comment = item.snippet.topLevelComment.snippet.textDisplay;
29 | let author = item.snippet.topLevelComment.snippet.authorDisplayName;
30 | let publishedAt = item.snippet.topLevelComment.snippet.publishedAt;
31 | let likeCount = item.snippet.topLevelComment.snippet.likeCount;
32 | comments.push([author, comment, publishedAt,likeCount]);
33 | });
34 |
35 | // Set nextPageToken for pagination
36 | nextPageToken = result.nextPageToken;
37 |
38 | } while (nextPageToken);
39 |
40 | // Log the comments or insert into Google Sheets
41 | Logger.log(comments);
42 | insertCommentsToSheet(comments);
43 | }
44 |
45 | // Helper function to insert comments into Google Sheets
46 | function insertCommentsToSheet(comments) {
47 | const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
48 |
49 | // Insert header at row 5 and apply formatting
50 | const headerRange = sheet.getRange(5, 1, 1, 4);
51 | headerRange.setValues([['Author', 'Comment', 'Published At','Like Count']]);
52 | headerRange.setFontWeight('bold'); // Make headers bold
53 | headerRange.setBackground('#f0f0f0'); // Light grey background for headers
54 | headerRange.setHorizontalAlignment('center'); // Center align headers
55 |
56 | // Insert comments starting from row 5
57 | if (comments.length > 0) {
58 | sheet.getRange(6, 1, comments.length, comments[0].length).setValues(comments);
59 | }
60 | }
61 |
62 | function auth() {
63 | cache = CacheService.getUserCache();
64 | token = ScriptApp.getOAuthToken();
65 | cache.put("token", token);
66 | }
67 |
68 | function formatReport() {
69 | auth();
70 | project = 'bigquerycourse-onetwothree';
71 | var ss = SpreadsheetApp.getActiveSpreadsheet();
72 | var aggSheet = ss.getSheetByName('Comments');
73 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
74 | reportSheet.clear();
75 |
76 | var aggData = aggSheet.getDataRange().getValues();
77 |
78 | cache = CacheService.getUserCache();
79 | token = cache.get("token");
80 | if (token == "") return "ERROR";
81 | Logger.log(`Token = ${token}`);
82 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-pro:generateContent`
83 | data = {
84 | contents: {
85 | role: "USER",
86 | parts: { "text": "I will provide you with YouTube Comments assess the sentiment in 5 sections. 1. Overall Sentiment of the video with positive, negative and neutral proportions stated along with 100 word overall summary. 2: Postive Sentiment - Summarise the positive sentiment and provide three reference tweets. Following the same pattern for Negative and Neutral in 3 and 4. 5: Suggestions for video improvement - Provide three key point to improve the next video we make based on the comments." + ' Do not include the video title. Do not create seperate sections for the reference tweets. Only reffer to the data provided and only include title text in title fonts . Full Data:' + aggData}
87 | },
88 | generation_config: {
89 | temperature: 1,
90 | topP: 1,
91 | maxOutputTokens: 1000
92 | }
93 | }
94 | const options = {
95 | method: "post",
96 | contentType: 'application/json',
97 | headers: {
98 | Authorization: `Bearer ${token}`,
99 | },
100 | payload: JSON.stringify(data)
101 | };
102 |
103 | const response = UrlFetchApp.fetch(url, options);
104 | if (response.getResponseCode() == 200) {
105 | json = JSON.parse(response.getContentText());
106 | answer = json.candidates[0].content.parts[0].text;
107 | Logger.log(answer);
108 |
109 | // Format the Markdown in Sheets
110 | var lines = answer.split('\n');
111 | var rowIndex = 1;
112 |
113 | lines.forEach(function(line) {
114 | var cell = reportSheet.getRange(rowIndex, 1);
115 | var text = line.trim();
116 |
117 | if (text.startsWith('### ')) {
118 | // Handle H3 titles
119 | text = text.replace('### ', '');
120 | cell.setValue(text)
121 | .setFontWeight('bold')
122 | .setFontSize(14)
123 | .setBackground('#1c4587') // Dark Blue 3 background
124 | .setFontColor('#FFFFFF'); // Set text color to white
125 | } else if (text.startsWith('## ')) {
126 | // Handle H2 titles
127 | text = text.replace('## ', '');
128 | cell.setValue(text)
129 | .setFontWeight('bold')
130 | .setFontSize(16)
131 | .setBackground('#1c4587') // Dark Blue 3 background
132 | .setFontColor('#FFFFFF'); // Set text color to white
133 | } else if (text.startsWith('* ')) {
134 | // Handle bullet points
135 | text = text.replace('* ', '');
136 | text = text.replace(/\*\*/g, ''); // Remove all '**'
137 | cell.setValue(text);
138 | } else if (text.startsWith('**')) {
139 | // Handle bold text
140 | text = text.replace(/\*\*/g, ''); // Remove all '**'
141 | cell.setValue(text)
142 | .setFontWeight('bold')
143 | .setFontSize(12)
144 | .setBackground('#1c4587') // Dark Blue 3 background
145 | .setFontColor('#FFFFFF'); // Set text color to white
146 | } else if (text.startsWith('# ')) {
147 | // Handle H1 titles
148 | text = text.replace('# ', '');
149 | cell.setValue(text)
150 | .setFontWeight('bold')
151 | .setFontSize(18)
152 | .setBackground('#CFE2F3');
153 | } else {
154 | // Handle regular text with potential bold sections
155 | var richTextBuilder = SpreadsheetApp.newRichTextValue().setText(text);
156 | var regex = /\*\*(.*?)\*\*/g;
157 | var match;
158 | var lastIndex = 0;
159 |
160 | while ((match = regex.exec(text)) !== null) {
161 | richTextBuilder.setTextStyle(match.index, match.index + match[1].length,
162 | SpreadsheetApp.newTextStyle().setBold(true).build());
163 | lastIndex = match.index + match[0].length;
164 | }
165 |
166 | // Remove markdown bold indicators
167 | text = text.replace(/\*\*/g, '');
168 | richTextBuilder.setText(text);
169 |
170 | cell.setRichTextValue(richTextBuilder.build());
171 | }
172 | rowIndex++;
173 | });
174 |
175 | // Set column width and enable text wrapping
176 | reportSheet.setColumnWidth(1, 1250);
177 | reportSheet.getRange(1, 1, rowIndex, 1).setWrap(true);
178 |
179 | // Add some indent to the left edge of the page
180 | reportSheet.insertColumnBefore(1);
181 | reportSheet.setColumnWidth(1, 50); // Add a narrow column for left margin
182 |
183 | // Autofit rows
184 | reportSheet.autoResizeRows(1, rowIndex);
185 | return answer;
186 | }
187 | Logger.log("ERROR");
188 | }
189 |
--------------------------------------------------------------------------------
/YouTube_Comments.js:
--------------------------------------------------------------------------------
1 | //"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/documents"]
2 |
3 | function getYouTubeComments() {
4 | const apiKey = 'ENTER YOUTUBE API KEY'; // Replace with your API key
5 | const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
6 |
7 | // Read and trim the video ID from cell B3
8 | let videoId = sheet.getRange('B3').getValue().trim(); // Remove any whitespace and ensure no extra characters
9 |
10 | // Ensure videoId does not contain any extra characters like backticks or quotes
11 | videoId = videoId.replace(/[`'"]/g, ""); // Removes any backticks, single or double quotes if present
12 |
13 | const maxResults = 100; // Number of comments to fetch in one API call (max 100)
14 | let nextPageToken = ''; // Used for pagination
15 | let comments = [];
16 |
17 | // Clear content from row 5 downwards
18 | sheet.getRange('4:5000').clearContent(); // Adjust the range as needed
19 |
20 | do {
21 | // Construct the API URL
22 | let url = `https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&videoId=${videoId}&maxResults=${maxResults}&key=${apiKey}&pageToken=${nextPageToken}`;
23 |
24 | // Fetch the data from YouTube API
25 | let response = UrlFetchApp.fetch(url);
26 | let result = JSON.parse(response.getContentText());
27 |
28 | // Extract comments and push to the array
29 | result.items.forEach(item => {
30 | let comment = item.snippet.topLevelComment.snippet.textDisplay;
31 | let author = item.snippet.topLevelComment.snippet.authorDisplayName;
32 | let publishedAt = item.snippet.topLevelComment.snippet.publishedAt;
33 | comments.push([author, comment, publishedAt]);
34 | });
35 |
36 | // Set nextPageToken for pagination
37 | nextPageToken = result.nextPageToken;
38 |
39 | } while (nextPageToken);
40 |
41 | // Log the comments or insert into Google Sheets
42 | Logger.log(comments);
43 | insertCommentsToSheet(comments);
44 | }
45 |
46 | // Helper function to insert comments into Google Sheets
47 | function insertCommentsToSheet(comments) {
48 | const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
49 |
50 | // Insert header at row 5 and apply formatting
51 | const headerRange = sheet.getRange(5, 1, 1, 3);
52 | headerRange.setValues([['Author', 'Comment', 'Published At']]);
53 | headerRange.setFontWeight('bold'); // Make headers bold
54 | headerRange.setBackground('#f0f0f0'); // Light grey background for headers
55 | headerRange.setHorizontalAlignment('center'); // Center align headers
56 |
57 | // Insert comments starting from row 5
58 | if (comments.length > 0) {
59 | sheet.getRange(6, 1, comments.length, comments[0].length).setValues(comments);
60 | }
61 | }
62 |
63 | function auth() {
64 | cache = CacheService.getUserCache();
65 | token = ScriptApp.getOAuthToken();
66 | cache.put("token", token);
67 | }
68 |
69 | function formatReport() {
70 | auth();
71 | project = 'ENTER PROJECT';
72 | var ss = SpreadsheetApp.getActiveSpreadsheet();
73 | var aggSheet = ss.getSheetByName('Comments');
74 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
75 | reportSheet.clear();
76 |
77 | var aggData = aggSheet.getDataRange().getValues();
78 |
79 | cache = CacheService.getUserCache();
80 | token = cache.get("token");
81 | if (token == "") return "ERROR";
82 | Logger.log(`Token = ${token}`);
83 | url = `https://us-central1-aiplatform.googleapis.com/v1/projects/${project}/locations/us-central1/publishers/google/models/gemini-1.5-flash:generateContent`
84 | data = {
85 | contents: {
86 | role: "USER",
87 | parts: { "text": "I will provide you with YouTube Comments assess the sentiment in 5 sections. 1. Overall Sentiment of the video with positive, negative and neutral proportions stated along with 100 word overall summary. 2: Postive Sentiment - Summarise the positive sentiment and provide three reference tweets. Following the same pattern for Negative and Neutral in 3 and 4. 5: Suggestions for video improvement - Provide three key point to improve the next video we make based on the comments." + ' Do not include the video title. Do not create seperate sections for the reference tweets. Only reffer to the data provided. Full Data:' + aggData}
88 | },
89 | generation_config: {
90 | temperature: 1,
91 | topP: 1,
92 | maxOutputTokens: 1000
93 | }
94 | }
95 | const options = {
96 | method: "post",
97 | contentType: 'application/json',
98 | headers: {
99 | Authorization: `Bearer ${token}`,
100 | },
101 | payload: JSON.stringify(data)
102 | };
103 |
104 | const response = UrlFetchApp.fetch(url, options);
105 | if (response.getResponseCode() == 200) {
106 | json = JSON.parse(response.getContentText());
107 | answer = json.candidates[0].content.parts[0].text;
108 | Logger.log(answer);
109 |
110 | // Format the Markdown in Sheets
111 | var lines = answer.split('\n');
112 | var rowIndex = 1;
113 |
114 | lines.forEach(function(line) {
115 | var cell = reportSheet.getRange(rowIndex, 1);
116 | var text = line.trim();
117 |
118 | if (text.startsWith('### ')) {
119 | // Handle H3 titles
120 | text = text.replace('### ', '');
121 | cell.setValue(text)
122 | .setFontWeight('bold')
123 | .setFontSize(14)
124 | .setBackground('#1c4587') // Dark Blue 3 background
125 | .setFontColor('#FFFFFF'); // Set text color to white
126 | } else if (text.startsWith('## ')) {
127 | // Handle H2 titles
128 | text = text.replace('## ', '');
129 | cell.setValue(text)
130 | .setFontWeight('bold')
131 | .setFontSize(16)
132 | .setBackground('#1c4587') // Dark Blue 3 background
133 | .setFontColor('#FFFFFF'); // Set text color to white
134 | } else if (text.startsWith('* ')) {
135 | // Handle bullet points
136 | text = text.replace('* ', '');
137 | text = text.replace(/\*\*/g, ''); // Remove all '**'
138 | cell.setValue(text);
139 | } else if (text.startsWith('**')) {
140 | // Handle bold text
141 | text = text.replace(/\*\*/g, ''); // Remove all '**'
142 | cell.setValue(text)
143 | .setFontWeight('bold')
144 | .setFontSize(12)
145 | .setBackground('#1c4587') // Dark Blue 3 background
146 | .setFontColor('#FFFFFF'); // Set text color to white
147 | } else if (text.startsWith('# ')) {
148 | // Handle H1 titles
149 | text = text.replace('# ', '');
150 | cell.setValue(text)
151 | .setFontWeight('bold')
152 | .setFontSize(18)
153 | .setBackground('#CFE2F3');
154 | } else {
155 | // Handle regular text with potential bold sections
156 | var richTextBuilder = SpreadsheetApp.newRichTextValue().setText(text);
157 | var regex = /\*\*(.*?)\*\*/g;
158 | var match;
159 | var lastIndex = 0;
160 |
161 | while ((match = regex.exec(text)) !== null) {
162 | richTextBuilder.setTextStyle(match.index, match.index + match[1].length,
163 | SpreadsheetApp.newTextStyle().setBold(true).build());
164 | lastIndex = match.index + match[0].length;
165 | }
166 |
167 | // Remove markdown bold indicators
168 | text = text.replace(/\*\*/g, '');
169 | richTextBuilder.setText(text);
170 |
171 | cell.setRichTextValue(richTextBuilder.build());
172 | }
173 | rowIndex++;
174 | });
175 |
176 | // Set column width and enable text wrapping
177 | reportSheet.setColumnWidth(1, 1250);
178 | reportSheet.getRange(1, 1, rowIndex, 1).setWrap(true);
179 |
180 | // Add some indent to the left edge of the page
181 | reportSheet.insertColumnBefore(1);
182 | reportSheet.setColumnWidth(1, 50); // Add a narrow column for left margin
183 |
184 | // Autofit rows
185 | reportSheet.autoResizeRows(1, rowIndex);
186 | return answer;
187 | }
188 | Logger.log("ERROR");
189 | }
190 |
191 |
--------------------------------------------------------------------------------
/automatereporting.js:
--------------------------------------------------------------------------------
1 | function fetchBigQueryData() {
2 | var projectId = 'YOURPROJECTHERE';
3 | var query = 'SELECT unique_key,CAST(created_date AS DATE) AS created_date, status, status_notes, agency_name, category, complaint_type, source FROM `bigquery-public-data.san_francisco_311.311_service_requests` Where extract(year from created_date) = 2024 LIMIT 100;'
4 |
5 | //'SELECT * FROM `bigquery-public-data.san_francisco_311.311_service_requests` where agency_name = "Muni Feedback Received Queue" LIMIT 100';
6 |
7 | var request = {
8 | query: query,
9 | useLegacySql: false
10 | };
11 |
12 | var queryResults = BigQuery.Jobs.query(request, projectId);
13 | var jobId = queryResults.jobReference.jobId;
14 |
15 | var results = BigQuery.Jobs.getQueryResults(projectId, jobId);
16 | var rows = results.rows;
17 |
18 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RawData');
19 | if (!sheet) {
20 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('RawData');
21 | } else {
22 | sheet.clear();
23 | }
24 |
25 | // Set headers
26 | var headers = results.schema.fields.map(field => field.name);
27 | sheet.appendRow(headers);
28 | var headerRange = sheet.getRange(1, 1, 1, headers.length);
29 | headerRange.setBackground('#000000').setFontColor('#FFFFFF').setFontWeight('bold');
30 |
31 |
32 | // Set Rows
33 | for (var i = 0; i < rows.length; i++) {
34 | var row = rows[i].f.map(cell => cell.v);
35 | sheet.appendRow(row);
36 | }
37 | }
38 |
39 | function formatReport() {
40 | var ss = SpreadsheetApp.getActiveSpreadsheet();
41 | var rawSheet = ss.getSheetByName('RawData');
42 | var reportSheet = ss.getSheetByName('Report') || ss.insertSheet('Report');
43 | reportSheet.clear();
44 |
45 | var rawData = rawSheet.getDataRange().getValues();
46 |
47 | // Adding KPI boxes
48 | var data = rawData.slice(1);
49 | var totalRequests = data.length;
50 | var closedRequests = data.filter(row => row[2] === 'Closed').length;
51 | var openRequests = totalRequests - closedRequests;
52 |
53 | // Adding KPIs with professional styling
54 | var kpiHeaders = [['Report Metrics']];
55 | var kpis = [
56 | ['Total Requests', totalRequests],
57 | ['Closed Requests', closedRequests],
58 | ['Open Requests', openRequests]
59 | ];
60 |
61 | // Append KPI headers
62 | reportSheet.getRange(1, 1, 1, 1).setValues(kpiHeaders);
63 | var headerRange = reportSheet.getRange(1, 1, 1, 1);
64 | headerRange.setBackground('#26428b').setFontColor('#FFFFFF').setFontWeight('bold').setFontSize(14);
65 | headerRange.mergeAcross();
66 |
67 | // Append KPI values
68 | reportSheet.getRange(2, 1, kpis.length, 2).setValues(kpis);
69 | var kpiRange = reportSheet.getRange(2, 1, kpis.length, 2);
70 | kpiRange.setBackground('#f1f1f1').setFontColor('#000000').setFontWeight('bold').setFontSize(12);
71 |
72 | // Adding some space before the table
73 | reportSheet.appendRow([' ']);
74 | // Add data refreshed timestamp
75 | reportSheet.appendRow(['']);
76 | var timestamp = new Date();
77 | reportSheet.appendRow(['Data refreshed on:', timestamp]);
78 | var timestampRange = reportSheet.getRange(reportSheet.getLastRow(), 1, 1, 2);
79 | timestampRange.setBackground('#f1f1f1').setFontColor('#000000').setFontWeight('bold');
80 | reportSheet.appendRow(['Open Requests']);
81 |
82 | // Filter specific columns to include in the detailed table
83 | // Specify the columns you want to include by their indices
84 | // Filter rows based on a specific column value (e.g., only open requests)
85 | var filteredRows = data.filter(row => row[2] === 'Open');
86 |
87 | // Specify the columns you want to include by their indices
88 | var columnsToInclude = [1, 2, 4, 5, 6];
89 |
90 | var filteredHeaders = columnsToInclude.map(index => rawData[0][index]);
91 | var filteredData = filteredRows.map(row => columnsToInclude.map(index => row[index]));
92 |
93 | // Append filtered data headers and rows
94 | reportSheet.appendRow(filteredHeaders);
95 | reportSheet.getRange(reportSheet.getLastRow() + 1, 1, filteredData.length, filteredData[0].length).setValues(filteredData);
96 |
97 | // Styling the detailed report header
98 | var detailHeaderRange = reportSheet.getRange(reportSheet.getLastRow() - filteredData.length - 1, 1, 1, filteredData[0].length);
99 | detailHeaderRange.setBackground('#26428b').setFontColor('#FFFFFF').setFontWeight('bold');
100 |
101 | // Delete "Sheet1" if it exists
102 | var sheetToDelete = ss.getSheetByName('Sheet1');
103 | if (sheetToDelete) {
104 | ss.deleteSheet(sheetToDelete);
105 | }
106 | }
107 |
108 | function onOpen() {
109 | var ui = SpreadsheetApp.getUi();
110 | ui.createMenu('Custom Menu')
111 | .addItem('Refresh Data', 'generateReport')
112 | .addToUi();
113 | }
114 |
115 | function generateReport() {
116 | fetchBigQueryData();
117 | formatReport();
118 | }
119 |
--------------------------------------------------------------------------------