<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vcavanna</title>
    <description>The latest articles on DEV Community by vcavanna (@vcavanna).</description>
    <link>https://dev.to/vcavanna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1126666%2Fff655539-3e44-4a37-b6a5-cf857264d7df.png</url>
      <title>DEV Community: vcavanna</title>
      <link>https://dev.to/vcavanna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vcavanna"/>
    <language>en</language>
    <item>
      <title>Web Scraping Car Sites to Help Me Shop Better</title>
      <dc:creator>vcavanna</dc:creator>
      <pubDate>Wed, 27 Sep 2023 20:27:28 +0000</pubDate>
      <link>https://dev.to/vcavanna/web-scraping-car-sites-to-help-me-shop-better-pop</link>
      <guid>https://dev.to/vcavanna/web-scraping-car-sites-to-help-me-shop-better-pop</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;It is a terrible time to buy a used car. According to the car research website &lt;a href="https://www.iseecars.com/" rel="noopener noreferrer"&gt;iseecars.com&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"[...] the average age of used cars sold increased from 4.8 years to 6.1 years, while the average price across all ages increased 33 percent, from $20,398 to $27,133."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I just graduated. I don't have a lot of money. I (currently) don't have a job to get more money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These problems stack up&lt;/strong&gt;. If my job is in-person or hybrid, I need a car to get there; a car that I literally don't have because I don't have a job. &lt;em&gt;(The contradictions in my future plans are mind-boggling, I know.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confronted by all these problems, I chose the only rational answer:&lt;/strong&gt; create a database on Redshift Serverless of used car entries from &lt;a href="https://edmunds.com" rel="noopener noreferrer"&gt;edmunds.com&lt;/a&gt; so I can research cars with ad-hoc SQL queries and make a pub/sub alert system for high-value entries.&lt;/p&gt;

&lt;p&gt;I want to share what I've learned and how I implemented this project in this post, but if you want to learn more check out the &lt;a href="https://github.com/vcavanna/scrapers" rel="noopener noreferrer"&gt;repo&lt;/a&gt; (give it a star just for me 😁). I especially suggest checking out the readme &lt;a href="https://github.com/vcavanna/scrapers/blob/master/README.md#tutorials-and-guides" rel="noopener noreferrer"&gt;tutorials and guides&lt;/a&gt; since it's a pretty comprehensive list of what I had to learn as a beginner in order to make this project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(But first...)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Table of Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
Design

&lt;ol&gt;
&lt;li&gt;
Local Scripts

