├── 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 | "\"Open" 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 | --------------------------------------------------------------------------------