DEV Community

Cover image for Why Did I Spend So Much Time Building a “Driving Personality Test”?
tosane932
tosane932

Posted on

Why Did I Spend So Much Time Building a “Driving Personality Test”?

Updated on July 7, 2026

A truck driver with 120 hours of programming study turned more than seven years of field experience into a fully browser-based application using sql.js.

🛻 Introduction

Hello from Japan! 🇯🇵

“Seven hours to build one quiz?”

Some people may think exactly that after reading the title.

If the goal were simply to display a few choices and let users click them, the project could probably have been completed in one or two hours.

However, what I built was not a simple multiple-choice quiz.

I am an active truck driver who operates both:

  • Large 20-ton trucks
  • Medium 7-ton trucks

During more than seven years of professional driving, I have repeatedly encountered situations that made me think:

This is the kind of behavior that leads to an accident.

I converted those real-world observations into a Driving Personality Test consisting of:

  • 50 questions
  • Five categories
  • Five answer levels
  • Scores ranging from 0.0 to 2.0 points per question

In this article, I will explain why designing this test took so much time, using actual code from the project.

You can try the application here:

https://tosane932.github.io/driver-personality-test/

⚠️ Estimated completion time: approximately 20 minutes

The application itself is lightweight and responds quickly.

The estimate includes the time required to carefully read the questions and answers.

Please complete it at your own pace.


🌐 Deployment and Cache Busting

This application is a serverless single-page application built entirely with frontend technologies.

Because there is no backend server, an old JavaScript asset such as app.js may remain cached in a returning user's browser.

This creates a risk that a newly deployed version will not appear immediately.

To solve this problem, I introduced cache busting through the HTML file.

Cache Behavior: Before and After

Whenever I update app.js, which contains the result logic and UI text, I also update the version query parameter in index.html.

<!-- Before:
The browser may keep using the old cached file. -->
<script src="app.js"></script>

<!-- After:
Changing the query parameter forces the browser
to request the updated file. -->
<script src="app.js?v=20260707"></script>
Enter fullscreen mode Exit fullscreen mode

Cache busting implementation

This simple version parameter reduces the risk of users continuing to run outdated JavaScript after a deployment.


💡 Design Focus: Fully Randomized Questions and Safe Data Handling

1. Reproducing the Unpredictability of Real Roads

Real roads contain unpredictable situations.

To recreate that uncertainty, I designed the application to completely shuffle all 50 questions whenever the page is reloaded.

This reduces order bias and prevents users from memorizing the sequence.

The goal is to make each session feel closer to a first-time reaction test.

2. Randomization with the Fisher–Yates Algorithm

I implemented my own shuffle function in app.js using the Fisher–Yates algorithm.

The algorithm is widely used because it can shuffle an array efficiently and fairly.

Its time complexity is:

O(n)
Enter fullscreen mode Exit fullscreen mode

This means the operation remains efficient even if the number of questions grows.

I also used the spread operator to create a copy of the original data before shuffling it.

function shuffleQuestions(questions) {
    const shuffled = [...questions];

    for (let i = shuffled.length - 1; i > 0; i--) {
        const randomIndex = Math.floor(Math.random() * (i + 1));

        [shuffled[i], shuffled[randomIndex]] = [
            shuffled[randomIndex],
            shuffled[i]
        ];
    }

    return shuffled;
}
Enter fullscreen mode Exit fullscreen mode

This prevents the original data in testData.js from being modified.

In logistics terms:

I do not damage the warehouse master data.

I create a safe copy for delivery and sort only that copy.

This was my way of applying risk management to data handling.

3. Ripple Effects for Clear User Feedback

When a user taps an answer, a ripple animation spreads outward from the tapped position.

I created this effect using CSS animations and JavaScript.

The button executes both:

  • createRipple
  • handleAnswer

These responsibilities are kept separate so that visual feedback does not interfere with the answer-processing logic.

