{"id":6246,"date":"2024-01-24T11:09:19","date_gmt":"2024-01-24T18:09:19","guid":{"rendered":"https:\/\/jeremywhittaker.com\/?p=6246"},"modified":"2024-01-24T17:39:40","modified_gmt":"2024-01-25T00:39:40","slug":"automating-1099-statement-from-treasurydirect-gov","status":"publish","type":"post","link":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/","title":{"rendered":"Automating 1099 statements from TreasuryDirect.gov"},"content":{"rendered":"\n<p>Ok, so <a href=\"https:\/\/new.jeremywhittaker.com\/index.php\/2022\/10\/13\/only-15-days-left-for-9-62-i-bonds\/\">Ibonds had a huge rate of return last year<\/a>. Perhaps you&#8217;re like me and you created.. a few hundred accounts. Well, now you have a major problem. You need to get your 1099 statement from each account. With the TreasuryDirect.gov OTP (one-time password) process this can be very time-consuming. Below is my treasury direct Python script. It is now modified to automatically download all of your 1099 documents. <br><br>You can see below this script will generate PDFs of all of your 1099 files. <br><br><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"636\" height=\"749\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\" alt=\"\" class=\"wp-image-6249 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png 636w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3-255x300.png 255w\" data-sizes=\"(max-width: 636px) 100vw, 636px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 636px; --smush-placeholder-aspect-ratio: 636\/749;\" \/><\/figure>\n\n\n\n<p>This script requires an input file that contains a column labeled &#8216;Treasury Direct&#8217; Every value under this column should be your Treasury Direct account numbers. You can read more about all the intricacies of this code from my previous post, <a href=\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/04\/26\/automating-the-management-of-hundreds-of-treasurydirect-gov-accounts\/\">automating multiple treasurydirect.gov accounts. <\/a><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">#main.py<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>from treasury_direct import process_account, close_chrome\nimport pandas as pd\n\n\ndef get_account_data(force_update_all=True):\n    for index, row in df.iterrows():\n        account_number = row&#91;'Treasury Direct']\n        print(f'Working on account number {account_number}')\n\n        # Skip empty or NaN account numbers\n        if pd.isna(account_number) or account_number == '':\n            print('Skipping empty account number.')\n            continue\n\n        # Check if the row is already complete\n        if force_update_all == False:\n            if not pd.isna(row&#91;'LLC Name']) and not pd.isna(row&#91;'Original Purchase Amount']) \\\n                    and not pd.isna(row&#91;'Current Value']) and not pd.isna(row&#91;'Issue Date']) \\\n                    and not pd.isna(row&#91;'Interest Rate']):\n                print(f'Skipping account number {row&#91;\"Treasury Direct\"]} as it is already complete.')\n                continue\n            success = process_account(account_number, df, index, url, force_update_all=False, get_bonds=False, get_bank_info=False, redeem=False, save_1099=True)\n        elif force_update_all == True:\n            success = process_account(account_number, df, index, url, force_update_all=False, get_bonds=False, get_bank_info=False, redeem=False, save_1099=True)\n\nif __name__ == '__main__':\n\n    df = pd.read_csv('accounts.csv')\n    url = \"https:\/\/www.treasurydirect.gov\/RS\/UN-Display.do\"\n\n    close_chrome()\n    get_account_data(force_update_all=True)\n\n\n\n\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">#treasury_direct.py<br><\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>from selenium import webdriver\r\nfrom selenium.webdriver.common.by import By\r\nfrom selenium.webdriver.support.ui import WebDriverWait\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.webdriver.chrome.options import Options\r\nfrom selenium.common.exceptions import NoSuchElementException, TimeoutException\r\nfrom webdriver_manager.chrome import ChromeDriverManager\r\nfrom selenium.webdriver.chrome.service import Service\r\nfrom selenium.webdriver.common.action_chains import ActionChains\r\n\r\nimport pyautogui\r\nimport time\r\nimport pandas as pd\r\nimport base64\r\nimport os\r\n\r\nfrom gmail import get_otp, move_otp_emails_to_trash\r\nimport psutil\r\nfrom config import password\r\n\r\ndef close_chrome():\r\n    for process in psutil.process_iter(&#91;\"name\", \"exe\"]):\r\n        try:\r\n            if process.info&#91;\"name\"] == \"chrome.exe\" or (process.info&#91;\"exe\"] and \"chrome\" in process.info&#91;\"exe\"]):\r\n                process.terminate()\r\n        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):\r\n            pass\r\n\r\n# Close any running Chrome instances\r\ndef process_account(account_number, df, index, url, force_update_all=False, get_bonds=True, get_bank_info=False, redeem=False, save_1099=False):\r\n    chrome_options = Options()\r\n    # chrome_options.add_argument(\"user-data-dir=\/home\/jeremy\/.config\/google-chrome\")\r\n    # chrome_options.add_argument(\"--headless\")\r\n    chrome_options.add_argument(\"--disable-gpu\")\r\n    chrome_options.add_argument(\"--no-sandbox\")\r\n    chrome_options.add_argument(\"--start-maximized\")\r\n\r\n    service = Service(ChromeDriverManager().install())\r\n    driver = webdriver.Chrome(service=service, options=chrome_options)\r\n    driver.get(url)\r\n\r\n    wait = WebDriverWait(driver, 10)\r\n\r\n    username_input = driver.find_element(By.NAME, \"username\")\r\n    username_input.send_keys(account_number)\r\n\r\n\r\n    submit_button = driver.find_element(By.NAME, \"submit\")\r\n    submit_button.click()\r\n\r\n\r\n\r\n    # Get the page source to check which text is present\r\n    page_source = driver.page_source\r\n\r\n    if 'OTP' in page_source:\r\n        otp_skipped = False\r\n    else:\r\n        otp_skipped = True\r\n\r\n    if not otp_skipped:\r\n        start_time = time.time()\r\n        otp_received = False\r\n\r\n        while not otp_received:\r\n            elapsed_time = time.time() - start_time\r\n\r\n            # Exit the loop and move to the next account if the timer exceeds 10 minutes\r\n            if elapsed_time > 5 * 60:\r\n                print(\"Timed out waiting for OTP after 10 minutes. Moving to the next account.\")\r\n                driver.close()\r\n                move_otp_emails_to_trash()\r\n                return  # Continue with the next iteration of the loop (if inside a loop)\r\n\r\n            otp = get_otp()\r\n\r\n            if otp is not None:\r\n                otp_received = True\r\n            else:\r\n                # Request a new OTP if the timer exceeds 5 minutes\r\n                if elapsed_time > 3 * 60:\r\n                    try:\r\n                        resend_link = driver.find_element(By.XPATH, '\/\/a&#91;contains(@href, \"\/RS\/OTP-New.do\")]')\r\n                        resend_link.click()\r\n                    except:\r\n                        print('Unable to click resend OTP link')\r\n                        move_otp_emails_to_trash()\r\n                        return\r\n\r\n                # Sleep for 10 seconds before trying again\r\n                time.sleep(10)\r\n\r\n        otp_input = driver.find_element(By.NAME, \"otp\")\r\n\r\n        otp_input.send_keys(otp)\r\n\r\n        #old checkbox no longer exists\r\n        # # Check the checkbox\r\n        # try:\r\n        #     checkbox = driver.find_element(By.NAME, \"registerM2M\")\r\n        #     checkbox.click()\r\n        # except:\r\n        #     print('Unable to click the checkbox')\r\n\r\n        otp_submit_button = driver.find_element(By.XPATH,\r\n                                                '\/\/input&#91;@class=\"action\" and @type=\"submit\" and @name=\"enter.x\"]')\r\n        otp_submit_button.click()\r\n\r\n    # Pause the script for 5 seconds\r\n    time.sleep(3)\r\n\r\n    try:\r\n        password_field = driver.find_element(By.NAME, \"password\")\r\n        password_field.send_keys(password)\r\n    except:\r\n        print('Unable to enter password')\r\n        return\r\n\r\n    try:\r\n        submit_button = driver.find_element(By.NAME, \"enter.x\")\r\n        submit_button.click()\r\n    except:\r\n        return\r\n\r\n    # Get the page source to check which text is present\r\n    page_source = driver.page_source\r\n\r\n    if 'Contact Info Verification' in page_source:\r\n        civ_skipped = False\r\n    else:\r\n        civ_skipped = True\r\n\r\n    if not civ_skipped:\r\n        verify_button = driver.find_element(By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Verify\"]')\r\n        verify_button.click()\r\n\r\n    time.sleep(3)\r\n\r\n    if get_bonds:\r\n        try:\r\n            link = wait.until(EC.presence_of_element_located((By.LINK_TEXT, \"SAVINGS BONDS\")))\r\n            link.click()\r\n        except (NoSuchElementException, TimeoutException):\r\n            print(\"Unable to locate the 'SAVINGS BONDS' link within the specified timeout. This account probably has no savings bonds.\")\r\n\r\n\r\n        time.sleep(3)\r\n\r\n        # Find all radio buttons\r\n        radio_buttons = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_all_elements_located((By.XPATH, '\/\/input&#91;@type=\"radio\"]'))\r\n        )\r\n\r\n        print(f\"Found {len(radio_buttons)} radio buttons\")  # Debugging statement\r\n\r\n        # Find the first radio button that is not disabled\r\n        radio_button_to_select = None\r\n        for radio_button in radio_buttons:\r\n            is_disabled = radio_button.get_attribute(\"disabled\")\r\n            print(f\"Radio button disabled attribute: {is_disabled}\")  # Debugging statement\r\n            if not is_disabled:\r\n                radio_button_to_select = radio_button\r\n                break\r\n\r\n        if radio_button_to_select:\r\n            print(\"Found an enabled radio button.\")\r\n            try:\r\n                radio_button_to_select.click()\r\n                print(\"Click successful.\")\r\n            except Exception as e:\r\n                print(f\"Error clicking the radio button: {e}\")\r\n        else:\r\n            print(\"No enabled radio button found.\")\r\n\r\n        # Click the submit button\r\n        submit_button = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Submit\"]'))\r\n        )\r\n        try:\r\n            submit_button.click()\r\n        except Exception as e:\r\n            print(f\"Error clicking the submit button: {e}\")\r\n        try:\r\n            # Locate the elements containing the desired information\r\n            llc_and_account_number = driver.find_element(By.XPATH, '\/\/div&#91;@id=\"accountnumber\"]').text\r\n\r\n            original_purchase_amount = driver.find_element(By.XPATH,\r\n                                                           '\/\/p&#91;contains(text(), \"Series I current holdings total amount\")]\/span').text\r\n            current_value = driver.find_element(By.XPATH,\r\n                                                '\/\/p&#91;contains(text(), \"Series I current holdings current value\")]\/span').text\r\n            issue_date = driver.find_element(By.XPATH, '(\/\/tr&#91;contains(@class, \"altrow\")]\/td)&#91;3]').text\r\n            interest_rate = driver.find_element(By.XPATH, '\/\/td&#91;contains(text(), \"%\")]').text\r\n\r\n            try:\r\n                status = driver.find_element(By.XPATH, '\/\/td&#91;contains(text(), \"Pending Redemption\")]').text\r\n                if status:\r\n                    print(f\"Status: {status}\")\r\n                    df.loc&#91;index, 'Status'] = status\r\n                else:\r\n                    print(\"Status is blank. Moving to the next field.\")\r\n                    df.loc&#91;index, 'Status'] = \"N\/A\"  # Or whatever value you wish to use for blank fields\r\n            except NoSuchElementException:\r\n                print(\"Element not found. Moving to the next field.\")\r\n                df.loc&#91;index, 'Status'] = \"N\/A\"  # Or whatever value you wish to use for missing fields\r\n            # Separate the LLC name and account number\r\n            llc_name, account_number = llc_and_account_number.split(':', 1)\r\n            llc_name = llc_name.strip().replace(\"LLC Name: \", \"\")\r\n            account_number = account_number.strip()\r\n\r\n            # Print the extracted information\r\n            print(f\"LLC Name: {llc_name}\")\r\n            print(f\"Account Number: {account_number}\")\r\n            print(f\"Original Purchase Amount: {original_purchase_amount}\")\r\n            print(f\"Current Value: {current_value}\")\r\n            print(f\"Issue Date: {issue_date}\")\r\n            print(f\"Interest Rate: {interest_rate}\")\r\n\r\n            # Save the extracted information as new columns for the current row\r\n            df.loc&#91;index, 'LLC Name'] = llc_name\r\n            df.loc&#91;index, 'Original Purchase Amount'] = original_purchase_amount\r\n            df.loc&#91;index, 'Current Value'] = current_value\r\n            df.loc&#91;index, 'Issue Date'] = issue_date\r\n            df.loc&#91;index, 'Interest Rate'] = interest_rate\r\n        except NoSuchElementException:\r\n            print(f\"Failed to extract ibond information for account {account_number}. Moving to the next account.\")\r\n\r\n\r\n    if redeem:\r\n        print('Redeem is turned on')\r\n        radio_buttons = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_all_elements_located((By.XPATH, '\/\/input&#91;@type=\"radio\"]'))\r\n        )\r\n\r\n        # Find the first radio button that is not disabled\r\n        radio_button_to_select = None\r\n        for radio_button in radio_buttons:\r\n            if not radio_button.get_attribute(\"disabled\"):\r\n                radio_button_to_select = radio_button\r\n                break\r\n\r\n        # Select the radio button\r\n        if radio_button_to_select:\r\n            radio_button_to_select.click()\r\n        else:\r\n            print(\"No enabled radio button found\")\r\n\r\n\r\n        # Click the submit button\r\n        select_button = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Select\"]'))\r\n        )\r\n        select_button.click()\r\n        time.sleep(1)\r\n\r\n        redeem_button = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Redeem\"]'))\r\n        )\r\n        redeem_button.click()\r\n        time.sleep(1)\r\n\r\n\r\n        review_button = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Review\"]'))\r\n        )\r\n        review_button.click()\r\n        time.sleep(1)\r\n\r\n\r\n\r\n        submit_button = WebDriverWait(driver, 10).until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"submit\" and @value=\"Submit\"]'))\r\n        )\r\n        submit_button.click()\r\n        time.sleep(1)\r\n\r\n    if get_bank_info:\r\n        # Get bank account information\r\n        # Click the \"ManageDirect\" link\r\n        wait = WebDriverWait(driver, 10)\r\n        manage_direct_link = wait.until(\r\n            EC.presence_of_element_located((By.XPATH, '\/\/a&#91;contains(@href, \"md.DisplayManageDirect\")]')))\r\n\r\n        manage_direct_link.click()\r\n\r\n        time.sleep(3)\r\n\r\n        # # Click the \"Update my account information\" link\r\n        # update_account_info_link = driver.find_element_by_xpath('\/\/a&#91;contains(@href, \"ai.DisplayEditAccountInfo\")]')\r\n        # update_account_info_link.click()\r\n\r\n        # Click the \"Update my Bank Information\" link\r\n        update_bank_info_link = driver.find_element(By.XPATH, '\/\/a&#91;contains(@href, \"bank.DisplayBankInfo\")]')\r\n        update_bank_info_link.click()\r\n\r\n        # # Check if the words \"Security Question\" exist on the page\r\n        # security_question_elements = driver.find_elements_by_xpath('\/\/h1\/strong&#91;contains(text(), \"Security Question\")]')\r\n        #\r\n        # if len(security_question_elements) > 0:\r\n        #     # Extract the question\r\n        #     question = driver.find_element_by_xpath('\/\/h3').text\r\n        #\r\n        #     # If the question contains the word \"pet's\", fill the input element with 'mona'\r\n        #     if \"pet's\" in question.lower():\r\n        #         answer_input = driver.find_element_by_xpath('\/\/input&#91;@type=\"password\" and @name=\"securityQuestionAnswer\"]')\r\n        #         answer_input.send_keys(pets_name)\r\n\r\n        time.sleep(3)\r\n\r\n        try:\r\n            bank_name = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;1]\/td&#91;3]\/strong').text\r\n            routing_number = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;2]\/td&#91;3]\/strong').text\r\n            account_number = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;3]\/td&#91;3]\/strong').text\r\n            names_on_account = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;4]\/td&#91;3]\/strong').text\r\n            account_type = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;5]\/td&#91;3]\/strong').text\r\n            return_code = driver.find_element(By.XPATH, '\/\/tr&#91;@class=\"altrow1\"]&#91;6]\/td&#91;3]\/strong').text\r\n\r\n            # Print the extracted information\r\n            print(\"Bank Name:\", bank_name)\r\n            print(\"Routing Number:\", routing_number)\r\n            print(\"Account Number:\", account_number)\r\n            print(\"Name(s) on Account:\", names_on_account)\r\n            print(\"Account Type:\", account_type)\r\n            print(\"Return Code:\", return_code)\r\n\r\n            # Save the extracted information as new columns for the current row\r\n            df.loc&#91;index, 'Bank Name'] = bank_name\r\n            df.loc&#91;index, 'Routing Number'] = routing_number\r\n            df.loc&#91;index, 'Account Number'] = account_number\r\n            df.loc&#91;index, 'Name(s) on Account'] = names_on_account\r\n            df.loc&#91;index, 'Account Type'] = account_type\r\n            df.loc&#91;index, 'Return Code'] = return_code\r\n\r\n            # print('Press enter to continue')\r\n            # input()\r\n        except NoSuchElementException:\r\n            print(f\"Failed to extract information for account {account_number}. Moving to the next account.\")\r\n\r\n\r\n\r\n\r\n    df.to_csv('accounts.csv', index=False)\r\n    #\r\n    # #code to redeem\r\n    # # Click on Current Holdings\r\n    # try:\r\n    #     body_element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'currentholdings')))\r\n    #     body_element.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find Current Holdings element.\")\r\n    #\r\n    # # Click on the radio button for Series I Savings Bond\r\n    # try:\r\n    #     series_i_radio = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"radio\" and @name=\"seriesCode\" and @value=\"9122270681520925360\"]')))\r\n    #     series_i_radio.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find Series I Savings Bond radio button.\")\r\n    #\r\n    # # Click on the Submit button\r\n    # try:\r\n    #     submit_button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@class=\"action\" and @type=\"submit\" and @name=\"572180930158889311\"]')))\r\n    #     submit_button.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find first Submit button.\")\r\n    #\r\n    # # Click on the next radio button\r\n    # try:\r\n    #     next_radio_button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@type=\"radio\" and @name=\"security_parms\" and @value=\"7956006363914591110\"]')))\r\n    #     next_radio_button.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find next radio button.\")\r\n    #\r\n    # # Click on the Select button\r\n    # try:\r\n    #     select_button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@class=\"action\" and @type=\"submit\" and @name=\"8745557767672189629\"]')))\r\n    #     select_button.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find Select button.\")\r\n    #\r\n    # # Click on the Redeem radio button\r\n    # try:\r\n    #     redeem_button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@class=\"action\" and @type=\"submit\" and @name=\"2263125525558940209\"]')))\r\n    #     redeem_button.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find Redeem button.\")\r\n    #\r\n    # # Click on the Review radio button\r\n    # try:\r\n    #     review_button = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '\/\/input&#91;@class=\"action\" and @type=\"submit\" and @name=\"5163266466587291065\"]')))\r\n    #     review_button.click()\r\n    # except TimeoutException:\r\n    #     print(\"Couldn't find Review button.\")\r\n\r\n    if save_1099:\r\n        directory = '.\/1099'\r\n\r\n        # Loop through a range of years, e.g., 2020 to 2024\r\n        if not os.path.exists(directory):\r\n            os.makedirs(directory)\r\n\r\n        for year in range(2023, 2025):\r\n            try:\r\n                manage_direct_link = wait.until(\r\n                    EC.presence_of_element_located((By.XPATH, '\/\/a&#91;contains(@href, \"md.DisplayManageDirect\")]')))\r\n                manage_direct_link.click()\r\n                time.sleep(2)\r\n\r\n                # year_link_text = f\"Year {year}\"\r\n                # year_link = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.LINK_TEXT, year_link_text)))\r\n                # year_link.click()\r\n                # time.sleep(2)\r\n\r\n                year_link = WebDriverWait(driver, 5).until(\r\n                    EC.element_to_be_clickable((By.XPATH, f\"\/\/a&#91;contains(text(), '{year}')]\")))\r\n                year_link.click()\r\n                time.sleep(2)\r\n\r\n                #you can click on the actual 1099 link but it won't exist in some instances(that tax year has to be in reportable period or later)\r\n                # # Find and Click on the \"View your 1099 for tax year\" Link\r\n                # view_1099_link_text = f\"View your 1099 for tax year {year}\"\r\n                # view_1099_link = WebDriverWait(driver, 10).until(\r\n                #     EC.presence_of_element_located((By.LINK_TEXT, view_1099_link_text)))\r\n                # view_1099_link.click()\r\n                # time.sleep(3)\r\n\r\n                # Print the Page to PDF\r\n                pdf_filename = f'{account_number}_{year}_1099.pdf'\r\n                pdf_path = os.path.join(directory, pdf_filename)  # Path to save PDF in the '1099' subdirectory\r\n                pdf_options = {\r\n                    'printBackground': True,\r\n                    'pageRanges': '1',\r\n                    'paperWidth': 8.27,  # A4 paper size\r\n                    'paperHeight': 11.69,  # A4 paper size\r\n                    'path': pdf_path  # Save PDF with formatted filename in the '1099' subdirectory\r\n                }\r\n\r\n                result = driver.execute_cdp_cmd(\"Page.printToPDF\", pdf_options)\r\n                with open(pdf_path, \"wb\") as file:\r\n                    file.write(base64.b64decode(result&#91;'data']))\r\n\r\n            except (NoSuchElementException, TimeoutException):\r\n                print(f\"Unable to locate or process a link for the year {year}.\")\r\n                continue\r\n    driver.close()\r\n    move_otp_emails_to_trash()\r\n    #Slow the program down as I believe the server is rate limiting.\r\n    # time.sleep(60)\r\n\r\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">#gmail.py<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nimport pickle\nimport base64\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom googleapiclient.discovery import build\nfrom googleapiclient.errors import HttpError\nfrom google.auth.transport.requests import Request\n\n# If modifying these SCOPES, delete the file token.pickle.\nSCOPES = &#91;'https:\/\/www.googleapis.com\/auth\/gmail.modify']\n\n\ndef get_credentials():\n    creds = None\n    # The file token.pickle stores the user's access and refresh tokens, and is\n    # created automatically when the authorization flow completes for the first time.\n    if os.path.exists('token.pickle'):\n        print(\"Loading credentials from pickle file.\")\n        with open('token.pickle', 'rb') as token:\n            creds = pickle.load(token)\n    else:\n        print(\"No pickle file found.\")\n\n    # If there are no (valid) credentials available, let the user log in.\n    if not creds or not creds.valid:\n        if creds and creds.expired and creds.refresh_token:\n            print(\"Credentials expired. Refreshing...\")\n            try:\n                creds.refresh(Request())\n                print(\"Credentials refreshed successfully.\")\n            except Exception as e:\n                print(f\"Could not refresh the token: {e}\")\n                # Remove the existing token.pickle file to avoid reusing it\n                if os.path.exists('token.pickle'):\n                    os.remove('token.pickle')\n                print(\"Removed expired token.pickle file.\")\n                # Trigger the OAuth2 flow\n                flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)\n                creds = flow.run_local_server(port=0)\n        else:\n            print(\"No valid credentials. Running authorization flow.\")\n            flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)\n            creds = flow.run_local_server(port=0)\n\n        # Save the credentials for the next run\n        with open('token.pickle', 'wb') as token:\n            print(\"Saving credentials to pickle file.\")\n            pickle.dump(creds, token)\n    else:\n        print(\"Credentials are valid.\")\n    return creds\n\ndef get_otp():\n    try:\n        creds = get_credentials()\n        service = build('gmail', 'v1', credentials=creds)\n        results = service.users().messages().list(userId='me',\n                                                  q='from:Treasury.Direct@fiscal.treasury.gov subject:\"One Time Passcode\" is:unread newer_than:1m').execute()\n        messages = results.get('messages', &#91;])\n        if not messages:\n            print('No messages found.')\n            return None\n        else:\n            # Get the first unread email\n            message = messages&#91;0]\n            msg = service.users().messages().get(userId='me', id=message&#91;'id'], format='full').execute()\n            msg_str = base64.urlsafe_b64decode(msg&#91;'payload']&#91;'body']&#91;'data']).decode()\n            otp = msg_str.splitlines()&#91;6].split()&#91;0]\n            if otp:\n                one_time_passcode = otp\n                print(f\"{one_time_passcode}\")\n                return one_time_passcode\n            else:\n                print(\"No One Time Passcode found in the email.\")\n                return None\n    except HttpError as error:\n        print(f'An error occurred: {error}')\n        return None\n\ndef move_otp_emails_to_trash():\n    try:\n        creds = get_credentials()\n        service = build('gmail', 'v1', credentials=creds)\n        results = service.users().messages().list(userId='me',\n                                                  q='from:Treasury.Direct@fiscal.treasury.gov subject:\"One Time Passcode\"').execute()\n        messages = results.get('messages', &#91;])\n        if not messages:\n            print('No messages found.')\n        else:\n            for message in messages:\n                service.users().messages().trash(userId='me', id=message&#91;'id']).execute()\n                print(f\"Moved message with ID {message&#91;'id']} to trash.\")\n    except HttpError as error:\n        print(f'An error occurred: {error}')\n\nmove_otp_emails_to_trash()\n#\n#\n# if __name__ == '__main__':\n#     get_emails()<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">#config.py<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>password = 'password'<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Ok, so Ibonds had a huge rate of return last year. Perhaps you&#8217;re like me and you created.. a few hundred accounts. Well, now you have a major&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-6246","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker\" \/>\n<meta property=\"og:description\" content=\"Ok, so Ibonds had a huge rate of return last year. Perhaps you&#8217;re like me and you created.. a few hundred accounts. Well, now you have a major...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\" \/>\n<meta property=\"og:site_name\" content=\"Jeremy Whittaker\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:published_time\" content=\"2024-01-24T18:09:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-01-25T00:39:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\" \/>\n<meta name=\"author\" content=\"JeremyWhittaker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"JeremyWhittaker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\"},\"author\":{\"name\":\"JeremyWhittaker\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"headline\":\"Automating 1099 statements from TreasuryDirect.gov\",\"datePublished\":\"2024-01-24T18:09:19+00:00\",\"dateModified\":\"2024-01-25T00:39:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\"},\"wordCount\":143,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\",\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\",\"name\":\"Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\",\"datePublished\":\"2024-01-24T18:09:19+00:00\",\"dateModified\":\"2024-01-25T00:39:40+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage\",\"url\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\",\"contentUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png\",\"width\":636,\"height\":749},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/new.jeremywhittaker.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automating 1099 statements from TreasuryDirect.gov\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\",\"url\":\"https:\/\/new.jeremywhittaker.com\/\",\"name\":\"Jeremy Whittaker\",\"description\":\"Research, software, markets, housing, and energy\",\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\",\"name\":\"JeremyWhittaker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"caption\":\"JeremyWhittaker\"},\"logo\":{\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\"},\"sameAs\":[\"http:\/\/www.jeremywhittaker.com\",\"https:\/\/www.facebook.com\/WhittakerJeremy\",\"https:\/\/www.linkedin.com\/in\/jeremywhittaker\/\"],\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_US","og_type":"article","og_title":"Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker","og_description":"Ok, so Ibonds had a huge rate of return last year. Perhaps you&#8217;re like me and you created.. a few hundred accounts. Well, now you have a major...","og_url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/","og_site_name":"Jeremy Whittaker","article_publisher":"https:\/\/www.facebook.com\/WhittakerJeremy","article_author":"https:\/\/www.facebook.com\/WhittakerJeremy","article_published_time":"2024-01-24T18:09:19+00:00","article_modified_time":"2024-01-25T00:39:40+00:00","og_image":[{"url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png","type":"","width":"","height":""}],"author":"JeremyWhittaker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"JeremyWhittaker","Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#article","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/"},"author":{"name":"JeremyWhittaker","@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"headline":"Automating 1099 statements from TreasuryDirect.gov","datePublished":"2024-01-24T18:09:19+00:00","dateModified":"2024-01-25T00:39:40+00:00","mainEntityOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/"},"wordCount":143,"commentCount":0,"publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/","url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/","name":"Automating 1099 statements from TreasuryDirect.gov - Jeremy Whittaker","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png","datePublished":"2024-01-24T18:09:19+00:00","dateModified":"2024-01-25T00:39:40+00:00","breadcrumb":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#primaryimage","url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png","contentUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/01\/image-3.png","width":636,"height":749},{"@type":"BreadcrumbList","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/01\/24\/automating-1099-statement-from-treasurydirect-gov\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/new.jeremywhittaker.com\/"},{"@type":"ListItem","position":2,"name":"Automating 1099 statements from TreasuryDirect.gov"}]},{"@type":"WebSite","@id":"https:\/\/new.jeremywhittaker.com\/#website","url":"https:\/\/new.jeremywhittaker.com\/","name":"Jeremy Whittaker","description":"Research, software, markets, housing, and energy","publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c","name":"JeremyWhittaker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","caption":"JeremyWhittaker"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g"},"sameAs":["http:\/\/www.jeremywhittaker.com","https:\/\/www.facebook.com\/WhittakerJeremy","https:\/\/www.linkedin.com\/in\/jeremywhittaker\/"],"url":"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/"}]}},"_links":{"self":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/6246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/comments?post=6246"}],"version-history":[{"count":3,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/6246\/revisions"}],"predecessor-version":[{"id":6253,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/6246\/revisions\/6253"}],"wp:attachment":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/media?parent=6246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/categories?post=6246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/tags?post=6246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}