Saturday, 30 December 2023

Reference Review: TechLead

Whassup, guys?!

Are you in for a treat today - I am going to review a resource I found on YouTube a couple years back. TechLead is a channel maintained by Patrick Shyu, a self-described "ex-Google ex-Facebook millionaire". Plus, of course, he was a tech lead. The title kind of gives it away, huh?


Years ago, back in 2019, a friend of mine (remember Jermyn?) pasted the link to the YouTube channel in a WhatsApp group chat. That was when Patrick Shyu had just been fired by Facebook.

I confess that I've not been keeping up with the channel as regularly as I could have, but there's so much content to consume on YouTube alone, and only one of me. That being said, Patrick Shyu's content is pretty good, in the appropriate doses.

The Premise

Patrick Shyu doles out advice on tech trends, software development and life generally, sometimes all at once. 

The Aesthetics

Patrick Shyu mostly conducts his podcasts in long continuous format (albeit with glaring cuts here and there). The visual is usually of him chilling with a mug of coffee and talking to the camera, occasionally gesticulating a bit. 

Once in a while, there will be a super-imposed video clip of some other visual to further accentuate his point.

Not terribly interesting in and of itself, to be honest. Patrick isn't ugly but he has less varied facial expressions than a concrete wall.

The Experience

Since the visual is rarely that interesting, it's the audio I've tended to focus on. I can let TechLead run in the background while I shower, cook or work. 

The Interface

Nothing says "basic" like TechLead. It's usually just a monologue, often with no chapters.

What I Liked

Some complain about the monotonous, deadpan delivery. It's actually my favorite part. Shyu's tongue-in-cheek, offhand references to his millionaire (sometimes multi-millionaire) status is hilarious. 

In particular, the trollish ones are side-splitting.







Honestly, I think a lot of his advice and observations are pretty sound, if fairly obvious.







The backdrops can be pretty cool. Most of the time, it's a little workdesk or a rustic cottage background, but occasionally it's an outdoor setting.

Normally I'm not a big fan of ads. But the way Shyu handles it, is smooth as silk. Much of it is down to his speedy and continuous verbal delivery, to the point that it's not glaringly obvious it's a sales pitch until you're a few seconds into it. This guy has turned it into an art form!

The fact that Patrick Shyu has actual experience working in Big Tech and has something to offer other than the usual rose-colored narrative, is already one helluva plus.

What I Didn't

His videos are all ten to twenty minute duration monologues. with no segregation or organization of points. In small doses, Patrick Shyu is fine, but if you have to sit through an entirely monologue to get to the points, it tends to get exhausting.

Sometimes Shyu drones on about his personal life, when all I really want to hear about is his opinion on tech, and tech experiences. Worse, sometimes he talks about cryptocurrency.

Conclusion

TechLead isn't great, but it's entirely serviceable if you want some industry advice, or just a voice in the background while you grind. A solid choice of channel for a techie to subscribe to.

My Rating

7 / 10

It's coffee time,
T___T

Sunday, 24 December 2023

Web Tutorial: The Random Christmas Card (Part 2/2)

We have created three different themes for three different pictures. Naturally, we are also going to want the text to match.

For that, we've already prepared those prompts in the themes array. Right now, index is still set to 2, so let's try sending the prompt to ChatGPT. For that, you will need a ChatGPT Developer Account and the key that comes with it.

