DEV Community

Rashmi.N.S
Rashmi.N.S

Posted on

The Day My AI Taught Me That Passing Tests Means Nothing

I never set out to build VentureTwin AI as just another chatbot. The idea was much bigger than answering questions. I wanted to build a digital twin that could understand a student's entire journey—their projects, certifications, technical skills, academics, achievements, and career interests—and use all of that to provide meaningful career guidance.

Instead of simply recommending jobs based on keywords or certificate counts, I wanted the system to answer a much harder question: What is this student actually good at, and where are they most likely to succeed?

To make that possible, I designed the platform as a collection of independent intelligence modules. The Certificate Intelligence module retrieved and verified certifications. Resume Intelligence evaluated technical skills and experience. Project Intelligence analyzed project metadata such as technology stack, complexity, implementation, and impact. Each module produced its own output, which was then passed to a scoring engine that generated a Career Readiness Score.

Individually, every module worked exactly as expected.

Then I compared two student profiles.

The first student had completed more than 20 online certifications but had only a couple of basic projects. The second student had fewer certifications, but had built full-stack applications, worked with AI models, contributed to open-source projects, and actively participated in hackathons and technical competitions.

I expected the second profile to receive stronger recommendations.

It didn't.

Instead, the student with the larger collection of certificates consistently received the higher Career Readiness Score.

At first, I assumed something was broken.

I traced every stage of the scoring pipeline, inspected API responses from every module, verified the PostgreSQL records, and even recalculated the scores manually. Every value matched. Every API response was correct. The database contained exactly what it should. The scoring engine was behaving exactly as I had programmed it.

Technically, nothing was wrong.

But the recommendations still didn't make sense.

That's when I realized I wasn't dealing with a software bug.

I was dealing with a feature engineering problem.

The scoring model assigned fixed weights to every feature, but those features existed on completely different scales. Certifications were represented as simple counts, while project quality depended on more nuanced measures like technical complexity, implementation depth, technology stack, and real-world impact.

Because certification counts naturally produced much larger numerical values, they dominated the final score—even when another student demonstrated far stronger engineering ability through real projects.

The algorithm wasn't biased because of a coding mistake.

It was biased because the features themselves weren't comparable.

To understand what was happening, I visualized the contribution of every feature to the final score. The graphs made the problem obvious. Certification-related values were overwhelming the scoring process, drowning out signals from project quality, technical depth, and practical experience.

Reducing a few weights would only have hidden the symptom.

Instead, I redesigned the entire scoring pipeline.

First, I normalized every feature so that values from different modules existed on the same scale. Then I replaced the fixed-weight approach with a weighted scoring strategy that emphasized project complexity, real-world implementation, consistency of learning, technical depth, and measurable impact instead of simply counting certificates. Finally, I introduced confidence checks to ensure that no single feature could disproportionately influence the final recommendation.

The difference was immediate.

Students with strong engineering experience were no longer penalized for having fewer certifications. The recommendations became balanced, explainable, and much closer to how an experienced mentor would evaluate a student's profile.

That bug completely changed how I think about building AI systems.

It's easy to assume that improving an AI model means choosing a better algorithm or increasing its accuracy. In reality, some of the hardest engineering problems appear long before model training begins.

If your features don't truly represent what you're trying to measure, even a perfectly functioning model can produce misleading decisions.

Since then, every time I design an AI system, I ask myself one question before writing any code:

Am I teaching the model to recognize real ability, or am I simply teaching it to count?

Sometimes the hardest bug isn't hidden in the code.

It's hidden in the logic that defines what the AI believes intelligence looks like.

Top comments (4)

Collapse
 
merbayerp profile image
Mustafa ERBAY

I really like the core lesson here: a system can be technically correct while still producing poor recommendations because the scoring logic doesn’t reflect the real-world objective.

One nuance I’d add is that this isn’t necessarily an AI-specific problem. From your description, the issue was primarily in the feature engineering and scoring design rather than the model itself. A weighted scoring engine will faithfully optimize whatever objective we define—even if that objective is an imperfect proxy for real ability.

I also like that you didn’t stop at tweaking a few weights. Normalizing features and limiting the influence of any single signal is a much more robust approach. I’d probably go one step further and validate the scoring model against evaluations from experienced mentors or historical outcomes. That helps answer the most important question: Does the score actually predict what we care about, or does it just produce internally consistent numbers?

Passing tests proves the implementation is correct. It doesn’t prove the system is measuring the right thing. That’s an important distinction for any decision-support system.

Collapse
 
rashmidebug43 profile image
Rashmi.N.S

Thanks. I really liked your point about changing layers instead of changing thresholds—that describes the debugging process much better than simply saying "I normalized the scores."

You're right that every check I had was validating whether the data was correct, not whether the scoring logic represented reality. Visualizing each feature's contribution was the moment I stopped asking "Are these values correct?" and started asking "Why is this decision being made?"

I also like your suggestion about the confidence guards. Right now they're designed to prevent any single feature from dominating, but intentionally creating synthetic edge cases that must trigger those guards is a much stronger way to validate them. A safeguard that never fails is difficult to distinguish from one that never actually works. That's definitely something I'll be adding as the project evolves.

Collapse
 
merbayerp profile image
Mustafa ERBAY

Exactly. That’s the distinction I found most interesting as well. A lot of systems validate data correctness, but far fewer validate decision quality. I think adding synthetic profiles that are expected to trigger the safeguards will make those confidence checks much more meaningful over time. Thanks for the thoughtful discussion!

Collapse
 
0012303 profile image
Alex Spinov

The part of this I would put at the top is not the normalization. It is how you found it: you visualized each feature's contribution to the score. That is a different layer from the one every check you ran lived on. Tracing the pipeline, inspecting the API responses, verifying the Postgres records, recalculating by hand — all of those ask whether the values are right, and every one of them was right. The defect was in what the values meant relative to each other, and no amount of correctness at the value layer can see that. A check has to hit the same layer the claim lives on, and yours only started working when you changed layers.

I hit the same shape twice in a row this week on something far dumber than a scoring model. I had a detector meant to catch markdown tables leaking into published HTML as raw text. Version one flagged any line with two or more pipe characters, and it flagged a page I knew was clean — because that page contains the code parseInt(x) || 200, and a logical-or is two pipes. So I tightened it: require a pipe at both ends of the line, or a row of dashes. That was worse. It flagged all seven pages, the known-clean one included, because a row of dashes appears inside <pre> blocks where an ASCII table is exactly what belongs.

Both versions returned a plausible count, in opposite directions, and the count was never going to tell me which one was true. What resolved it was printing the matched lines together with their parent tag instead of printing the number: [tag=p] is a leak, [tag=pre] is normal code. Same move you made. The threshold was never the knob. The layer was.

Which leads to the one question I would ask about the fix. Those confidence checks that stop any single feature from dominating — do they have a profile they are required to fail? A synthetic student built to trip them, run on every deploy? Right now they have never fired in anger, and a guard that has never rejected anything looks exactly like a guard that cannot reject anything. That is your own thesis one level up: your modules were each individually correct too.