Timesheet Validator - Validation Rules

Version 4.3.0 | Last Updated: March 25, 2026 | ASK IT Limited
Total Rules
22
Completed
19
Pending
3
Completion
86%
Priority Rule Name Scope Condition (Logic) Output / Remark Pattern Status
Group A No Work Times Recorded — C / D empty A1→A2→A2.5: If/ElseIf chain (mutually exclusive); A2.1 = implicit exemption inside A2. Remarks scans Col G + Col H together.
GRP A1 Leave without reason (all empty)PT: ⊘ Skipped Weekday Weekday AND
• Start Time (C) = Empty
• End Time (D) = Empty
• Remarks (H) = Empty
Business Logic:
On weekdays, employees are expected to work. If no time is entered and no remarks explain the absence, this indicates unapproved leave.

Example:
Date: Jan 15 (Mon) | C/D/H: All empty → Error
dispDate & " Leave without reason (weekday, no time in C/D, no remarks in H)" Done
GRP A2 Leave without reason (missing time, no AL/SL/PH)PT: ⊘ Skipped Weekday Weekday AND
• C = Empty AND D = Empty
• Remarks (H) NOT contain:
  AL/SL/CL/ML/PL/NPL/NPSL/Public Holiday
Business Logic:
A scheduled work day with no time entries and no valid leave code is flagged as leave without reason. Both C and D must be empty for this rule to fire — the one-filled-one-empty case routes to Rule 2.7 instead.

Example:
Date: Jan 20 (Mon) | C: Empty, D: Empty, Remarks: "Doctor appointment" → Error
dispDate & " Leave without reason (weekday, no time in C/D, no AL/SL/Public Holiday in remarks)" Done
GRP A2.1 Special paid leave (CL/ML/PL)PT: ✓ Applied Weekday Saturday PH (Weekday OR Sat OR PH) AND
• C = Empty AND D = Empty
• actualHours = 0
• Remarks contain: CL or ML or PL
Business Logic:
CL, ML, and PL are special paid leave types that don't require work hours. These are exempt from "leave without reason" rules.

Example:
Date: Jan 25 (Mon) | C/D: Empty, Remarks: "CL - Family emergency" → OK ✅
(No remark - valid paid leave CL/ML/PL)
Why no remark?
This rule documents a silent pass. CL, ML, and PL keywords are listed in Rule 2's exemption conditions, so Rule 2 never fires for these entries — the system quietly accepts them with no remark generated.

In plain terms: "This employee took CL/ML/PL. No time needed, no error raised."
Done
GRP A2.5 Half-day leave without work hoursPT: ⊘ Skipped Weekday Weekday AND
• Remarks contain "(AM)" or "(PM)"
• C = Empty AND D = Empty
Business Logic:
If marking half-day leave (AM or PM), the employee should still work the other half and enter those hours in columns C/D.

Example:
Date: Feb 5 (Wed) | C/D: Empty, Remarks: "AL (AM)" → Error
dispDate & " - Half-day leave but no work hours (expected work time in C/D for other half)" Done
GRP A2.7 (FT) Incomplete time entry — one of start/end missing (FT)PT: ⊘ Skipped WeekdaySaturday (5.5-day) (Weekday OR scheduled Saturday) AND FT AND
• Exactly one of C or D is filled
Business Logic:
Both start time (C) and end time (D) must be provided together. Having one without the other is a data entry error regardless of what leave codes are present in G/H.

Examples:
Date: Jan 20 (Mon) | C: 09:00, D: Empty, Remarks: "AL" → Error
Date: Jan 20 (Mon) | C: Empty, D: 18:00, Remarks: "" → Error
dispDate & " - Incomplete time entry (has start time in C but missing end time in D)"
or
dispDate & " - Incomplete time entry (has end time in D but missing start time in C)"
Done
GRP A2.7 (PT) Incomplete time entry — one of start/end missing (PT)PT: ✓ Applied All Days isPartTime = True AND
• Exactly one of C or D is filled
Business Logic:
PT employees are exempt from Rules 1, 2, and 2.5 (leave without reason). However, having only one of start/end time is still a data-entry error on any day type.