&lt;ul&gt;
&lt;li&gt;Extract and Transform&lt;/li&gt;
&lt;li&gt;Load (Part 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Loading Bucket&lt;/li&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;Car DB&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Extensions&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;li&gt;More Notes&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy38rvybn4vie2x7zeffo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy38rvybn4vie2x7zeffo.png" alt="AWS Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AWS architecture, as of so far, relies on just a few components of AWS (all of this was implemented on the AWS free trial.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
Local Scripts

&lt;ul&gt;
&lt;li&gt;Extract and Transform&lt;/li&gt;
&lt;li&gt;Load (Part 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Loading Bucket&lt;/li&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;Car DB&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Local Scripts
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Extract and Transform
&lt;/h4&gt;

&lt;p&gt;The Extract and Transform aspects are done in the file &lt;a href="https://github.com/vcavanna/scrapers/blob/master/edmunds_etl/edmunds_scraper.py" rel="noopener noreferrer"&gt;&lt;code&gt;edmunds_scraper.py&lt;/code&gt;&lt;/a&gt;, which uses the packages &lt;code&gt;bs4&lt;/code&gt; (a.k.a &lt;a href="https://beautiful-soup-4.readthedocs.io/en/latest/#" rel="noopener noreferrer"&gt;&lt;code&gt;Beautiful Soup&lt;/code&gt;&lt;/a&gt;) for web scraping, &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;boto3&lt;/code&gt;&lt;/a&gt; for interacting programatically with AWS, and &lt;a href="https://docs.python-requests.org/en/latest/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/a&gt; for fetching data from Edmunds.&lt;/p&gt;

&lt;p&gt;It scrapes from the inventory of edmunds and generates a file called &lt;code&gt;car_data.csv&lt;/code&gt; for a particular make and model of car.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Fields in the CSV File&lt;/b&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;car_entry_id&lt;/strong&gt;: A made up field vaguely representing a unique ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VIN&lt;/strong&gt;: The Vehicle Identification Number described here. Theoretically unique, although there's probably data entry errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;year&lt;/strong&gt;: The year the car was made&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;make&lt;/strong&gt;: The brand of the car&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;trim&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;miles&lt;/strong&gt;: The number of miles the car's been driven.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;offer&lt;/strong&gt;: The dealership's offer as seen from the Edmunds site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mpg_avg&lt;/strong&gt;: The mpg assuming 55% city driving, 45% highway driving. (needs to be renamed, I know)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mpg_city&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mpg_highway&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;driver_count&lt;/strong&gt;: Number of owners of the vehicle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;accidents&lt;/strong&gt;: The number of (recorded!) accidents for the vehicle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;usage_type&lt;/strong&gt;: The way the car's been used (usually either corporate vehicle, personal use only, or personal use)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;city&lt;/strong&gt;: City of the dealership&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;state&lt;/strong&gt;: State of the dealership&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dist_from_car&lt;/strong&gt;: Distance of the car from my location (DFW area)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;run_date&lt;/strong&gt;: The date that the ETL job was performed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Load (pt. 1)
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/vcavanna/scrapers/blob/master/edmunds_etl/copyToS3.py" rel="noopener noreferrer"&gt;&lt;code&gt;copyToS3.py&lt;/code&gt;&lt;/a&gt; file does exactly what it says on the tin. It's just a tiny script to use my &lt;code&gt;boto3&lt;/code&gt; auth and load the file into my S3 bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. S3
&lt;/h3&gt;

&lt;p&gt;At least as of right now, S3 is seeming like the easiest part of AWS. I just created the file, and configured a few IAM permissions, and I was set.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lambda
&lt;/h3&gt;

&lt;p&gt;The lambda is set to trigger once data is loaded into s3, performing a redshift &lt;code&gt;COPY&lt;/code&gt; command. From &lt;a href="https://stackoverflow.com/questions/37480621/how-can-i-copy-data-from-amazon-s3-to-redshift-automatically/37481851#37481851" rel="noopener noreferrer"&gt;what I've read&lt;/a&gt;, this two-part load sequence is best practice for automatically copying to S3.&lt;/p&gt;

&lt;p&gt;The script for Lambda was mostly borrowed code, by the way. You can find it below if you want to take a look. I still need to make some edits to the script... as embarassing as it is to say, right now the lambda &lt;code&gt;event&lt;/code&gt; parameter doesn't actually do anything, it's all hard-coded in.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Car DB
&lt;/h3&gt;

&lt;p&gt;There are two things that I learned from working with Redshift... the first is that &lt;strong&gt;it actually isn't that hard to &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/tutorial-loading-data.html" rel="noopener noreferrer"&gt;copy data in through S3&lt;/a&gt; and query it if you just use the console&lt;/strong&gt;. So if you're just starting out, copy some data into your redshift database and try out some querying! It's actually just fine for experimentation (although technically &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/what-is.html" rel="noopener noreferrer"&gt;AWS Athena&lt;/a&gt; might be the better choice for experimental ad-hoc queries.)&lt;/p&gt;

&lt;p&gt;The second thing that I learned is that &lt;strong&gt;it is much harder to query programmatically&lt;/strong&gt;. I had to borrow the Lambda python script and tinker with IAM permissions for a couple hours before it worked. Granted, I'm new to this, but still.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensions
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting. Having access to your own database of cars gives you statistics that these car database websites don't give you access to.&lt;/p&gt;

&lt;p&gt;Here's a few example of useful features that I can make now that the data is loaded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create Alerts for Cars I Like&lt;/strong&gt;: No passive scrolling through cars anymore. I can set a query that I like to happen every time new data comes into the database, and if there's any entries that fit the query I'll send an email my way with the report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track Order Volume Over Time&lt;/strong&gt;: Assuming that scrapes happen periodically, I can just make a periodic snapshot table for the models that I'm interested in. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect to Make, Model, Trim Databases&lt;/strong&gt;: If I do additional webscraping for features in a car broken down all the way to the trim grain, I can query down to the exact features that I want. I can choose my own &lt;strong&gt;aesthetic&lt;/strong&gt; &lt;em&gt;(nice leather seats with black exterior)&lt;/em&gt; or &lt;strong&gt;core functionality&lt;/strong&gt; &lt;em&gt;(an engine with x amount of horsepower).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect to VIN Databases&lt;/strong&gt;: The VIN (Vehicle Identification Number) is a number associated with a specific car, tied to its entire history. There are web databases tied to that as well. So I can increase the value of the database by scraping that database as well for the cars I'm interested in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calculate Time On The Market&lt;/strong&gt;: Dealers supposedly use high-pressure tactics like saying that the car might not stay on the market for long. With a database, I can make a study about whether or not that's actually true! &lt;em&gt;&lt;strong&gt;I can counter-point aggressive car salesmen by pointing to the evidence to the contrary for their own dealership!&lt;/strong&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything else I want to do, really.&lt;/strong&gt; I think that's actually the takeaway, and a great transition to my conclusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There's nothing wrong with doing car shopping the simpler way instead of turning to over-engineered solutions. &lt;em&gt;&lt;strong&gt;But this way turned my dread about car shopping into a feeling of mastery.&lt;/strong&gt;&lt;/em&gt; I'm actually looking forward to talking to that overbearing car salesman, so that I can show him all the cool charts that I made.&lt;/p&gt;

&lt;p&gt;... Okay, maybe he won't appreciate the charts when he's trying to sell me a car. Either way, I hope you've enjoyed my little article on setting up a data scrape ETL into the cloud. If you did, take a look at my repo to see what's new about the project, and consider giving it a star. Especially when I'm just starting out, it really helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Github Repo
&lt;/h3&gt;

&lt;p&gt;Can be found &lt;a href="https://github.com/vcavanna/scrapers" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Contribution guidelines aren't set, but get in touch with me if you're interested in working on it! My plan is to make it open source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Script
&lt;/h3&gt;

&lt;p&gt;&lt;b&gt;Here's the code I used for Lambda&lt;/b&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import time
import traceback
import boto3
import logging
from collections import OrderedDict

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    # input parameters passed from the caller event
    # Amazon Redshift Serverless Workgroupname
    redshift_workgroup_name="default-workgroup"
    # database name for the Amazon Redshift serverless instance
    redshift_database_name = "dev"
    # IAM Role of Amazon Redshift sererless instance having access to S3
    redshift_iam_role = "arn:aws:iam::751714364321:role/service-role/AmazonRedshift-CommandsAccessRole-20230825T095934"
    # run_type can be either asynchronous or synchronous; try tweaking based on your requirement
    run_type = "synchronous"

    sql_statements = OrderedDict()
    res = OrderedDict()

    if run_type != "synchronous" and run_type != "asynchronous":
        raise Exception(
            "Invalid Event run_type. \n run_type has to be synchronous or asynchronous.")

    isSynchronous = True if run_type == "synchronous" else False

    # initiate redshift-data redshift_data_api_client in boto3
    redshift_data_api_client = boto3.client('redshift-data')

    sql_statements['CURRENT_USER'] = "select current_user;"
    sql_statements['COPY'] = "COPY dev.public.cars FROM 's3://edmunds-cars/load/car_entries.csv' " + "iam_role '" + redshift_iam_role + """' FORMAT AS CSV DELIMITER ',' QUOTE '"' IGNOREHEADER 1 REGION AS 'us-east-2';"""
    logger.info("Running sql queries in {} mode!\n".format(run_type))

    try:
        for command, query in sql_statements.items():
            logging.info("Example of {} command :".format(command))
            res[command + " STATUS: "] = execute_sql_data_api(redshift_data_api_client, redshift_database_name, command, query,
                                                            redshift_workgroup_name, isSynchronous)

    except Exception as e:
        raise Exception(str(e) + "\n" + traceback.format_exc())
    return res


def execute_sql_data_api(redshift_data_api_client, redshift_database_name, command, query, redshift_workgroup_name, isSynchronous):

    MAX_WAIT_CYCLES = 20
    attempts = 0
    # Calling Redshift Data API with executeStatement()
    res = redshift_data_api_client.execute_statement(
        Database=redshift_database_name, WorkgroupName=redshift_workgroup_name, Sql=query)
    query_id = res["Id"]
    desc = redshift_data_api_client.describe_statement(Id=query_id)
    query_status = desc["Status"]
    logger.info(
        "Query status: {} .... for query--&amp;amp;gt;{}".format(query_status, query))
    done = False

    # Wait until query is finished or max cycles limit has been reached.
    while not done and isSynchronous and attempts &amp;amp;lt; MAX_WAIT_CYCLES:
        attempts += 1
        time.sleep(1)
        desc = redshift_data_api_client.describe_statement(Id=query_id)
        query_status = desc["Status"]

        if query_status == "FAILED":
            raise Exception('SQL query failed:' +
                            query_id + ": " + desc["Error"])

        elif query_status == "FINISHED":
            logger.info("query status is: {} for query id: {} and command: {}".format(
                query_status, query_id, command))
            done = True
            # print result if there is a result (typically from Select statement)
            if desc['HasResultSet']:
                response = redshift_data_api_client.get_statement_result(
                    Id=query_id)
                logger.info(
                    "Printing response of {} query --&amp;amp;gt; {}".format(command, response['Records']))
        else:
            logger.info(
                "Current working... query status is: {} ".format(query_status))

    # Timeout Precaution
    if done == False and attempts &amp;amp;gt;= MAX_WAIT_CYCLES and isSynchronous:
        logger.info("Limit for MAX_WAIT_CYCLES has been reached before the query was able to finish. We have exited out of the while-loop. You may increase the limit accordingly. \n")
        raise Exception("query status is: {} for query id: {} and command: {}".format(
            query_status, query_id, command))

    return query_status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>aws</category>
      <category>database</category>
    </item>
    <item>
      <title>Interactions Tracker, Part 3: Why I stopped and Lessons Learned</title>
      <dc:creator>vcavanna</dc:creator>
      <pubDate>Mon, 25 Sep 2023 14:07:28 +0000</pubDate>
      <link>https://dev.to/vcavanna/interactions-tracker-part-3-why-i-stopped-and-lessons-learned-eik</link>
      <guid>https://dev.to/vcavanna/interactions-tracker-part-3-why-i-stopped-and-lessons-learned-eik</guid>
      <description>&lt;p&gt;I had taken a break from writing posts to assess which projects I really wanted to work on and do some more research into the field of data engineering. Unfortunately, working with the Interactions Tracker didn't really make the cut. This article explains why and shows which directions I'm looking to for project inspiration now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topics for Future Posts
&lt;/h2&gt;

&lt;p&gt;Like you've seen, it's been a while since I've posted. I'm still planning to contribute posts on dev.to, and the break from developing posts and this project have been enriching. I have 3 posts planned, each of which I'll write and publish within the next 3 weeks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Project Design: Data informed Car Purchases&lt;/strong&gt;: scraping data off the Edmunds car website to answer which car would be the best buy. Unlike the interactions project, the Edmunds data scraping project has a proof of concept ETL demo already working with AWS infrastructure up and running (Redshift, S3, Lambda, IAM, and IAM Center). I'll show how I implemented this entire setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Resource Review: Kaggle&lt;/strong&gt;: I give a break down how all of the resources on Kaggle could be used for data engineering, data science and data analytics projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Book Review: &lt;em&gt;How to Read a Book&lt;/em&gt;&lt;/strong&gt;: While it certainly was written for a broader audience than developers, &lt;em&gt;How to Read a Book&lt;/em&gt; offers a solution to the problems of imposter syndrome and an ever-expanding knowledge base. I show how the principles in the book have been applied to my computer science projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, I still have plans to contribute interesting articles. I know this section seems strange  Keep in touch! Subscribe! &lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Stopped
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Accessing the Data Source
&lt;/h3&gt;

&lt;p&gt;Every ETL job starts with &lt;strong&gt;E&lt;/strong&gt;xtraction. Every extraction needs a source to extract from. Sources hidden by passwords aren't publicly facing. Sources that have personally identifiable information (PII) are trusted only to particular entities. I am not one of those entities that could access that data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Value to Work Ration
&lt;/h3&gt;

&lt;p&gt;The actual value that an interactions tracker adds is fairly minimal: assuming I only made the Minimum Viable Product, all it does is add the ability to check which students aren't being connected to on campus.&lt;/p&gt;

&lt;p&gt;There was another project of significantly more increased scope that I considered: developing a events planning tool that would publish events, track timelines, and enable collaboration with University officials, with calendar and mail add-ons. It would add value to the degree that it integrated with other University services (ideally, a poster, email, and push notification would all go out when you pressed publish on the service, and photos taken and associated with events could be attached to event attendance to make developing yearbooks much easier). But at least at the moment, working on this would take up more of my time than I can allow. To the backburner it goes!&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The details of modeling data
&lt;/h3&gt;

&lt;p&gt;I learned that when representing the real world through data, I need to think at the lowest possible grain of detail. One problem I encountered in the dataset was mixing up the grains: I had a "event" grain for interactions as grouped by event, and a "interactions" grain, which was one level below that. As you might have seen in the last article, that only confused the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make simple, complete projects to start
&lt;/h3&gt;

&lt;p&gt;Every project has to start with a bare bones implementation. But in order to make those projects worthwhile, you have to introduce value early on. That's what keeps the motivation going. A project like my database modeling for student interactions is an interesting thought experiment, but &lt;em&gt;does not immediately deliver value&lt;/em&gt;. That's why my next project is associated with something I'll need to do anyway: research for buying a car.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't be afraid to start
&lt;/h3&gt;

&lt;p&gt;Even though I didn't complete the project, I think I got some insight into data modeling that I would not otherwise have. Obviously none of this learning would happen if I simply read books about the subject; I actually needed to jump in to try and articulate how this database would work. So if I could limit the scope, then from a learning standpoint that makes any of these projects worthwhile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope any other aspiring data engineers can take a look at my mistakes and avoid them, or at the very least get started. I had sat around waiting for the ideal project to fall on my lap. That strategy hadn't really worked. When you're someone like me, searching for jobs without great success, the winning strategy is to &lt;em&gt;experiment&lt;/em&gt;. Just beginning helps. It's only after I was tinkering around with this (admittedly poor) project idea that I started to develop other ideas. So get started! I wish you all the best on your development journey.&lt;/p&gt;

</description>
      <category>interactionstracker</category>
      <category>redshift</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Interactions Tracker, Part 2: Revising the Data Model</title>
      <dc:creator>vcavanna</dc:creator>
      <pubDate>Fri, 25 Aug 2023 14:32:40 +0000</pubDate>
      <link>https://dev.to/vcavanna/interactions-tracker-part-2-revising-the-data-model-4nef</link>
      <guid>https://dev.to/vcavanna/interactions-tracker-part-2-revising-the-data-model-4nef</guid>
      <description>&lt;p&gt;For my next article on the interactions tracker, I had planned to show how the model that I had created could be implemented. Unfortunately enough, the data model I made isn't ready for a RDBMS. That's okay! This mistake gives the opportunity to go over the exact sticking point, learn both my thought process and the RDBMS process, and how I can implement this in a way that better adheres to warehousing principles drawn from Kimball.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what exactly was the mistake that I made in my data model?
&lt;/h2&gt;

&lt;p&gt;Well, let's re-evaluate the diagram that I had produced earlier:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dZ5_52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qojb2xyuzc9woxt5v0m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dZ5_52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qojb2xyuzc9woxt5v0m7.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This diagram has two fact tables at two different levels of detail or grain.&lt;/strong&gt; So we're not really practicing dimensional modeling.&lt;/p&gt;

&lt;p&gt;This became apparent as I began making the SQL tables for this dimensional model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import sqlite3
conn = sqlite3.connect("rez_life.db")
event_columns = [
    "event_key INTEGER PRIMARY KEY",
    "staff_key INTEGER",
    "student_keys ??????", # What do I do here??
    "conversation_type VARCHAR",
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;student_keys&lt;/code&gt; can't be represented as a single element of data. This is important because student_keys is meant to be a foreign key to the 'dim_member' table. I know that one of my business questions was to answer "Who is attending each event?" To get that answer, I need a query that works like 'from fct_events select student_keys' in order to get the list of students.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events should not be a fact table
&lt;/h2&gt;

&lt;p&gt;Of course, it's &lt;em&gt;hypothetically&lt;/em&gt; possible to store a list of names in a single column like I wanted in my first attempt at data modeling, and keep the "event" level of grain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt; residentKeyList = ["Jeffrey", "Zack", "Cassandra"]
&amp;gt;&amp;gt; residentKeysAsString = ""
&amp;gt;&amp;gt; for key in residentKeyList:
        residentKeysAsString+= key + "_"

&amp;gt;&amp;gt; print(residentKeysAsString)
Jeffrey_Zack_Cassandra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now I have it all together, like I wanted. The issue with this is twofold: 1) Now I have to create a custom parser, which is prone to breaking easily, and 2) In choosing a higher level grain, I'm imposing limits on what I can do with this data.&lt;/p&gt;

&lt;p&gt;For the second issue, let's suppose I don't just want a list of residents that attended the event. &lt;strong&gt;Suppose I only want the residents that live in one particular dormitory&lt;/strong&gt;! This makes sense from a residence life standpoint: while it's great to offer events to non-residents, you want to make sure that events mostly bring in students that you're asked to serve.&lt;/p&gt;

&lt;p&gt;All of the sudden, our handy concatenated string of residents is close to useless! We would need to write a custom query that gets the list of students through SQL, parses through python, then check through the list of students in python for whether they reside in the dormitory of choice. Already I sense the data analysts shaking their fists!&lt;/p&gt;

&lt;h2&gt;
  
  
  Remodeling to make events a dimension table is the answer
&lt;/h2&gt;

&lt;p&gt;Wouldn't it be so much better if we could answer this question in a single data query? For instance:&lt;br&gt;
&lt;code&gt;SELECT interactions.resident_name FROM fct_interactions INNER JOIN dim_members ON fct_interactions.member_key=dim_members.member_key WHERE fct_interactions.event_key="Chili Cookout" AND dim_members.residence="Clark Hall"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This gets the same data as the above complicated process simply by joining two tables together and filtering the results. Keeping things at the lowest possible grain is standard practice for Kimball, and it's a mistake of mine to try something else.&lt;/p&gt;

&lt;p&gt;Luckily, the remodel is very simple:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hZHgJE8L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfngfirataa3tp3b3948.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hZHgJE8L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfngfirataa3tp3b3948.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply calling the events table a dimension table rather than make it a separate fact table keeps the grain at the same level, meaning that the queries I mentioned above can be called fairly easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Unless you want angry analysts, keep the data to one element per cell. Doing otherwise defeats the point of RDBMS. Seems like a clear point in retrospect.&lt;/li&gt;
&lt;li&gt;The events table will be treated as a dimensions table for the lowest grain level from now on. We'll see more about how this impacts the design in the next post, when I implement the SQL database.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>interactionstracker</category>
    </item>
    <item>
      <title>Part 1: Tracking Interactions - Initial Design and Data Modeling</title>
      <dc:creator>vcavanna</dc:creator>
      <pubDate>Mon, 21 Aug 2023 16:16:27 +0000</pubDate>
      <link>https://dev.to/vcavanna/part-1-tracking-interactions-initial-design-and-data-modeling-563k</link>
      <guid>https://dev.to/vcavanna/part-1-tracking-interactions-initial-design-and-data-modeling-563k</guid>
      <description>&lt;p&gt;I'm making a project to track interactions. Follow my progress in these articles as I work towards a fully operational relation tracking site, SQL database, and REST API.&lt;/p&gt;

&lt;p&gt;This first article introduces the tech stack and models the data based off of the Four-Step Dimensional Model Process in Kimball's Book: &lt;a href="https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/books/data-warehouse-dw-toolkit/"&gt;Data Warehouse Toolkit&lt;/a&gt;, which I will be referencing periodically later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Four-Step Dimensional Model Process
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1) Select the business process
&lt;/h2&gt;

&lt;p&gt;Community leaders are often challenged to ensure everyone is included and feels like their needs are met. While for small groups a leader just has to remember who they talked to, if for some ungodly reason you have groups of +20 people and multiple people leading the community, you cannot easily ensure that everyone is being seen. To make things more complicated, some interactions are one on one, and some are within a group context. So I'm building a community tracker to make sure that leaders have access to data on whether each person in the community has interacted with the leaders.&lt;/p&gt;

&lt;p&gt;Normally the process would involve talking with the operators of the business to understand their work and data needs, I am experienced in the business so I can skip that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Business Process:&lt;/strong&gt; Track the process of interacting with community members in conversations and community events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Needs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provide a portal for data entry&lt;/strong&gt; for individual interactions and event attendance between staff/community leaders and members.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide a backend database&lt;/strong&gt; to answer the below business questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide a REST API&lt;/strong&gt; so other developers can reference / extract data&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Business Questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Need answered
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Who is not regularly interacting with staff and community leaders?&lt;/li&gt;
&lt;li&gt;Who has had mainly negative interactions with the staff and community leaders?&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Nice to have answered
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;What types of interactions are there?&lt;/li&gt;
&lt;li&gt;What events are associated with a particular group leader, and which members are associated with those events?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2) Declare the Grain
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;There are actually two transactions implicitly in the business process I outlined above. There will be one row for transactions at the interaction level, and one row for transactions at the event level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grain 1: One row per interaction between a student and staff&lt;br&gt;
Grain 2: One row per event held by staff&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Identify the dimensions
&lt;/h2&gt;

