Archive Commit
This commit is contained in:
72
instructions/README.md
Normal file
72
instructions/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Project Scaffold
|
||||
|
||||
1. Initialize a node project to be able to install npm dependencies
|
||||
```bash
|
||||
npm init
|
||||
```
|
||||
2. Install the following backend dependencies that we will need
|
||||
| App Dependencies | Dev Dependencies |
|
||||
| - | - |
|
||||
| express | nodemon |
|
||||
| morgan | concurrently |
|
||||
| multer | |
|
||||
| csvtojson | |
|
||||
| openai | |
|
||||
3. Initialize a Vite React Project into a new subfolder called frontend
|
||||
```bash
|
||||
npm create vite@latest frontend --template react
|
||||
```
|
||||
4. `cd` into the frontend directory and install dependencies
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
5. In the frontend directory update vite.config.js to be as follows. This will allow us to proxy requests at `/api/`from our frontend to the backend.
|
||||
```js
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server:{
|
||||
proxy:{
|
||||
"/api" : {
|
||||
target: 'http://localhost:3000/api/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
6. Add the following script to the root package.json file.
|
||||
```json
|
||||
"dev": "concurrently \"nodemon app.js\" \"cd frontend && npm run dev\""
|
||||
```
|
||||
7. Make an `app.js` in the root directory and make a boilerplate express app that will work with our frontend.
|
||||
```js
|
||||
const path = require('path')
|
||||
const express = require('express')
|
||||
const logger = require('morgan')
|
||||
const app = express()
|
||||
const port = 3000
|
||||
|
||||
app.use(express.json())
|
||||
app.use(logger('dev'))
|
||||
|
||||
app.get('/api/hello', (req, res) => {
|
||||
res.send(
|
||||
`<h1>Hello World</h1>`
|
||||
)
|
||||
})
|
||||
|
||||
app.use('/', express.static(path.join(__dirname, 'public')))
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening on port ${port}`)
|
||||
})
|
||||
```
|
||||
8. You can now start the backend and frontend server with the same command `npm run dev` from the root of your project.
|
||||
|
||||
9. Open the url that Vite reports (should be something along the lines of `http://127.0.0.1:<port>`) and the default Vite app should render. Add `/api/hello` to the url and you should see the header from our express server route. This means we have successfully bootstrapped our project.
|
||||
|
||||
121
instructions/README2.md
Normal file
121
instructions/README2.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Building Out The Frontend
|
||||
## Clearing the Vite Boilerplate
|
||||
1. Delete the `src` folder and the `public` folder.
|
||||
2. Create a new `src` folder and a new `public` folder.
|
||||
3. Create a new `main.jsx` file in the `src` folder and add the following boilerplate code.
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
```
|
||||
4. Create a new `App.jsx` file in the `src` folder and add the following boilerplate code.
|
||||
```jsx
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<div>Hello</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
```
|
||||
|
||||
## Integrating TailwindCSS
|
||||
1. Install TailwindCSS and PostCSS in your `frontend` directory.
|
||||
```bash
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
```
|
||||
2. Create a new `postcss.config.cjs` file in the root of the project and add the following code.
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
```
|
||||
3. Create a new `tailwind.config.cjs` file in the root of the project and add the following code.
|
||||
```js
|
||||
module.exports = {
|
||||
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
```
|
||||
4. Create a new `index.css` file in the `src` folder and add the following code.
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
```
|
||||
5. Update the `App.jsx` file to be as follows.
|
||||
```jsx
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<div className="bg-gray-100 h-screen flex justify-center items-center">
|
||||
<div className="bg-white rounded-lg shadow-lg p-8">
|
||||
<h1 className="text-2xl font-bold mb-4">Welcome to the Budget App</h1>
|
||||
<p className="text-gray-500 mb-4">This is a simple app that will help you categorize your expenses.</p>
|
||||
<p className="text-gray-500 mb-4">Upload a CSV file and we will do the rest.</p>
|
||||
<div className="flex justify-center">
|
||||
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Upload CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
6. You should now see a basic app with a button that says "Upload CSV". We can now start building out the functionality of the app and any styling can continu to be done with TailwindCSS.
|
||||
|
||||
## Adding File Upload Functionality
|
||||
1. Import useState into `App.jsx` and add a state variable called `file` that will hold the file that is uploaded.
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
```
|
||||
2. Add the following code to the `App` function.
|
||||
```jsx
|
||||
const [file, setFile] = useState(null)
|
||||
```
|
||||
3. Add a file selector input just above the `UploadCSV` button.
|
||||
```jsx
|
||||
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
|
||||
```
|
||||
4. Add an onClick attribute to the `UploadCSV` button and add a callback function to handle the upload.
|
||||
5. Next, define the callback function that will handle the upload in between your useState and return statements. For now, we will just console log the file name.
|
||||
```jsx
|
||||
console.log(file.name)
|
||||
```
|
||||
4. You should now be able to select a file and see the file name in the console. We can now start building out the backend to handle the file upload to the server.
|
||||
5. Next we will make a form to handle the request to the backend server. Replace the console.log statement with the following code.
|
||||
```jsx
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
```
|
||||
6. Next, we will make a POST request to the backend server. Make the following fetch request.
|
||||
```jsx
|
||||
fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
```
|
||||
7. fetch is a network request that returns a promise. We can use the promise to handle the response from the server. Add the following code to handle the response.
|
||||
```jsx
|
||||
.then((res) => res.json())
|
||||
.then((data) => console.log(data))
|
||||
```
|
||||
8. You should now be able to upload a file and see the response in the console. We can now start building out the backend to handle the file upload to the server.
|
||||
84
instructions/README3.md
Normal file
84
instructions/README3.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Building Out The Backend
|
||||
## Handling the File Upload
|
||||
1. In the `app.js` file, add multer as a dependency and configure it to store files in a memory buffer.
|
||||
```js
|
||||
const multer = require('multer')
|
||||
|
||||
const storage = multer.memoryStorage()
|
||||
const upload = multer({ storage: storage })
|
||||
```
|
||||
2. Add a new route to the app that will handle the file upload. (This should match the path from the frontend)
|
||||
```js
|
||||
app.post('/api/upload', (req, res) => {
|
||||
console.log(req.file)
|
||||
res.send("File Uploaded")
|
||||
})
|
||||
```
|
||||
3. The above route will log the file object to the terminal. We can use this to make sure the file is being uploaded correctly. We then close the connection with the client by sending a response of "File Uploaded". The challenge is however that `req.file` is undefined. This is because we haven't added the `upload.single('file')` middleware to the route. We will do this in the next step.
|
||||
4. Add the `upload.single('file')` middleware to the route. This will parse the file from the request body and store it in `req.file` for us anytime a file is included in a request to that route. This is why middleware is so great, it allows for simplistic extensibility.
|
||||
```js
|
||||
app.post('/api/upload', upload.single('file'), (req, res) => { ...})
|
||||
```
|
||||
4. You can now test the route by selecting a file in the frontend and clicking the "Upload CSV" button. You should see the file object logged in the terminal of your server.
|
||||
|
||||
## Making Sense of the CSV File
|
||||
1. When we upload a file, we get a file object back. This object contains a lot of information about the file, including the file name, the file type, and the file size. It also contains a buffer that contains the actual contents of the file. We can use the buffer to read the contents of the file.
|
||||
2. Read the buffer and convert it to a string. We can do this by using the `toString()` method on the buffer. The 'utf8' argument tells the method to convert the buffer to a string using the utf8 encoding. (as opposed to ascii, base64, etc.)
|
||||
```js
|
||||
const csvString = req.file.buffer.toString('utf8')
|
||||
```
|
||||
3. We can now log csvString to the terminal to see the contents of the file. You should see a string that contains the contents of the file (all our transactions represented as a string).
|
||||
|
||||
## Adding a Little Error Handling
|
||||
1. You may end up accidentally not selecting a file when you click the "Upload CSV" button. This will cause the server to crash because `req.file` will be undefined. We can add a little error handling to prevent this from happening.
|
||||
2. Add an if statement to the route that checks if `req.file` is undefined. If it is, send a message to the client.
|
||||
```js
|
||||
if (!req.file) {
|
||||
res.status(400).send("No file uploaded")
|
||||
}
|
||||
```
|
||||
|
||||
## Setting up Our Sorting Magic
|
||||
1. Because we will have a lot of logic for sorting the transactions, we will create a new file to handle this. Create a new file called `sortTransactions.js` in the root directory next to app.js
|
||||
2. In the `sortTransactions.js` file, export a default function that takes a string and an array as an argument. This string will be the contents of the csv file and the array will be the list of categories.
|
||||
```js
|
||||
export default function (transactions, categories) {
|
||||
// logic to sort transactions
|
||||
}
|
||||
```
|
||||
3. For now just return a string that says "Hello World". We will add the logic later.
|
||||
4. Import the `sortTransactions` function into the `app.js` file.
|
||||
```js
|
||||
import sortTransactions from './sortTransactions.js'
|
||||
```
|
||||
6. You may notice this import looks a little different than the ones we have been using. This is because we are using ESM imports. This is the new standard for importing modules in JavaScript. You can read more about it [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import). It is also what we have been using in the frontend but it's a recent addition to Node which used to only support CommonJS imports. You can read more about that [here](https://nodejs.org/api/modules.html#modules_modules_commonjs_modules).
|
||||
7. Refactor your imports to use ESM imports.
|
||||
```js
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import sortTransactions from './sortTransactions.js'
|
||||
```
|
||||
8. Call the `sortTransactions` function in the route and pass in the csvString and the categories array. Go ahead and hardcode the categories array for now.
|
||||
```js
|
||||
const sortedTransactions = sortTransactions(csvString, ['Bills', 'Groceries', 'Restaurants', 'Entertainment', 'Shopping', 'Travel'])
|
||||
```
|
||||
9. Return the sortedTransactions to the client.
|
||||
```js
|
||||
res.send(sortedTransactions)
|
||||
```
|
||||
|
||||
|
||||
// Not shared below this line
|
||||
Notes:
|
||||
- Migrate to app.js to use ESM Imports
|
||||
- Add "type": "module" to package.json
|
||||
- Use neat-csv to parse csv to json onject
|
||||
- Need to make a [__dirname](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#what-do-i-use-instead-of-__dirname-and-__filename) variable to get the path to the static folder
|
||||
|
||||
Add to frontend/src/App.jsx to see the return response.
|
||||
```js
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
})
|
||||
```
|
||||
91
instructions/README4.md
Normal file
91
instructions/README4.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Challenge Starter
|
||||
This week we will be integrating with OpenAI. Some code will be given to you because the focus of today will be around researching how to authenticate to OpenAI's API and how the API responds based on a given input. We want the response to be as deterministic as possible and you will need to write a prompt that can satify this requirement.
|
||||
|
||||
## OpenAI
|
||||
1. Make a new file called openai.js in the root directory with the following contents:
|
||||
```js
|
||||
import { Configuration, OpenAIApi } from "openai";
|
||||
|
||||
const configuration = new Configuration({
|
||||
organization: "<<Org ID>>",
|
||||
apiKey: "<<API Key>>",
|
||||
});
|
||||
const openai = new OpenAIApi(configuration);
|
||||
|
||||
export default async function (transactions, categories) {
|
||||
if (!configuration.apiKey) {
|
||||
return "OpenAI API key not configured";
|
||||
}
|
||||
|
||||
try {
|
||||
const completion = await openai.createCompletion({
|
||||
model: "text-davinci-003",
|
||||
prompt: generatePrompt(transactions, categories),
|
||||
temperature: 0.0,
|
||||
max_tokens: 2000,
|
||||
});
|
||||
return completion.data.choices[0].text;
|
||||
} catch (error) {
|
||||
// Consider adjusting the error handling logic for your use case
|
||||
if (error.response) {
|
||||
console.error(error.response.status, error.response.data);
|
||||
} else {
|
||||
console.error(`Error with OpenAI API request: ${error.message}`);
|
||||
}
|
||||
return "Failed to sort transactions"
|
||||
}
|
||||
}
|
||||
|
||||
function generatePrompt(transactions, categories) {
|
||||
return ``;
|
||||
}
|
||||
```
|
||||
2. Spend some time understanding how the code works and refactor it to meet your purposes. Again, you will need a prompt that is deterministic and can be used to sort transactions. Because the API can be tough to test, OpenAI has given us a [playground](https://platform.openai.com/playground) to experiment within.
|
||||
3. Inside your sortTransactions.js file, import the openai.js file and call the function with the transactions and categories. You will need to await the response and then return it.
|
||||
|
||||
## Route Endpoint
|
||||
1. In your app.js file, we will import `neat-csv` to parse the csv text we receive from `sortTransactions` to transform it into a json object and return that to the client. Your complete endpoint should look something like this:
|
||||
```js
|
||||
app.post('/api/upload/', upload.single('file'), async (req, res) => {
|
||||
if(!req.file) {
|
||||
res.send('No file uploaded.')
|
||||
return
|
||||
}
|
||||
|
||||
const csvString = req.file.buffer.toString('utf8')
|
||||
|
||||
let results = await sortTransactions(csvString,
|
||||
['Bills', 'Groceries', 'Restaurants', 'Entertainment', 'Shopping', 'Travel']
|
||||
)
|
||||
|
||||
let transactions = await neatCsv(results)
|
||||
|
||||
res.send(transactions)
|
||||
})
|
||||
```
|
||||
|
||||
## sortTransactions.js Starting Point
|
||||
```js
|
||||
import returnCategoriesCSV from './openai.js';
|
||||
|
||||
export default async function (transactions, categories) {
|
||||
returnedCSVText = await returnCategoriesCSV(transactions, categories);
|
||||
|
||||
return returnedCSVText
|
||||
}
|
||||
```
|
||||
|
||||
## Getting it working through the entire stack
|
||||
You may be asking yourself what is the point of `sortTransactions.js`. You are handling the request and response in `app.js` and you are handling the sorting in `openai.js`. But in between you have the `sortTransactions.js` file. This is where you will be doing the work of parsing the inputs and outputs from the client and openAI in a structured manner taking into account the considerations below. Pseudo code and remembering your advanced array methods like .split() and .join() will be *incredibly* helpful here.
|
||||
|
||||
There is no "right solution" to this challenge. The goal is to get you thinking about how to structure your code and how to think about the problem. I've solved it in one manner but there are many ways to do it. Be prepared to spend a lot of your effort on thinking about the conceptual steps you will need to take.
|
||||
|
||||
### Considerations
|
||||
- What is async/await and how does it work?
|
||||
- How many transactions can you sort at once?
|
||||
- How many requests to the API can you make every minute?
|
||||
|
||||
### Warning
|
||||
- The API will be slow, it has to do a lot of work as a Large Language Model.
|
||||
|
||||
|
||||
301
instructions/README5.md
Normal file
301
instructions/README5.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Frontend Rendering
|
||||
The goal is to now render the transactions in the browser. There are many ways to render this to the user, the first is to list out all the transactions at once the other is to segment them by category. The first is easier to implement but the second is more useful.
|
||||
|
||||
Below are some code snippets to help you get started. You'll have to compose them yourself and add additional logic.
|
||||
|
||||
## A Generic Component
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
function ComponentName({pro1, prop2}) {
|
||||
return (
|
||||
<h1>ComponentName</h1>
|
||||
)
|
||||
}
|
||||
|
||||
export { ComponentName }
|
||||
```
|
||||
|
||||
## Using a Generic Component
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import {TransactionsList} from './TransactionsList';
|
||||
|
||||
function HigherOrderComponent() {
|
||||
return (
|
||||
<div>
|
||||
<TransactionsList prop1={data} />
|
||||
<TransactionsList prop1={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Rendering a Card
|
||||
```jsx
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 mt-6">
|
||||
<h1 className="text-2xl font-bold mb-4">Transactions</h1>
|
||||
<p className="text-gray-500 mb-4">No transactions to display.</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Rendering a Table
|
||||
```jsx
|
||||
<div className="bg-slate-100 rounded-lg shadow-lg p-8 mt-6">
|
||||
<h1 className="text-2xl font-bold mb-4">Title</h1>
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2">Heading 1</th>
|
||||
<th className="px-4 py-2">Heading 2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.map((array) => (
|
||||
<tr key={arrayItem.id}>
|
||||
<td className="border px-4 py-2">{datavalue1}</td>
|
||||
<td className="border px-4 py-2">{datavalue2}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Helpful Tips
|
||||
## Capturing State from the Backend
|
||||
```jsx
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
|
||||
function handleUpload() {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
fetch('/api/upload/', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setTransactions(data);
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Faster Backend Testing
|
||||
Instead of using OpenAI to process transactions for testing, just return the following json in app.js. This will allow you to test your frontend rendering without having to wait for the OpenAI API to respond and not incur extra charges for API use.
|
||||
|
||||
```js
|
||||
var tempTransactions = [
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "02/03/2023",
|
||||
"Description": "CRUNCHYROLL *MEMBERSHI 415-503-9235 CA",
|
||||
"Debit": "11.03",
|
||||
"Credit": "",
|
||||
"Category": "Entertainment"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "02/02/2023",
|
||||
"Description": "CITY OF MARYSVILLE, WA MARYSVILLE WA",
|
||||
"Debit": "240.50",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "02/01/2023",
|
||||
"Description": "GOOGLE CLOUD XW9DZ4 650-253-0000 CA",
|
||||
"Debit": "0.03",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/30/2023",
|
||||
"Description": "STATE STREET FAMILY CH MARYSVILLE WA",
|
||||
"Debit": "50.00",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/30/2023",
|
||||
"Description": "SNOHOMISH COUNTY PUD 425-783-1000 WA",
|
||||
"Debit": "89.05",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/28/2023",
|
||||
"Description": "HAGGEN 3604 MARYSVILLE WA",
|
||||
"Debit": "9.77",
|
||||
"Credit": "",
|
||||
"Category": "Groceries"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/28/2023",
|
||||
"Description": "SP HIYA HEALTH BOCA RATON FL",
|
||||
"Debit": "32.82",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "TARGET.COM * 800-591-3869 MN",
|
||||
"Debit": "5.00",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "TARGET.COM * 800-591-3869 MN",
|
||||
"Debit": "25.57",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "PINEWOOD FAMILY DENTAL MARYSVILLE WA",
|
||||
"Debit": "485.00",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "Amazon.com2I8ZG7YJ3 Amzn.com/bill WA null XXXXXXXXXXXX9998",
|
||||
"Debit": "5.62",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "TARGET 00021923 MARYSVILLE WA null XXXXXXXXXXXX8138",
|
||||
"Debit": "8.43",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/24/2023",
|
||||
"Description": "ONLINE PAYMENT, THANK YOU",
|
||||
"Debit": "",
|
||||
"Credit": "-4182.23",
|
||||
"Category": "",
|
||||
"_6": ""
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/23/2023",
|
||||
"Description": "STATE STREET FAMILY CH MARYSVILLE WA",
|
||||
"Debit": "50.00",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/22/2023",
|
||||
"Description": "GOOGLE YouTubePremium 650-253-0000 CA",
|
||||
"Debit": "16.57",
|
||||
"Credit": "",
|
||||
"Category": "Entertainment"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/20/2023",
|
||||
"Description": "PEROT MUSEUM CAFE QPS DALLAS TX",
|
||||
"Debit": "25.82",
|
||||
"Credit": "",
|
||||
"Category": "Restaurants"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/20/2023",
|
||||
"Description": "THE HOME DEPOT #4726 MARYSVILLE WA",
|
||||
"Debit": "19.14",
|
||||
"Credit": "",
|
||||
"Category": "Groceries"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/20/2023",
|
||||
"Description": "Amazon.com4L04G50H3 Amzn.com/bill WA null XXXXXXXXXXXX9998",
|
||||
"Debit": "13.12",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/20/2023",
|
||||
"Description": "AMZN Mktp US*622L107Y3 Amzn.com/bill WA null XXXXXXXXXXXX9998",
|
||||
"Debit": "6.55",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/19/2023",
|
||||
"Description": "STARBUCKS STORE 03321 MARYSVILLE WA",
|
||||
"Debit": "10.28",
|
||||
"Credit": "",
|
||||
"Category": "Restaurants"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/19/2023",
|
||||
"Description": "STATE STREET FAMILY CH MARYSVILLE WA",
|
||||
"Debit": "50.00",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/19/2023",
|
||||
"Description": "TARGET.COM * 800-591-3869 MN",
|
||||
"Debit": "52.74",
|
||||
"Credit": "",
|
||||
"Category": "Shopping"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/19/2023",
|
||||
"Description": "ZIPLY FIBER * INTERNET 866-699-4759 WA",
|
||||
"Debit": "50.94",
|
||||
"Credit": "",
|
||||
"Category": "Bills"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/18/2023",
|
||||
"Description": "7-ELEVEN 39782 DFW AIRPORT TX",
|
||||
"Debit": "1.72",
|
||||
"Credit": "",
|
||||
"Category": "Travel"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/18/2023",
|
||||
"Description": "BUC-EE'S #36 TERRELL TX",
|
||||
"Debit": "21.53",
|
||||
"Credit": "",
|
||||
"Category": "Travel"
|
||||
},
|
||||
{
|
||||
"Status": "Cleared",
|
||||
"Date": "01/17/2023",
|
||||
"Description": "DALLAS ZOO MANAGEMENT DALLAS TX",
|
||||
"Debit": "40.00",
|
||||
"Credit": "",
|
||||
"Category": "Entertainment"
|
||||
}
|
||||
]
|
||||
```
|
||||
Reference in New Issue
Block a user