Create three variables - key, org and url. key is your API key (It's represented here as "sk-xxx". That's not my actual API key but I'm not about to disclose it!). org is the identifier of your account. url is the ChatGPT endpoint.

index.php
$index = rand(0, 2);
$index = 2;
$content = $themes[$index]["prompt"];

$key = "sk-xxx";
$org = "org-FUOhDblZb1pxvaY6YylF54gl";
$url = 'https://api.openai.com/v1/chat/completions';  

Create array headers. In there, we have three elements which are all strings. key and org are used in the headers array, and it's used to identify the user.

index.php
$key = "sk-xxx";
$org = "org-FUOhDblZb1pxvaY6YylF54gl";
$url = 'https://api.openai.com/v1/chat/completions';  

$headers = [
    "Authorization: Bearer " . $key,
    "OpenAI-Organization: " . $org,
    "Content-Type: application/json"
];

So here, we create the object needed to pass into the endpoint call. We use the appropriate prompt and set the maximum number of tokens at 1000.

index.php
$headers = [
    "Authorization: Bearer " . $key,
    "OpenAI-Organization: " . $org,
    "Content-Type: application/json"
];

$messages = [];
$obj = [];
$obj["role"] = "user";
$obj["content"] = $themes[$index]["prompt"];
$messages[] = $obj;

$data = [];
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = 1000;

Here, we use data and headers in cURL. Bear in mind we have to do a json_encode() on data in order to turn it into a string.

index.php
$data = [];
$data["model"] = "gpt-3.5-turbo";
$data["messages"] = $messages;
$data["max_tokens"] = 1000;

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

Run curl_exec() with curl as an argument, and assign the returned result to result. Use curl_errno() to see if there's an error, and print it if so. At the end of it, sew things up with curl_close().

index.php
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

$result = curl_exec($curl);
if (curl_errno($curl)) {
    echo 'Error:' . curl_error($curl);
}


curl_close($curl);

Turn result from a string back to an object using json_decode(). There may be breaks in the content. Use nl2br() to convert them to HTML breaks, and assign the result to the content string.

index.php
curl_close($curl);

$result = json_decode($result);
$content = nl2br($result->choices[0]->message->content);

Here's some more sanitization you may want to implement.

index.php
$result = json_decode($result);
$content = nl2br($result->choices[0]->message->content);
$content = str_replace("[Recipient]", $name, $content);
$content = str_replace("[Recipient's Name]", $name, $content);
$content = str_replace("[Your Name]", "Teochew Thunder", $content);

Now let's see what we're getting!




If you refresh, it should be different!




Change the value and test again.

index.php
$index = 1;

Nice poem!




Last test!

index.php

$index = 0;

And you see the content is different now. It's snow-based.




Now comment this line off. We don't need it anymore.

index.php
//$index = 0;

And now it should be totally random.




Try it with a value in the name parameter.



That's it...

Enjoy sending your Christmas card to your friends. This is just a beginning; feel free to add more themes if you wish.

Merry Christmas, Human Being!
T___T

Thursday, 21 December 2023

Web Tutorial: The Random Christmas Card (Part 1/2)

It's less than a week to Christmas, and I'd like to welcome you to the traditional Christmas-themed web tutorial!

This year, just as we did back in 2017, we will be working with LESS. Nothing fancy, just that we will have multiple themes for the stylesheets and something like LESS makes sense. What we are attempting to create is a randomly generated online Christmas card with a random theme and a random message. This will be accomplished using PHP. With that in mind, let's proceed!

First, we have two folders - img and css. index.php will be in the main folder. styles.less will be in the css folder, with the LESS JavaScript file in the js folder. Three images will be in the img folder.

img/snow.jpg



img/food.jpg



img/nativity.jpg



We have some HTML here, with a reference to the LESS file and the JavaScript file for LESS.

index.php
<!DOCTYPE html>
<html>
    <head>
        <title>Xmas 2023</title>
        <link rel="stylesheet/less" type="text/css" href="css/styles.less" />

        <script src="js/less.min.js"></script>
    </head>

    <body>

    </body>
</html>


We will insert a placeholder for the body tag's class.

index.php
<body class="theme_">

</body>


Within, we will have a div styled using card.

index.php
<body class="theme_">
    <div class="card">

    </div>

</body>


And inside that, we have three more divs, one styled using image and the other styled using greeting. The last one is just to offset whatever the float properties for image and greeting eventually become.

index.php
<body class="theme_">
    <div class="card">
        <div class="image">

        </div>

        <div class="greeting">

        </div>

        <div style="clear: both"></div>

  </div>
</body>


And we'll have a header and paragraph tag inside the div styled by greeting.

index.php
<body class="theme_">
  <div class="card">
        <div class="image">

        </div>

        <div class="greeting">
          <h1></h1>
          <p></p>
        </div>

        <div style="clear: both"></div>
  </div>
</body>


The LESS file starts with some styling for the html and body tags. This basically just defines font size and resets margins and padding. We also define centered so that we can use this as a mixin for other classes later.

css/styles.less
html, body
{
    height: 100%;
    padding: 0px;
    margin: 0px;
    font-size: 14px;
}

.centered
{
    margin-left: auto;
    margin-right: auto;
}


There's not going to be much to see here. We first need to add some PHP. The first line we add is the variable name. If there is a parameter by the name of, well, name, in the URL, use that value. If not, just use the string "Human Being".

index.php
<?php
    $name = (isset($_GET["name"]) ? $_GET["name"] : "Human Being");
?>


<!DOCTYPE html>
<html>
    <head>
        <title>Xmas 2023</title>
        <link rel="stylesheet/less" type="text/css" href="css/styles.less" />

        <script src="js/less.min.js"></script>
    </head>

    <body>
        <div class="card">
            <div class="image">

            </div>

            <div class="greeting">
                <h1></h1>
                <p></p>
            </div>

            <div style="clear: both"></div>
        </div>
    </body>
</html>


Then we have an array, themes. It has three elements, each of them an array with keys. The name key values have been provided. The rest are right now empty strings.

index.php
<?php
    $name = (isset($_GET["name"]) ? $_GET["name"] : "Human Being");

    $themes = [
        ["name" => "snow", "title" => "", "prompt" => ""],
        ["name" => "food", "title" => "", "prompt" => ""],
        ["name" => "nativity", "title" => "", "prompt" => ""],
    ];

?>


Here, I've filled in the other values. title is what is going to appear as a header in your eventual output. These strings use the variable name. prompt is what we will be using later, to get content.

index.php
<?php
    $name = (isset($_GET["name"]) ? $_GET["name"] : "Human Being");

    $themes = [
        ["name" => "snow", "title" => "Seasons Greetings for a White Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 80 to 100 words, as a greeting on a Christmas Card, involving snow and winter"],
        ["name" => "food", "title" => "Dear " . $name . ", wishing you festive food and fun!", "prompt" => "Generate a short poem, using less than 100 words, as a greeting on a Christmas Card, revoving around food"],
        ["name" => "nativity", "title" => "Have a blessed Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 50 to 100 words, as a greeting on a Christmas Card, involving Jesus and the Nativity"]
    ];
?>


And then we use the rand() function to define the variable index as a value from 0 to 2. Then we define content as the prompt key value of the element of themes referenced by the value of index. Thus every time the page is reloaded, a different theme is used!

index.php
<?php
    $name = (isset($_GET["name"]) ? $_GET["name"] : "Human Being");

    $themes = [
        ["name" => "snow", "title" => "Seasons Greetings for a White Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 80 to 100 words, as a greeting on a Christmas Card, involving snow and winter"],
        ["name" => "food", "title" => "Dear " . $name . ", wishing you festive food and fun!", "prompt" => "Generate a short poem, using less than 100 words, as a greeting on a Christmas Card, revoving around food"],
        ["name" => "nativity", "title" => "Have a blessed Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 50 to 100 words, as a greeting on a Christmas Card, involving Jesus and the Nativity"]
    ];

    $index = rand(0, 2);

    $content = $themes[$index]["prompt"];

?>


However, since we're at the design stage, we hardcode the value of index at 0.

index.php
<?php
    $name = (isset($_GET["name"]) ? $_GET["name"] : "Human Being");

    $themes = [
        ["name" => "snow", "title" => "Seasons Greetings for a White Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 80 to 100 words, as a greeting on a Christmas Card, involving snow and winter"],
        ["name" => "food", "title" => "Dear " . $name . ", wishing you festive food and fun!", "prompt" => "Generate a short poem, using less than 100 words, as a greeting on a Christmas Card, revoving around food"],
        ["name" => "nativity", "title" => "Have a blessed Christmas, " . $name . "!", "prompt" => "Generate a short paragraph, between 50 to 100 words, as a greeting on a Christmas Card, involving Jesus and the Nativity"]
    ];

    $index = rand(0, 2);
    $index = 0;

    $content = $themes[$index]["prompt"];
?>


In the HTML, add in the PHP code to display title strings and content.

index.php
<!DOCTYPE html>
<html>
    <head>
        <title>Xmas 2023</title>
        <link rel="stylesheet/less" type="text/css" href="css/styles.less" />

        <script src="js/less.min.js"></script>
    </head>

    <body class="theme_<?php echo $themes[$index]["name"]; ?>">
        <div class="card">
            <div class="image">

            </div>

            <div class="greeting">
                <h1><?php echo $themes[$index]["title"]; ?></h1>
                <p><?php echo $content; ?></p>
            </div>

            <div style="clear: both"></div>
        </div>
    </body>
</html>


If you were to run the code now, it would look like this. This is in dire need of styling.




We'll do it in the LESS file. Start by creating a style called theme_snow. That's the one that is currently active because we hardcoded the value of index to 0. While we're at it, create classes for theme_food and theme_nativity.

css/styles.less
html, body
{
    height: 100%;
    padding: 0px;
    margin: 0px;
    font-size: 14px;
}

.centered
{
    margin-left: auto;
    margin-right: auto;
}

.theme_snow
{

}

.theme_food
{

}

.theme_nativity
{

}



In here, we want to set the font and the background to a deep blue.

css/styles.less
.theme_snow
{
    font-family: arial;

    background: rgb(0, 0, 100);

}


Now we want to style card within theme_snow. In this case, card will be a translucent white rectangle in the middle of the screen, with rounded corners. We'll use centered here, which we defined earlier.

css/styles.less
.theme_snow
{
    font-family: arial;

    background: rgb(0, 0, 100);

    .card
    {
        margin-top: 10px;
        width: 600px;
        height: 650px;
        padding: 2em;
        border-radius: 10px;
        .centered;
        background: rgba(255, 255, 255, 0.5);
    }

}


We then style image. It will be circular and it will use the snowman image as its background. Again, it is in the middle of its parent, so use centered.

css/styles.less
.theme_snow
{
    font-family: arial;

    background: rgb(0, 0, 100);

    .card
    {
        margin-top: 10px;
        width: 600px;
        height: 650px;
        padding: 2em;
        border-radius: 10px;
        .centered;
        background: rgba(255, 255, 255, 0.5);
    }

    .image
    {
        width: 250px;
        height: 250px;
        border-radius: 50%;
        margin-bottom: 10px;
        background-image: url(../img/snow.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
        .centered;
    }

}


Finally, we style greeting. It will have white text, and take the full width and a certain height.

css/styles.less
.theme_snow
{
    font-family: arial;

    background: rgb(0, 0, 100);

    .card
    {
        margin-top: 10px;
        width: 600px;
        height: 650px;
        padding: 2em;
        border-radius: 10px;
        .centered;
        background: rgba(255, 255, 255, 0.5);
    }

    .image
    {
        width: 250px;
        height: 250px;
        border-radius: 50%;
        margin-bottom: 10px;
        background-image: url(../img/snow.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
        .centered;
    }

    .greeting
    {
        width: 100%;
        height: 350px;
        color: rgb(255, 255, 255);
    }

}


Refresh. Quite a drastic change, ain't it?




Back to the PHP, change the value of index.

index.php
$index = rand(0, 2);
$index = 1;


And now we will work on the CSS class theme_food. As before, we set the font and change the background color to a deep red.

css/styles.less
.theme_food
{
    font-family: verdana;

    background: rgb(150, 0, 0);

}


The card styling here is another rectangle, also centered using centered, and it's an opaque cream color. We use box-shadow to give this card a translucent black shadow.

css/styles.less
.theme_food
{
    font-family: verdana;

    background: rgb(150, 0, 0);

    .card
    {
        margin-top: 10px;
        width: 800px;
        height: 600px;
        padding: 1em;
        .centered;
        background: rgb(200, 200, 100);
        box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
    }

}


For image, we are going to float it left and make it a tall rectangle. Then we are going to make food.jpg its background.

css/styles.less
.theme_food
{
    font-family: verdana;

    background: rgb(150, 0, 0);

    .card
    {
        margin-top: 10px;
        width: 800px;
        height: 600px;
        padding: 1em;
        .centered;
        background: rgb(200, 200, 100);
        box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
    }

    .image
    {
        width: 50%;
        height: 100%;
        float: left;
        background-image: url(../img/food.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
    }

}


For greeting, we will float it right.

css/styles.less
.theme_food
{
    font-family: verdana;

    background: rgb(150, 0, 0);

    .card
    {
        margin-top: 10px;
        width: 800px;
        height: 600px;
        padding: 1em;
        .centered;
        background: rgb(200, 200, 100);
        box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
    }

    .image
    {
        width: 50%;
        height: 100%;
        float: left;
        background-image: url(../img/food.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
    }

    .greeting
    {
        width: 45%;
        height: 100%;
        float: right;
    }

}


And the final result is...




Great. Time to work on the final theme, theme_nativity. This one is slightly different in the sense that image and greeting are not next to each other, but occupy the same space. They intersect.

First, change the value of index to 2.

index.php

$index = rand(0, 2);
$index = 2;


Define font and background for theme_nativity. Let' go for beige.
css/styles.less
.theme_nativity
{
    font-family: georgia;

    background: rgb(150, 100, 50);

}


card is going to be a large rectangle, with no defined background, and a thick brown border. Again, we use centered to center it - bet you're glad we're using LESS now!

css/styles.less
.theme_nativity
{
    font-family: georgia;

    background: rgb(150, 100, 50);

    .card
    {
        margin-top: 50px;
        width: 800px;
        height: 600px;
        .centered;
        border: 1em solid rgb(50, 0, 0);
    }

}


We will use image to fill up the whole of card. For this, we set the position property to absolute and use nativity.jpg as the background image.

css/styles.less

.theme_nativity
{
    font-family: georgia;

    background: rgb(150, 100, 50);

    .card
    {
        margin-top: 50px;
        width: 800px;
        height: 600px;
        .centered;
        border: 1em solid rgb(50, 0, 0);
    }

    .image
    {
        width: 800px;
        height: 600px;
        position: absolute;
        background-image: url(../img/nativity.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
    }

}


Here, greeting will be a translucent black box with white text and rounded corners, set right in the middle of image. Again, position will be set to absolute for this to happen. The margin-left and margin-top properties are a function of the width and height of greeting compared to the width and height of image.

css/styles.less
.theme_nativity
{
    font-family: georgia;

    background: rgb(150, 100, 50);

    .card
    {
        margin-top: 50px;
        width: 800px;
        height: 600px;
        .centered;
        border: 1em solid rgb(50, 0, 0);
    }

    .image
    {
        width: 800px;
        height: 600px;
        position: absolute;
        background-image: url(../img/nativity.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
    }

    .greeting
    {
        width: 400px;
        height: 300px;
        position: absolute;
        margin-left: 200px;
        margin-top: 150px;
        padding: 1em;
        background-color: rgba(0, 0, 0, 0.8);
        color: rgb(255, 255, 255);
        border-radius: 5px;
    }

}


We'll even add in some stying for h1 for shits and giggles. I want it to be in italics.

css/styles.less
.theme_nativity
{
    font-family: georgia;

    background: rgb(150, 100, 50);

    .card
    {
        margin-top: 50px;
        width: 800px;
        height: 600px;
        .centered;
        border: 1em solid rgb(50, 0, 0);
    }

    .image
    {
        width: 800px;
        height: 600px;
        position: absolute;
        background-image: url(../img/nativity.jpg);
        background-position: center center;
        background-size: cover;
        background-repeat: no-repeat;
    }

    h1
    {
        font-style: italic;
        font-size: 1.5em;
        text-align: center;
    }


    .greeting
    {
        width: 400px;
        height: 300px;
        position: absolute;
        margin-left: 200px;
        margin-top: 150px;
        padding: 1em;
        background-color: rgba(0, 0, 0, 0.8);
        color: rgb(255, 255, 255);
        border-radius: 5px;
    }
}


Neat!




This didn't take long at all. We've managed to create styles for three different themes with radically different layouts, through the power of LESS.

Next

Filling in the text using ChatGPT.

Saturday, 16 December 2023

Functions that handle NULL values in databases

Not every value in a database has a well-defined value. Sometimes there is no value, or a NULL.

Empty values!

In these cases, you may need to handle these values, especially if there are calculations involved. Take the following table, TABLE_SALES, for example. There are missing values in the DISCOUNT column.

TABLE_SALES
DATETIME ITEM QTY SUBTOTAL DISCOUNT
2023-10-10 12:13:10 STRAWBERRY WAFFLE 2 20 0
2023-10-10 12:44:54 STRAWBERRY WAFFLE 1 10 0
2023-10-11 15:03:09 CHOCO DELIGHT 1 25 -2.5
2023-10-11 18:22:42 ORANGE SLICES 5 30
2023-10-12 10:56:01 STRAWBERRY WAFFLE 4 40 -3

Now let's say we tried this query.
SELECT DISCOUNT FROM TABLE_SALES

This does not present a problem.
DISCOUNT
0
0
-2.5

-3

But what if we wanted to use it as part of a calculation? You would have situations where we tried to add NULL values to the value of SUBTOTAL.
SELECT (SUBTOTAL + DISCOUNT) AS NETT FROM TABLE_SALES

Functions to handle NULL values

There are functions to handle these cases. They are named differently in different database systems. In SQLServer, it's IFNULL(). In MySQL, it's ISNULL(). In Oracle, it's NVL().

In all of these cases, two arguments are passed in. The first is the value that could be NULL. The second is the value to substitute it with if the value is NULL. Thus, for Oracle, it would be...
SELECT (SUBTOTAL + NVL(DISCOUNT, 0)) AS NETT FROM TABLE_SALES

This is nice and neat, but we can do better. The problem here is portability. If you had to move your data from Oracle to MySQL, for example, you would have to change all instances of NVL() to ISNULL().

The COALESCE() function

COALESCE() is a function that exists in all of the databases mentioned above. How does it work?

Well, you slip in any number of arguments to the COALSECE() function call, and the function will return the first non-NULL value. Thus...
COALSECE(NULL, NULL, 0.5, 1, NULL, 8)

.... will return this.
0.5

So if we did this...
SELECT (SUBTOTAL + COALESCE(DISCOUNT, 0)) AS NETT FROM TABLE_SALES

...it would return this. And you would be able to use that same function anywhere!
NETT
20
10
23.5
30
37

Finally...

Handling NULL values is important. Whether you choose to handle them at the data entry level (not allowing NULL values in a column) or in a calculation (using the COALESCE() function), at some point you have to handle them. I hope this helped!
NULL and forever,
T___T

Tuesday, 12 December 2023

Spot The Bug: The Baby Name Dataframe Misgendering

It's a whole new episode of Spot The Bug. Time to let these bugs know who's boss!

In this episode, we will deal with some mishaps I encountered while screwing around with Python.

Just look at
all these bugs...

First of, there was a dataset of baby names that I copied from https://raw.githubusercontent.com/hadley/data-baby-names/master/baby-names.csv.
"year","name","percent","sex"
1880,"John",0.081541,"boy"
1880,"William",0.080511,"boy"
1880,"James",0.050057,"boy"
1880,"Charles",0.045167,"boy"
1880,"George",0.043292,"boy"
1880,"Frank",0.02738,"boy"
1880,"Joseph",0.022229,"boy"
1880,"Thomas",0.021401,"boy"
1880,"Henry",0.020641,"boy"
.........
.........
.........

This CSV was loaded into Python, and then fiddled with. The idea was to make two copies, do whatever I needed with those copy, and then go back to the original dataset. Unfortunately, things did not quite turn out that way. In the first copy, data_boy, for the sake of neatness, I changed the SEX column's values from "boy" to "M". Then I turned it into a subset of itself where Gender was "M". In the second copy, data_girl, I did the same thing, changing "girl" to "F" and only retaining rows where the value was "F".
import pandas as pd

data = pd.read_csv("babynames.csv")

data_boy = data
data_girl = data
data_boy.replace(to_replace = "boy", value = "M", inplace=True )
data_boy = data_boy[(data_boy.sex == "M")]
data_girl.replace(to_replace = "girl", value = "F", inplace=True )
data_girl = data_girl[(data_girl.sex == "F")]

So far so good.
data_boy.head()


data_girl.head()


Then I noticed that there were boys named "Flora" in the dataset.
.........
.........
1880,"Erasmus",4.2e-05,"boy"
1880,"Esau",4.2e-05,"boy"
1880,"Everette",4.2e-05,"boy"
1880,"Firman",4.2e-05,"boy"
1880,"Fleming",4.2e-05,"boy"
1880,"Flora",4.2e-05,"boy"
1880,"Gardner",4.2e-05,"boy"
1880,"Gee",4.2e-05,"boy"
.........
.........

I decided to do a little filtering in the original dataframe, data, to see how many such rows there were.
data_flora = datadata_flora = data_flora.query('name == "Flora" & sex == "boy"')

But there were no rows!
data_flora.head()

What went wrong

When I made the first copy, data_boy, and then removed all "M" data, the original dataframe, data, mirrored those changes. Same for data_girls. Thus when I made the third copy from the original, it would only have "M" and "F", not "boy" and "girl"!

Why it went wrong

It was really simple, as it turned out. I had used the equality operator to copy the dataframe data, erroneously thinking that this was how it was done. Instead, all it did was make a shallow copy of data. Any changes to data_boy, data_girl and data_flora would be reflected in data.

How I fixed it

What I should have done was use the copy() method.
data_boy = data.copy()
data_girl = data.copy()
data_boy.replace(to_replace = "boy", value = "M", inplace=True )
data_boy = data_boy[(data_boy.sex == "M")]
data_girl.replace(to_replace = "girl", value = "F", inplace=True )
data_girl = data_girl[(data_girl.sex == "F")]

data_flora = data.copy()
data_flora = data_flora.query('name == "Flora" & sex == "boy"')

And there it was!
data_flora.head()

Moral of the story

Just because it works a certain way in other programming languages, does not mean jack. Well, not always, anyway. Some programming languages do things a certain way out of necessity.

The equality sign was a perfectly valid way of copying a dataframe if you want changes in either copy and original to reflect in both. But if you want an independent copy, you need to use the copy() method.

copy() that?
T___T

Thursday, 7 December 2023

Whose Fault Is It, Really?

Assigning blame is often the simplest task in the modern professional workplace. Pointing fingers comes almost second nature when something goes wrong.

Sometimes, it's meant to be an exercise in pinpointing target areas that need to be addressed. And that's a good thing; problems can't be solved if they aren't first identified and acknowledged.

Other times, it's pure animal instinct. We tell ourselves that we just want to solve the problem, but the problem could actually be ourselves. And pointing those fingers elsewhere is more self-preservation. It's a cop-out, the refuge of the weak.

The finger-pointing game.

The tragedy is that Management isn't immune to this; quite often, they're the ones most guilty of this. Sure, I could fill pages talking about the bosses I've encountered who simply refused to accept responsibility for things going south. And arguably, their sin is greater. After all, the higher up the ladder you are, the greater the responsibility.

However, in the spirit of this blogpost, I'm going to direct this at myself.

The last few years have been the first time in my long and storied career that I was ever in a position of leadership, in the sense that people looked to me for guidance. And I'm not proud to say there were some times I fell short, especially when it came to accepting responsibility. In these cases, my first instinct was to be annoyed with my co-workers for screwing up.

Case #1: The Painfully Simple Mistake

One of these cases happened when a co-worker sent me a CSV data file to upload into a system I had written. Now, mistakes in data are common, and what can I say, they happen. This co-worker sometimes sent me files with hundreds, sometimes a couple thousand rows, and if there were mistakes in the data, I shrugged it off, quietly cleaned it up and got on with it.

The problem was, the latest CSV file had only five rows in it. And all of them had the same mistake. Now if a thousand row CSV file has mistakes, I'm inclined to be forgiving. But if you can't get five lines of data right, this is not something that can be explained away by incompetence, because my opinion is that nobody is that incompetent. Rather, this tells me that the guilty party has lost interest in staying employed.

Only five!

Thus, I had to be very firm in my communication. We were two years into using this system, and mistakes like these just were not acceptable, especially not when the mistake in question happened in a five row CSV file. I felt like an asshole after that, because there is no way to point all of that out without insulting the other party's competence, no matter how politely and charitably the email is worded. I simply don't have the soft skills for that level of diplomacy.

But then arose the question: whose fault is it, really?

I could have said something when mistakes were occurring in bigger files. That way, I could have been understanding but firm. Instead, I chose to clean up quietly and the result was that they got comfortable thinking they could just hand me rubbish and I would just make it work. In effect, I let the situation escalate till there was no way for me to be nice about it. Whose fault was it that I'd been painted into that corner? Mine. I have to accept some degree of culpability.

Case #2: The Lack of Understanding

There was a certain tech process that needed to be done yearly. One of my colleagues, further down the hierarchy, was tasked with carrying out the process. Towards the deadline, there was a bunch of emails as he posed certain questions regarding the process, to different departments. This would normally be my area of expertise, but being swamped with more urgent matters, I elected to spend my time on those matters and let others answer those queries.

A week from the deadline, I found that there was a mistake in the process. My colleague suggested remedies, but his suggestions proved to me that while he had memorized the process, he did not actually understand the process and what had gone wrong. Consequently, his suggestions would never have worked. I made my exasperation known.

Again, whose fault is it, really?

Did he do anything wrong? Technically, yes, due to an unexpected variation in the conditions, he proceeded to carry out the process incorrectly. He had failed to understand the process. But see, understanding the process wasn't his job. It was mine. At my level, as the undisputed programming expert in that very non-technical company, I was supposed to be providing the proper guidance. Understanding the process was above his pay grade. At his pay grade, all he could actually be expected to do was show up to work on time, do his job, and leave on time.

I should know. I've been where he is. The fact that I'm no longer at that position was because when I was in that position, I did more than was expected of me. However, that should be an individual choice. He did his job. It was me who was guilty of negligence. And being in the position I am now, I need to own it.

The crown's heavier
than it looks.

They say "heavy is the head that wears the crown". Well, what I was wearing wasn't exactly a crown, but it had been my responsibility, as head of that department, to either ensure that those executing the process understood the process, or step in before something went wrong. I had done neither. This was my screw-up.

And if I were to honestly examine my own exasperation, I was actually more annoyed with myself than with him. And rightfully so.

The Takeaway

I've often pissed on people in positions of authority who seem incapable of acknowledging their own culpability in any sort of mess that occurs. This has shown me, upon introspection, that I'm not automatically immune to this foible. It takes both conscious effort and humility.

Titles aren't just words. The onus is on the holder of said title, to provide that title, via action, some legitimacy.

Own your shit, yo.
T___T

Sunday, 3 December 2023

Ode to my Lenovo

Two weeks back, my trusty Lenovo Z51-70 laptop gave up the ghost. It was showing signs well before that; consistently dropping my network connection, lagging severely when saving files and most tellingly, a noticeable flicker on the monitor. Can't say it didn't give me fair warning when on Saturday, it refused to power on.

Now, that was a sentimental moment.

Rest well, my friend.

I first obtained this flat white beauty during a Christmas Sale in 2014, when I was about to begin a Specialist Diploma in Mobile Apps Development. While my company at the time had issued me a decent notebook, I didn't feel comfortable using company property for my personal enterprise. I had a personal laptop, but it was on its last legs. Thus, I went for an i5 Intel Core that would meet my programming needs.

2015 to 2017

2015 was the second semester of my Specialist Diploma in Mobile Apps Development. With Java being the operating language, I installed Eclipse and the Java Runtime Environment. I also had NodeJS installed, and wrote apps in jQuery Mobile to deploy onto the mobile phone. This would perhaps be the most serious and intensive period of using the Lenovo.

Mobile app development.

Somewhere along the way, in addition to my steady diet of HTML, CSS and JavaScript experimentation, I experimented with things QBasic, LESS and AngularJS. An odd mix, I know, but I was having fun.

MeteorJS came along for me in early 2017. The lessons I learned here would help me years later when I experimented with ReactJS.

2017 to 2019

This was the period of my life where I settled down into a regular nine-to-five. I still experimented on weekends, writing D3 and ReactJS code. It was also the time I picked up Ruby and Rails, for whatever reason.

Like a stallion.

The Lenovo handled it all like a champ. It was tireless, inexhaustible, like a stallion in its prime.

Around the latter half of this period, I started working on my ACTA. Whatever work I needed done, I did it on my trusty laptop.

At some point during this period, I also got married. Late nights at the console were no longer viable, not if I wanted to sleep in the same bed as my wife. Still, I squeezed in whatever time I could.

2019 to 2023

This was the year the COVID-19 pandemic hit, and marked one of the longest durations my wife and I were geographically separated. During this time, my work on the Lenovo intensified, with me trying out Highcharts and VueJS.

During this period, however, I had acquired a MacBook and was gradually transitioning away from my faithful Lenovo which was starting to show its years.

Getting pretty old.

It was also when I embarked on my yet another Specialist Diploma, this time in Data Analytics. The MacBook handled Python and Tableau, but only my Lenovo could run Spotfire and  Power BI. It creaked and groaned, but ultimately delivered.

Why is this particular laptop special?

Why do I feel so strongly about this fella? It's not the first laptop to die on me, after all. I've lived more than forty years. But the last nine years of my professional life have been pretty tumultuous, and also represented the bulk of my career advancement and tech education, spanning two Specialist Diplomas and a Higher Certificate. This Lenovo was with me through all of it. I played no games on it. All I did was write code, and run code. (Some of that code were games I wrote, but we're just splitting hairs at that point!)

Much of whatever important files I have are saved on the cloud - on Google Drive, GitHub and Trello. Thankfully, I can resume my work on a new machine with little trouble. But I'll always treasure my time with this machine.


Rest In PC,
T___T

Sunday, 26 November 2023

Web Tutorial: The Self-affirmations WordPress Plugin (Part 4/4)

Having written a function to generate email, we will now send it!

Email in WordPress can be a tricky issue. Sure, we can use the native function wp_mail(). However, it is, half the time, going to fail depending on your server settings which you do not always have control over. Thus, it's advised to use an email plugin.

The one I am using for this, is WP Mail SMTP. Feel free to use your own. I used my email account at Google to configure WP Mail SMTP. Again, you may use any other method to configure it; this just happened to be the most convenient for me. Just read through the documentation, do a test send, and Bob's your uncle!


Once you have this out of the way, let's focus on writing the job that will send email on a regular basis. This is the function tt_selfaffirmations(). We begin by declaring list, and using it to store the result returned by running tt_get_readytoreceive(). Then we iterate through each element of list. There should be only one, though if you haven't reset the LAST_SENT column in your Oracle APEX database, it will be 0.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {

    }

}


In the Foreach loop, we get the string name by concatenating the first_name and last_name properties of the current element, with a space in between. Then using the value of name, and the current element's email, gender and dob properties, we get the email array by running tt_generate_mail().

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

    }
}


In this If block, we run wp_mail() using the email property, the title property of email and the body property of email with an "unsubscribe" line appended to the end. The rest of the arguments aren't that crucial. See the wp_email() function description here.

In essence, we're checking if the email was sent with no error.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {

        }

    }
}


If so, we should run the tt_set_lastsent() function to set the LAST_SENT column of that record to today's date so the email won't get sent a second time.

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {
            tt_set_lastsent($l->email);
        }
    }
}