Example:
PT employee | Date: Jan 20 (Mon) | C: 09:00, D: Empty → Error
dispDate & " - Incomplete time entry (has start time in C but missing end time in D)"
or
dispDate & " - Incomplete time entry (has end time in D but missing start time in C)"
Done
Group B Work Times Present — C & D both filled Each rule is an independent parallel check — not an If/ElseIf chain.
GRP B2.6 Full-day NPL/NPSL with work hours presentPT: ✓ Applied Weekday Saturday (5.5-day) (Weekday OR Saturday 5.5-day) AND
• C filled AND D filled
• Remarks contain "NPL" or "NPSL"
• No "(AM)" or "(PM)" marker in remarks
Business Logic:
Full-day NPL or NPSL means no work should be recorded. If C/D are filled alongside a full-day NPL/NPSL remark (without an AM/PM marker), the entry is contradictory.

Half-day NPL(AM) or NPL(PM) is valid — the employee works the other half and enters times in C/D.

Example (error):
Date: Mar 4 (Tue) | C: 09:00, D: 18:00, Remarks: "NPL" → Full-day NPL but 9.0h recorded → Error

Example (valid):
Date: Mar 4 (Tue) | C: 13:00, D: 18:00, Remarks: "NPL (AM)" → Half-day NPL, afternoon worked → OK
dispDate & " - Full-day NPL/NPSL but work hours present (C-D=" & Format(roundedActual,"0.0") & "h)" Done
GRP B3 Working hours not numeric (E is N/A)PT: ⊘ Skipped Weekday Weekday AND
• C filled AND D filled
• actualHours > 0 (from C-D)
• E NOT numeric (N/A, text, error)
Business Logic:
If time is entered (C & D), the working hours formula (E) should calculate a numeric value. "N/A" or errors indicate formula/data issues.

Example:
Date: Jan 12 (Mon) | C: 09:00, D: 18:00, E: "N/A", actual: 9.0h → Error
dispDate & " Working hours missing C-D=" & Format(actualHours,"0.0") & "h E is not numeric (N/A)" Done
GRP B4 C/D vs E mismatchPT: ✓ Applied Weekday Weekday AND
• C filled AND D filled
• E is numeric
• E ≠ (D-C) × 24
Business Logic:
The working hours in column E must match the calculated difference between end time and start time [(D-C) × 24].

Example:
Date: Jan 18 (Mon) | C: 09:00, D: 18:00, E: 8.5h, actual: 9.0h → Error
dispDate & " Working hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(workHours,"0.0") & "h" Done
GRP B5 Actual < expected hoursPT: ⊘ Skipped Weekday Weekday AND
• actualHours + tolerance(0.5) < expectedHours
Business Logic:
Employees should work their expected hours unless on approved leave. Small discrepancies within tolerance (0.5h) are allowed for rounding.

Example:
Date: Feb 8 (Mon) | actual: 7.5h, expected: 9.0h → 7.5+0.5=8.0<9.0 → Error
dispDate & " Hours mismatch actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
GRP B6 OT not allowed (no OT eligibility)PT: ⊘ Skipped Weekday Weekday AND
• actualHours ≤ expectedHours + tolerance
• OT (G) > 0
Business Logic:
Overtime is only permitted when actual hours exceed expected hours + tolerance. Working exactly expected hours = no OT allowed.

Example:
Date: Jan 22 (Mon) | actual: 9.0h, expected: 9.0h, OT: 1.0h → Error
dispDate & " OT not allowed actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h" Done
GRP B7 OT miscalculation (1-hour buffer)PT: ⊘ Skipped Weekday Weekday AND
• actualHours > expectedHours
• calcOT = MAX(0, actual - expected - 1)
• calcOT ≠ OT in col F
Business Logic - 1-hour buffer rule:
Employees must work >1 hour beyond expected hours before OT is counted.
• actual - expected ≤ 1h → No OT allowed
• actual - expected > 1h → OT = excess hours - 1

