How the training data is made, kept honest, and cleaned.
Synthetic data & random seeds PRNG = pseudo-random number generator #
Computer-generated records that stand in for real data. A seed makes the “random” sequence repeat exactly, so everyone who runs the script gets the same dataset.
Where it is used in this project
generate_naive (V1) uses a private random.Random(seed). generate_hardened (V2) seeds the global generator but saves and restores its state with getstate()/setstate(), so calling it does not change randomness elsewhere. Both build 4,000 rows. Timestamps use datetime.now(), so the dates are not reproducible even though everything else is.
How it helps
There is no real customer data, so the project can manufacture a deliberately easy dataset (V1) and a hard one (V2) on demand, with no privacy issues, and re-create either exactly.
Shortcomings & limits
Synthetic text is only as rich as its templates: V2 has just 212 distinct sentences in 4,000 rows. Models learn the generator, not real customers, which is why V3 and the real-data validation exist.
Theory & history
A PRNG is a deterministic algorithm that produces numbers that look random. Starting it from the same seed always yields the same sequence. That is what makes an experiment reproducible.
Python offers a global generator (random.seed, random.random) and private generators (random.Random(seed)). A private one cannot be disturbed by other code.
Template-based text generation slot filling #
Writing a sentence once with {placeholders} and filling each slot with a random choice, so one template yields many different sentences.
Where it is used in this project
CRITICAL_TEMPLATES holds sentences such as "App keeps crashing during {step} step…"; CRITICAL_FILLS maps step to ["checkout", "payment", …]. _fill swaps each placeholder for random.choice(options). The ambiguous templates deliberately mix positive and negative words.
How it helps
V1 picked from 9 fixed sentences (9 unique texts in its database). Templates lift that to 212 in V2, so keyword memorisation no longer gives a perfect score.
Label noise simulated annotator disagreement #
Deliberately wrong labels, mimicking real people who disagree or make mistakes when labelling data.
Where it is used in this project
About 3% of rows (label_noise_rate=0.03) get label = 1 - label and urgency_score = 1.0 - urgency_score. Only V2 has this; V1 has none.
How it helps
With perfectly clean labels any model can reach a flawless score. Noise puts a ceiling below 1.0 so a perfect metric becomes a warning sign instead of a trophy.
Shortcomings & limits
The noise here is random and symmetric. Real label noise is usually systematic (ambiguous cases flip more).
Overfitting & memorisation #
A model that fits its training data so closely that it fails on new data. Perfect scores on a tiny, repetitive dataset are the classic symptom.
Where it is used in this project
V1 is the textbook case. Its 4,000 rows contain only 9 distinct sentences, so the baseline classifiers score macro-F1 = 1.0 and K-Means reaches silhouette = 1.0 with inertia ≈ 0. The model memorised the sentences. V2 changes the data (not the code) and the numbers become realistic: F1 ≈ 0.91, silhouette ≈ 0.33.
How it helps
Recognising the V1 result as memorisation rather than success is the central lesson of the project.
Theory & history
- Detect it with a held-out test set and by comparing train vs validation error.
- Fight it with more varied data, regularisation (dropout, weight decay), early stopping, or simpler models.
Data leakage & the train / validation / test split #
Leakage happens when information from the test data sneaks into training, making results look better than they are. The cure is to split first and fit everything on the training part only.
Where it is used in this project
The deep models shuffle indices with np.random.RandomState(42).permutation(n) and slice 15% test, 15% validation, 70% train. The vocabulary is built from train_texts only. V3 reuses the same seed and fractions, so V2 and V3 are scored on the identical held-out rows. The classical models use train_test_split(test_size=0.2, stratify=…).
How it helps
Makes the V2-versus-V3 comparison fair and keeps test numbers honest.
Shortcomings & limits
data_insights.py scores all 4,000 rows, including training rows, so its confusion matrix and “best threshold” are in-sample and optimistic.Theory & history
Typical split: train (learn weights), validation (tune and early-stop), test (touch once at the end).
Class imbalance & stratified sampling #
When one class is rare (critical feedback is 15% of V2's generated rows), a model can score high accuracy by always predicting the common class. Stratified splitting and class weights keep the rare class visible.
Where it is used in this project
stratify=df[label_col] keeps the same critical/non-critical ratio in train and test. Both baselines use class_weight="balanced", and the headline metric is macro-F1 so each class counts equally.
How it helps
Accuracy alone would hide a model that ignores the minority class.
Regular expressions (regex) a pattern mini-language for text #
A compact notation for describing text patterns: \s+ is “one or more whitespace”, #(\w+) is “a hash followed by word characters, and remember the word”.
Where it is used in this project
TextIngestor defines six compiled patterns as class attributes, including the back-reference (.)\1{2,} that shrinks “sooooo” to “soo”. The copilot also uses regex: _ROOT_CAUSE_KEYWORDS (case-insensitive keyword rules) and _parse_json_response (strip markdown fences, grab the first {…}).
How it helps
Cleaning 4,000 texts costs about 0.024 ms per record end to end (read + clean + insert, from training_summary.json).
Shortcomings & limits
Regex matches characters, not meaning. _ROOT_CAUSE_KEYWORDS treats “down” inside “download” as an outage, and ignores sentiment entirely.
Theory & history
- Capture group
( ) remembers a match; \1 in a replacement or pattern refers back to it. - Compiling (
re.compile) pre-builds the pattern so it is not re-parsed on every call.
History. Formalised by Stephen Kleene in the 1950s as “regular events”; Ken Thompson brought them into text editors in the late 1960s.
Text normalisation (cleaning) #
Turning messy raw text into a consistent form (lower-case, no HTML or URLs, no stray punctuation) so models see the same word the same way every time.
Where it is used in this project
clean_text runs a fixed pipeline of substitutions, in an order that matters: lower-case, remove HTML, URLs and @mentions, keep the word of a #hashtag, shrink repeated letters, drop other punctuation, collapse spaces. Both raw_text and cleaned_text are stored in the warehouse. It is called from 7 places outside the tests. The BiLSTM and TF-IDF get cleaned text; V3 deliberately gets raw text because its tokenizer uses casing and punctuation (“!!!”) as signal.
How it helps
One shared function guarantees training and live scoring clean text identically, which prevents “train/serve skew”.