button.addEventListener("click", (event) => {
    createRipple(event);
    handleAnswer(option);
});
Enter fullscreen mode Exit fullscreen mode

The ripple effect gives users immediate feedback:

The tap was received successfully.

Because the test is designed around quick judgment, I wanted the interface to reduce accidental input and avoid interrupting concentration.


⛑️ What Inspired the Project

The previous day, I was discussing an idea with AI:

What if I collected and analyzed daily work reports?

After sleeping on it, I had a concern the next morning.

Daily reports are company documents.

Would collecting and analyzing them create copyright or information-management risks?

I had already encountered a similar structural obstacle while building my previous application, puoppo.

The original flow was:

Scrape a specific website
        ↓
Repeatedly blocked by the website
        ↓
Change route and use RSS feeds
        ↓
Collect approximately 100 summaries
        ↓
Send them to Gemini
        ↓
Visualize public trends and frustrations
Enter fullscreen mode Exit fullscreen mode

Instead of continuing toward a blocked or risky route, I found an alternative route that achieved the same objective.

That same thinking applied here.

Rather than using internal company reports, I decided to create fictional driving scenarios.

This allowed me to explore professional judgment without using private or copyrighted company documents.

My conclusion was:

If one route is legally or ethically unclear, it is better to reach the same objective using an open and original subject.

That idea became the Driving Personality Test.

Instead of analyzing individual reports, I created fictional situations based on my own field experience.

This avoided the copyright and information-management problem at the structural level.


📋 Development Records

The complete development history is available here:

https://github.com/tosane932/driver-personality-test

🚶 Development Timeline

1. The Idea Appeared at 9:00 AM and the Basic Structure Was Completed

I also updated the README.

Time: approximately one hour

Initial structure

2. Updated the Evaluation Logic and Result Text

I also:

  • Split the project into separate HTML, CSS, and JavaScript files
  • Added a reset link to the result screen
  • Improved maintainability

Time: approximately one hour

File separation

Result screen update

3. Changed the Scoring Criteria and Test System

Time: approximately one hour

Scoring changes

Test system changes

4. Break, Lunch, Driving Family Members, and Housework

🍙 🧺 🚘️ 🧹

5. Completed Version 1 of the Driving Personality Test

Time: approximately one hour

Version 1 completed

6. Revised the Evaluation Logic

Time: approximately one hour

Evaluation logic update

7. Prevented LocalStorage Key Conflicts

I created a unique storage key for the application.

Time: approximately one hour

LocalStorage key update

8. Renamed the System from DPQ to DPT

I changed:

DPQ → DPT
quiz → test
Enter fullscreen mode Exit fullscreen mode

This required replacing related names throughout the project.

Time: approximately one hour

System rename

By this point, the development work had taken approximately seven hours.


🗡️ The Main Strength of This System

The core value of this test is not the interface.

It is the content of the questions.

All 50 questions were designed around five categories.

1. Encounters with vehicles and pedestrians on public roads
   10 questions

2. Schedule pressure and operational management
   10 questions

3. Work sites, cargo handling, and loading bays
   10 questions

4. Severe weather and physical limitations
   10 questions

5. Professional composure and consideration for others
   10 questions
Enter fullscreen mode Exit fullscreen mode

Why Five Categories?

These five categories cover both external risks and internal resources.

External Risk Management

  • Other road users
  • Work sites
  • Physical environments

Internal Resource Management

  • Scheduling pressure and impatience
  • Mental control and professional pride

Each category contains ten questions, giving all five categories equal weight.

This creates a balanced 50-question structure.

From my personal professional experience, removing any one category would make the test incomplete as a driving-personality assessment.

This structure is therefore not random.

It was designed backward from the behaviors I believe are necessary for professional driving.


A Five-Level Score Instead of Right or Wrong

Many safety-driving checklists use only two values:

  • Correct
  • Incorrect

However, real-world decisions are not always that simple.

There is often a gap between:

A decision that is legally correct but socially awkward at the work site

and:

A decision that fits the atmosphere of the work site but carries greater risk

To represent this gradient, every answer is assigned one of five scores:

2.0
1.5
1.0
0.5
0.0
Enter fullscreen mode Exit fullscreen mode

This allows the test to measure tendencies rather than simply labeling answers as right or wrong.


🔧 Technical Implementation

The technology stack is intentionally simple:

  • HTML
  • CSS
  • Vanilla JavaScript
  • sql.js
  • WebAssembly-based SQLite running in the browser

The application does not depend on an external frontend framework.

1. Data Structure for Five-Level Scoring

All 50 questions use the same scoring structure.

{
    category: "Encounters with vehicles and pedestrians on public roads",

    question: "You meet an oncoming passenger car on a narrow road...",

    options: [
        {
            text: "Move to a safe waiting position if possible...",
            score: 2.0
        },
        {
            text: "Stop with the hazard lights on...",
            score: 1.5
        },
        {
            text: "Wait until the other driver moves or conditions change...",
            score: 1.0
        },
        {
            text: "Move forward little by little while waiting...",
            score: 0.5
        },
        {
            text: "Sound the horn aggressively...",
            score: 0.0
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

I standardized every question to the same five-level scale to keep the later aggregation logic simple.

If different questions used inconsistent score intervals, the total score could become distorted.

For example, one question might accidentally carry more weight than another.

By keeping all 50 questions symmetrical, I could normalize the final result to a clear scale:

0 to 100 points
Enter fullscreen mode Exit fullscreen mode

2. Designing the Start, Test, and Result Screens

The earliest version immediately displayed the first question.

Later, I decided to add:

  • An information-based start page
  • A history section
  • A result screen

The application therefore changed to a three-screen structure.

function showScreen(screen) {
    document.getElementById("start-screen").style.display =
        screen === "start" ? "block" : "none";

    document.getElementById("test-container").style.display =
        screen === "test" ? "block" : "none";

    document.getElementById("back-to-top-link").style.display =
        screen === "test" ? "block" : "none";

    document
        .getElementById("result-area")
        .classList.toggle("show", screen === "result");
}
Enter fullscreen mode Exit fullscreen mode

At first, I directly changed style.display inside separate onclick handlers.

As the number of screens increased, conditions became scattered across the code.

The following responsibilities existed in different places:

  • Hide the start screen
  • Show the test screen
  • Display or hide the back link
  • Show the result screen

Changing one part sometimes broke another screen.

In one case, I wrote the result screen's display property in multiple places.

The result page then became permanently invisible.

To solve this, I centralized the screen state inside one function:

showScreen(screen)
Enter fullscreen mode Exit fullscreen mode

Now the application controls the active screen from one location.

For result-area, I also stopped changing the display property directly in JavaScript.

Instead, I used:

classList.toggle("show")
Enter fullscreen mode Exit fullscreen mode

This separated responsibilities:

  • JavaScript decides the state
  • CSS decides how the state looks

Adding more screens still requires new conditions, but the structure is less likely to create contradictory display states.


3. Introducing sql.js and Moving from Memory to Persistence

At first, test history existed only in an in-memory sql.js database.

// Early version:
// The database exists only in memory.

db = new SQL.Database();
Enter fullscreen mode Exit fullscreen mode

This was lightweight, but the history disappeared whenever the page was closed or reloaded.

After using the application myself, I immediately noticed the inconvenience.

I changed the design so that the SQLite database binary would be exported and stored in LocalStorage.

function saveScore(score) {
    db.run(
        `
        INSERT INTO scores (score, taken_at)
        VALUES (?, ?);
        `,
        [score, takenAt]
    );

    // Export the entire database as binary data.
    const exportedData = db.export();

    // Convert the binary array into JSON-compatible data.
    localStorage.setItem(
        STORAGE_KEY,
        JSON.stringify(Array.from(exportedData))
    );
}
Enter fullscreen mode Exit fullscreen mode

This method has a cost.

Every time a score is inserted, the entire database is exported and written to LocalStorage.

If the number of records became very large, the operation would gradually become heavier.

However, DPT stores only:

  • A numerical score
  • A date and time string

Each record is only a small amount of data.

Even after 100 test sessions, the total size should remain only a few kilobytes.

Considering the approximate LocalStorage capacity and browser main-thread performance, I judged that this design was reasonable for the current application.

If I later store larger data, such as the answer to every question, I plan to consider migrating to IndexedDB.


4. Standardizing Names from quiz to test

Early in development, I called the project a “quiz.”

Later, I decided that “test,” or more specifically “personality test,” was more accurate.

The impact of this naming change was larger than expected.

It affected:

  • Variable names
  • Function names
  • CSS selectors
  • LocalStorage keys

Examples:

quizData → testData
initQuiz() → initTest()
.quiz-card → .test-card
Enter fullscreen mode Exit fullscreen mode

The LocalStorage key also changed.

truck_driver_quiz_db_v1
Enter fullscreen mode Exit fullscreen mode

became:

truck_driver_test_db_v1
Enter fullscreen mode Exit fullscreen mode

This was not technically difficult work, but consistent naming directly affects maintainability.

The cost of renaming now was smaller than the future cost of keeping contradictory terminology throughout the codebase.


Progress in Numbers: Comparing DPT with puoppo

Project Study Time at Development Development Time Main Work
puoppo 42 hours 10 hours Started with scraping, was blocked, moved to RSS, collected approximately 100 entries, summarized them with Gemini, and visualized public trends and dissatisfaction
DPT 120 hours 7 hours Five-level scoring, 50-question evaluation logic, sql.js persistence, browser-only architecture

Looking back at the development process, puoppo was not a simple project either.

The sentence:

I switched from scraping to RSS because the scraping was blocked.

does not fully describe the actual work.

The complete pipeline was:

Collect approximately 100 RSS entries
        ↓
Send the content to Gemini
        ↓
Generate summaries
        ↓
Analyze trends and dissatisfaction
        ↓
Display the results in a web application
Enter fullscreen mode Exit fullscreen mode

The seven hours spent on DPT involved a different type of work.

puoppo was a pipeline-oriented project:

Collect external data, summarize it, and visualize it.

DPT was a structure-oriented project:

Convert my field experience into 50 questions, build evaluation logic, and complete everything inside the browser.

The number of hours alone does not explain the quality or type of work.

The important difference is how that time was used.


When I developed puoppo, I barely remembered Git or deployment commands.

Many technical terms were completely unfamiliar, and I spent a large amount of time simply understanding the workflow.

By the time I developed DPT, I understood more of the overall process.

I recognized more terms and had a clearer idea of how the files and code related to one another.

I still had difficulties, but fewer tasks felt completely unknown.

As a result, I could move more quickly while also understanding more of the code.


Summary

  • I chose a fictional personality test as a way to express my professional driving experience without using private company reports.
  • A system becomes meaningful when someone other than the developer can use it.
  • I applied one consistent five-level scoring structure to all 50 questions.
  • This prevented unnecessary complexity in the aggregation logic.
  • The move from an in-memory database to persistent browser storage came from actually using the application.
  • Standardizing names from quiz to test was a small but important investment in maintainability.
  • Fully randomized questions reduced order bias.
  • Cache busting reduced the risk of returning users receiving outdated JavaScript.
  • Centralizing screen transitions reduced contradictory UI states.
  • sql.js and LocalStorage allowed the application to remain entirely browser-based.

Let us return to the original question:

“Why did it take seven hours to build one quiz?”

Because this was not simply a page with several choices.

The work included:

  • Converting field experience into 50 questions across five categories
  • Representing the gray areas of real-world judgment with five scoring levels
  • Building separate start, test, and result screens
  • Redesigning data storage so

Top comments (0)