Example:
Date: Feb 3 (Wed) | actual: 11.0h, expected: 9.0h, OT(F): 2.0h, calcOT: 11-9-1=1.0h → Error
dispDate & " OT miscalculation actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h OT(calc)=" & Format(calcOT,"0.0") & "h (OT = hours - 1)"
What does "(OT = hours - 1)" mean?
The message reminds the reviewer of the 1-hour buffer formula. OT only starts counting after the employee has worked more than 1 hour beyond their expected hours.

Example breakdown:
actual=11h, expected=9h → excess=2h → OT allowed = 2−1 = 1.0h
But col F shows 2.0h → mismatch → Error

OT(col F) = what the employee declared in the timesheet
OT(calc) = what the system calculated using the formula
Done
GRP B8 Late coming (time-based)PT: ✓ Applied (when implemented) Weekday Weekday AND
• C filled AND D filled
• startTime > scheduledStartTime + 15min
Business Logic:
Employees arriving later than scheduled start time + 15min tolerance are marked as late. Requires employee schedule data.

Example:
Date: Jan 10 (Mon) | Scheduled: 09:00, Actual: 09:25 → 09:25>09:15 → Error

⚠️ Status: Not started - requires employee schedule data
dispDate & " Late coming start " & Format(startTime,"hh:nn") Pending
GRP B9 Early leave (time-based)PT: ✓ Applied (when implemented) Weekday Weekday AND
• C filled AND D filled
• endTime < scheduledEndTime - 15min
Business Logic:
Employees leaving earlier than scheduled end time - 15min tolerance are marked as early departure. Requires employee schedule data.

Example:
Date: Jan 15 (Mon) | Scheduled: 18:00, Actual: 17:30 → 17:30<17:45 → Error

⚠️ Status: Not started - requires employee schedule data
dispDate & " Early leave end " & Format(endTime,"hh:nn") Pending
GRP B10 Short hours (possible early leave/late coming)PT: ⊘ Skipped Weekday Weekday AND
• C filled AND D filled
• actualHours + tolerance < expectedHours
Business Logic:
When hours worked are significantly less than expected (beyond 0.5h tolerance), it suggests late arrival or early departure. This flags for manager review.

Example:
Date: Feb 12 (Fri) | actual: 7.0h, expected: 9.0h → Error (warning)
dispDate & " Working hours less than standard (possible early leave/late coming) actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
Group C Saturday — 5.5-day Staff Only Applies only when HR flag "5.5 days (Y/N)" = Y. 5-day staff Saturday falls into Group D instead.
GRP C11 Saturday leave without reason (5.5-day staff)PT: ⊘ Skipped Saturday Saturday AND 5.5-day employee AND
• 0 < actualHours ≤ 4
• actualHours < expectedHours
• Remarks NOT contain leave types
Business Logic:
5.5-day employees are expected to work Saturdays (typically 4 hours). Partial hours without valid leave explanation require justification.

Example:
Date: Jan 11 (Sat) | Type: 5.5-day, expected: 4.0h, actual: 2.5h, Remarks: "Personal matters" → Error
dispDate & " - Sat leave without reason (5.5-day staff) actual " & Format(actualHours,"0.0") & "h expected " & Format(expectedHours,"0.0") & "h" Done
GRP C11.5 Saturday leave without AM/PM marker (5.5-day staff)PT: ⊘ Skipped Saturday Saturday AND 5.5-day employee AND
• Remarks contain any leave type:
  AL/SL/CL/ML/PL/NPL/NPSL/COMPASSION
• No "(AM)" or "(PM)" marker in remarks
Business Logic:
5.5-day staff Saturdays are working half-days. Any leave taken on a Saturday should specify AM or PM to clarify which half was taken. A warning is generated when a leave type is present but no half-day marker is found.

Example (warning):
Date: Jan 11 (Sat) | 5.5-day, Remarks: "SL" → Warning: "Jan 11 - Saturday SL should specify (AM) or (PM)"

Example (valid):
Date: Jan 11 (Sat) | 5.5-day, Remarks: "SL (AM)" → OK ✅