If you run the Test job link, this is what you should see. tt_get_readytoreceive() was run, followed by tt_get_terms() followed by tt_generate_email().


More importantly, this should appear in your inbox!


So the sending of email is good to go. Time to set up the CRON job. For this, I use the WP Crontrol plugin. After activating the plugin, go to Tools, then Cron Events.


Once you click on Add New, you should be redirected to this interface. We'll use "cron_selfaffirmations" as the hook name, and set it to run once daily.


Once you confirm, you should see it in the list.


What next? Well, go back to your plugin file. Add this line. It basically uses the add_action() function to link the tt_selfaffirmations() function with the cron_selfaffirmations hook. Thus when the CRON job kicks in, it will run tt_selfaffirmations()!

wp-content/plugins/tt_selfaffirmations/tt_selfaffirmations.php
function tt_selfaffirmations()
{
    $list = tt_get_readytoreceive();

    foreach($list as $l)
    {
        $name = $l->first_name . " " . $l->last_name;
        $email = tt_generate_mail($l->email, $name, $l->gender, $l->dob);

        if (wp_mail($l->email, $email["title"], $email["body"] . "\n\nTo unsubscribe to the Self-affirmations Mailing List, please reply to this email with the subject 'UNSUBSCRIBE'.", "", [] ))
        {
            tt_set_lastsent($l->email);
        }
    }
}

add_action("cron_selfaffirmations", "tt_selfaffirmations");


If you reset your LAST_SENT column in the Oracle APEX database, the email will be sent when the job next runs. Set it to tun 5 minutes from now and see if it works!

That's all!

Thank you for staying with me! This was fun, y'all. And I didn't even have to write all that much code!


Your Greatest Fan,
T___T