&lt;p&gt;In order to map out dimension tables, we answer the who, what, where, when, why questions.&lt;br&gt;
Who?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The leader&lt;/li&gt;
&lt;li&gt;The student
What?&lt;/li&gt;
&lt;li&gt;The event in which the interaction occurred (grain 1)&lt;/li&gt;
&lt;li&gt;The type of conversation / interaction (more on this later)&lt;/li&gt;
&lt;li&gt;Whether it was a recurring event (grain 2)
Where?&lt;/li&gt;
&lt;li&gt;The location of the event / interaction
When?&lt;/li&gt;
&lt;li&gt;The date of the event / interaction
Why?&lt;/li&gt;
&lt;li&gt;N/A
How?&lt;/li&gt;
&lt;li&gt;N/A&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, though, we have to recognize the issue with this type of data. Data like this isn't generated by a machine, but instead has to be inputted by a staff member. You can contrast this data to data like purchases in retail, salesforce data, etc. &lt;/p&gt;

&lt;p&gt;Since this is the case, it's best to limit the amount of user input to the minimum viable product, at least at first. The business doesn't benefit from a product that their staff doesn't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Identify the facts
&lt;/h2&gt;

&lt;p&gt;Interactions Fact Table&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Student Key&lt;/li&gt;
&lt;li&gt;Staff Key&lt;/li&gt;
&lt;li&gt;Conversation Type&lt;/li&gt;
&lt;li&gt;Event Key&lt;/li&gt;
&lt;li&gt;Location Key&lt;/li&gt;
&lt;li&gt;Date Key&lt;/li&gt;
&lt;li&gt;Negative or Positive Interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events Fact Table&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staff Key&lt;/li&gt;
&lt;li&gt;Attendance (Student Keys)&lt;/li&gt;
&lt;li&gt;Event Key&lt;/li&gt;
&lt;li&gt;Date Key&lt;/li&gt;
&lt;li&gt;Location Key&lt;/li&gt;
&lt;li&gt;Negative or Positive Interaction&lt;/li&gt;
&lt;li&gt;Conversation Type&lt;/li&gt;
&lt;li&gt;Recurring Event? (Y/N)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that these questions have been answered, we can create a sample diagram of how these fact and dimension tables relate:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dZ5_52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qojb2xyuzc9woxt5v0m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dZ5_52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qojb2xyuzc9woxt5v0m7.png" alt="Image description" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I'll just quickly outline the tech stack for this project:&lt;/p&gt;