Leave types checked: AL, SL, CL, ML, PL, NPL, NPSL, COMPASSION
dispDate & " - Saturday AL should specify (AM) or (PM)"
dispDate & " - Saturday SL should specify (AM) or (PM)"
dispDate & " - Saturday CL should specify (AM) or (PM)"
dispDate & " - Saturday ML should specify (AM) or (PM)"
dispDate & " - Saturday PL should specify (AM) or (PM)"
dispDate & " - Saturday NPL should specify (AM) or (PM)"
dispDate & " - Saturday NPSL should specify (AM) or (PM)"
dispDate & " - Saturday COMPASSION should specify (AM) or (PM)"
(8 warning variants, one per leave type)
Done
GRP C12 Saturday OT after 5.5 days (4h+ only)PT: ⊘ Skipped Saturday Part A: Sat AND 5.5-day AND
• C/D filled, weeklyDays>5.5, hrs≥4

Part B: Sat AND 5.5-day AND
• actualHours > 4
• calcOT = actualHours - 4
• calcOT ≠ OT in col F
Business Logic - Saturday OT Rule:
• First 4 hours = regular working hours
• Hours beyond 4 = overtime
• OT only counted if worked >5.5 days that week
• Formula: Saturday OT = actualHours - 4

Example:
Date: Jan 18 (Sat) | Type: 5.5-day, actual: 6.0h, OT(F): 2.5h, calcOT: 6.0-4.0=2.0h → Error
Part A:
dispDate & " Saturday OT hours=" & Format(actualHours,"0.0") & "h (after 5.5 days rule)"

Part B:
dispDate & " - Sat OT miscalculation (5.5-day staff) actual " & Format(actualHours,"0.0") & "h OT(col F)=" & Format(otFromCol,"0.0") & "h OT(calc)=" & Format(satOT,"0.0") & "h (OT = hours - 4)"
Part A vs Part B — what's the difference?

Part A — OT was recorded but the employee didn't even qualify for Saturday OT (didn't work >5.5 days that week). The message just states the hours; no formula comparison needed.

Part B — Employee did qualify for OT, but the amount in col F is wrong. Saturday OT formula is actual − 4 (first 4h = regular, beyond 4h = OT).

Example (Part B):
actual=6.0h → OT should be 6−4=2.0h
But col F shows 2.5h → mismatch → Error
Done
Group D Rest Day — Sunday / Public Holiday / Saturday (5-day staff) 5-day staff Saturday treated as rest day (same as Sun/PH). 5.5-day staff Saturday handled by Group C.
GRP D13 Rest day hours mismatch (Sun/PH/Sat-5day)PT: ⊘ Skipped Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• At least one of C-D / E / F filled
• NOT (C-D = E = F)
Business Logic - Rest day work rule:
• All hours worked on a rest day = OT (no "regular hours")
• C-D, E, and F must all match
• Applies to: Sunday, Public Holiday, AND Saturday for 5-day staff
• 5-day staff Saturday is treated as a rest day (same as Sun/PH)
• 5.5-day staff Saturday is handled separately by Rules 11 & 12

Example (Sun/PH):
Date: Jan 1 (Sun-PH) | C: 10:00, D: 15:00, E: 5.0h, F: 4.5h → Error

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | C: 10:00, D: 14:00, E: 4.0h, F: 3.5h → Error
Version 1:
dispDate & " Public Holiday hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(vE,"0.0") & "h F=" & Format(vF,"0.0") & "h"

Version 2:
dispDate & " - Public Holiday hours mismatch C-D=" & Format(actualHours,"0.0") & "h E=" & Format(CDbl(colE),"0.0") & "h"
Why two versions?
Two slightly different message formats exist in the code due to different execution paths within the same rule.

Version 1 — Fired when all three of C−D, E, and F are available. Includes the F column value in the message for full visibility.

Version 2 — Fired when F is missing or zero. Only compares C−D vs E.

Both indicate the same underlying issue: the hours across C−D, E, and F don't all match. A future cleanup could unify these into one message format.
Done
GRP D14 Rest day no work, no issue (Sun/PH/Sat-5day)PT: ✓ Applied Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• C = Empty
• D = Empty
Business Logic:
Sundays, public holidays, and Saturdays for 5-day staff are all rest days. No work = no error.
5-day staff Saturday is a rest day (same treatment as Sun/PH).

Example (Sun/PH):
Date: Jan 1 (Sun-New Year) | C/D: Empty → OK ✅ (no error)

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | C/D: Empty → OK ✅ (no error)
(No remark) Done
GRP D15 Leave marked on rest day (warning)PT: ✓ Applied Sunday/PH Saturday (5-day) (Sunday OR Public Holiday OR
Saturday with 5-day staff) AND
• Remarks contain any leave type:
  AL/SL/CL/ML/PL/NPL/NPSL/
  COMPASSION
Business Logic:
Leave types shouldn't be marked on rest days (Sunday, Public Holiday, or Saturday for 5-day staff) since these are already non-working days. This is a WARNING to prevent accidental leave deduction.
Leave is NOT counted but a warning remark is generated.

Example (Sun/PH):
Date: Jan 1 (Sun-New Year) | Remarks: "AL" → Warning: "Jan 1 - AL marked on rest day (Sun/PH)"

Example (5-day Saturday):
Date: Jan 11 (Sat) | 5-day staff | Remarks: "SL" → Warning: "Jan 11 - SL marked on rest day (Sun/PH)"

7 different messages: One for each leave type (AL, SL, CL, ML, PL, NPL, COMPASSION)
dispDate & " - AL marked on rest day (Sun/PH)"
dispDate & " - SL marked on rest day (Sun/PH)"
dispDate & " - CL marked on rest day (Sun/PH)"
dispDate & " - ML marked on rest day (Sun/PH)"
dispDate & " - PL marked on rest day (Sun/PH)"
dispDate & " - NPL marked on rest day (Sun/PH)"
dispDate & " - COMPASSION marked on rest day (Sun/PH)"
(7 different messages based on leave type)
Why does 5-day Saturday say "(Sun/PH)" in the message?
The message template was written when this rule only covered Sunday and Public Holidays. 5-day staff Saturday was added later as a third rest-day type, but the label in the remark string was not updated.

Impact: The warning is functionally correct — it still alerts HR that leave was marked on a rest day. Only the label is slightly misleading for Saturday cases.

Future: Could be improved to say "(rest day)" or detect the day type and output "(Sat-5day)" vs "(Sun/PH)" separately.
Done
Group E Part-Time (PT) Employees — Specific Outputs These outputs apply exclusively to employees with work schedule "PT" in the HR sheet. FT employees receive "N/A" for PT-specific columns.
GRP EPT-1 PT working hours & daysPT: ★ Exclusive All Days isPartTime = True
After day loop ends:
• Read footer "TOTAL WORKING HOURS" col E → totalPTHours
• Read footer "TOTAL WORKING DAYS" col E → totalPTDays
Intermediate col B: totalPTHours
Intermediate col C: totalPTDays
(FT employees: both written as "N/A")
Done
GRP EPT-2 468-hour statutory cap statusPT: ★ Exclusive All Days isPartTime = True AND
HR column "468" = "Y"
Intermediate col E (pt_468_status): "✓ met"
(Only written if HR 468 = Y AND isPartTime)
Done
GRP EPT-3 PT Others leave — warning & suppressionPT: ★ Exclusive (when implemented) All Days isPartTime = True AND
Col G = "Others"
Business Logic:
PT employees should use the Col G dropdown (AL/SL/NPL variants) for leave. When Col G = "Others" for a PT employee, the entry is flagged with a warning remark and no leave type is counted — all effHas... flags are forced to False. The FT "Others → Col H" path remains unchanged.

Example:
PT employee | Col G: "Others", Col H: "CL" → Warning + CL NOT counted

⚠️ Status: Not started — Batch 5 implementation pending
dispDate & " - PT leave: 'Others' not applicable for PT employees (use Col G AL/SL/NPL dropdown)"
(warning remark only; all effHas... flags = False, leave not counted)
Pending

📋 Intermediate Sheet — intermediate - after scanning

One row is written per employee per run. Col F is hidden (reserved). Col E (pt_468_status) is written for PT employees where HR 468 = Y. All other columns always written.

Column Schema (A–Q)