&lt;h1&gt;
  
  
  Tech Stack
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Python language&lt;/li&gt;
&lt;li&gt;Flask framework&lt;/li&gt;
&lt;li&gt;SQLite database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These components, already used in the earlier tutorial, should be all I need to make this project happen.&lt;/p&gt;

&lt;p&gt;That's all for now, I'll be back with updates as the project progresses forwards!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Making a Python SQL Database with a Flask Web API</title>
      <dc:creator>vcavanna</dc:creator>
      <pubDate>Mon, 14 Aug 2023 00:51:26 +0000</pubDate>
      <link>https://dev.to/vcavanna/making-a-python-sql-database-with-a-flask-web-api-4a</link>
      <guid>https://dev.to/vcavanna/making-a-python-sql-database-with-a-flask-web-api-4a</guid>
      <description>&lt;p&gt;This is meant to document my project to get familiar with Python, SQL Databases, and Flask by creating a Flask API. My goal is to follow and implement tutorials, gradually working up to the first project.&lt;/p&gt;

&lt;p&gt;The first tutorial that I'm following for this project is &lt;a href="https://realpython.com/flask-connexion-rest-api/"&gt;realpython's flask and connexion tutorial&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm going to update this post with any takeaways, difficulties, etc., as well as post the end results.&lt;/p&gt;

&lt;p&gt;Challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependency conflicts!! I found in the tutorial that despite following the steps, the connexion dependency didn't work with the flask-sql package installed. I resolved this by updating the connexion dependency, which fixed the issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key Takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;swagger is an extension of flask that lets you document your flask API according to openAI standards.&lt;/li&gt;
&lt;li&gt;One thing I wish the tutorial had was a way to easily test the API as I was developing it. So I added Postman following &lt;a href="https://apidog.com/blog/how-to-import-swagger-into-postman/"&gt;this tutorial&lt;/a&gt;, which was very helpful.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oKMy-7n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwo3cqdgdmkqgl7qdbtb.png" alt="Image description" width="800" height="450"&gt;
&lt;/li&gt;
&lt;li&gt;Definitely it would be nice to research the SQLAlchemy app! I'm new to SQL database management, so this seems like a good way to start.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