Col Header Description
Aemployee IDEmployee code (e.g. A0012). Row is matched by this key on subsequent runs — updated in-place if already exists.
Btotal working hours (PT)PT employees only — read from timesheet footer row "TOTAL WORKING HOURS" col E (pre-calculated by Generator). Written as "N/A" for FT employees.
Ctotal working days (PT)PT employees only — read from timesheet footer row "TOTAL WORKING DAYS" col E (pre-calculated by Generator). Written as "N/A" for FT employees.
D468Reserved column (468-hour statutory cap tracking). Currently written as "N/A".
Ept_468_statusWritten as "✓ met" only when isPartTime = True AND HR column "468" = "Y". Blank for all FT employees and PT employees without 468 flag.
F(hidden)Reserved / hidden column. Not written by current code.
GOT - weekdays/sat (hours)Total OT hours accumulated on weekdays and scheduled Saturdays for the month.
HOT - sun/PH (hours)Total OT hours accumulated on Sundays, Public Holidays, and 5-day-staff Saturdays.
Ino. of AL taken (days)Count of AL day-equivalents taken (full-day = 1, half-day = 0.5). Validated against HR quota; overtaking AL highlighted red in remarks.
Jno. of SL taken (days)Count of SL day-equivalents taken (full-day = 1, half-day = 0.5). Validated against HR quota; overtaking SL highlighted red in remarks.
K4 consecutive SL (Y/N)"Y" if 4 or more consecutive SL days detected, "N" otherwise. "Y" displayed in red font (RGB 255,0,0); "N" resets to automatic colour.
Lno. of NPL/NPSL taken (days)Count of NPL/NPSL day-equivalents taken (full-day = 1, half-day = 0.5).
MCL hours used (compensation leave)Total CL hours deducted. Uses actual C–D hours when available; falls back to HR paid-hours (Sat default: 4h). Validated against hrCLCurrent balance; overtaking CL hours highlighted red in remarks.
Nstatus"completed" — no issues found. "needs review" — one or more remarks generated.
OremarksAll validation messages for the employee, newline-separated. "No issue" when clean. Certain keywords are highlighted red inline: SL > 3 days, overtaking AL, overtaking SL, overtaking CL hours.
Pissues"Y" if any remarks exist, blank otherwise. Used by downstream processing to filter employees needing attention.
Qscan timestampDate/time of the most recent validation run for this employee (format: yyyy-mm-dd hh:mm). Always written; overwrites previous value.

Remarks Column (O) — Inline Red Highlights

After all remarks are written, specific keywords are coloured red within the cell text (character-level formatting, not cell background):

Keyword Trigger condition
SL > 3 daysMonthly SL count exceeds 3 days
overtaking ALAL taken exceeds HR quota (no. of AL column in input (HR))
overtaking SLSL taken exceeds HR quota (no. of SL column in input (HR))
overtaking CL hoursCL hours used exceeds hrCLCurrent balance from input (HR)

📊 Intermediate Calculation Sheet — intermediate - calculation

A companion sheet to intermediate - after scanning. Holds HR master data used for payroll calculations. One row per employee; updated in-place on each run. Sheet and header are auto-created if missing.

Col Header Description
Aemployee IDEmployee code (e.g. A0012). Row is matched by this key — updated in-place if already exists.
Oother allowance (e.g. housing, standby)Parsed total of allowance amounts from the HR sheet "other allowance" column (e.g. "Housing 500; Standby 200" → 700). Used downstream for payroll computation. Written on every run; overwrites previous value.

📄 Standalone Validation Log File

Each validation run appends one row per issue to timesheet - validation log.xlsx in the same source folder. The file and "issue log" sheet are auto-created if missing. SharePoint URL paths are supported. The log is append-only — it preserves full run history and never overwrites previous results.

Column Description
A — Run TimestampDate/time the validation ran (format: yyyy-mm-dd hh:mm:ss)
B — Employee IDEmployee code parsed from filename (e.g. A0012)
C — Employee NameFull name read from timesheet cell C6
D — YearTimesheet year
E — Month3-letter month code (e.g. JAN)
F — DateDate extracted from the remark line (text before first " - "); blank for "No issue" rows
G — MessageRemark text after the date prefix; "No issue" when no errors detected
H — Severity "Warning" — message contains "rest day" or "should specify"
"Error" — all other issues
Blank — "No issue" rows