# parse-time-sheet.lua

## Description

[PEG Pattern Documentation](/Volumes/nc/nc-server/documentation/peg.md)

This Lua script parses time sheet markdown files and converts them to structured JSON format. It validates time entries, detects errors, and provides detailed error reporting with context.

## Usage

```bash
lua parse-time-sheet.lua
```

The script processes predefined markdown files containing time sheet data and outputs JSON files with parsed events and error information.

## Input Format

The script supports multiple time sheet formats:

### Format 1: Day entries with project lines

```markdown
- Mon 2025-01-01
  project1: 09:00-12:00 Task description
  project2: 13:00-17:00 Another task
```

### Format 2: Day entries with time constraints

Date entries can optionally include time constraints:
- Only start time: `- Mon 2025-01-01 09:00-` (Work starts at 9:00, no end constraint)
- Only end time: `- Mon 2025-01-01 -17:00` (Work ends at 17:00, no start constraint)
- Both start and end: `- Mon 2025-01-01 09:00-17:00` (Work between 9:00 and 17:00)
- No time constraint: `- Mon 2025-01-01` (No time constraints)

### Format 3: Inline day entries

```markdown
Mon 2025-01-01 09:00-17:00 Work description
```

### Format 4: Mixed format with tasks

```markdown
- Mon 2025-01-01
  project1: 09:00-12:00 Main task
    subtask details
    more subtask info
  project2: 13:00-17:00 Another task
```

## Output

The script generates JSON files with the following structure:

```json
{
  "file_path": "/path/to/input.md",
  "parse_time": "2025-01-01 12:00:00",
  "person": "username",
  "event_count": 1713,
  "error_count": 10,
  "warning_count": 1,
  "day_count": 68,
  "created_at_seconds": 1234567890,
  "event": [
    {
      "level": 1,
      "date": "2025-01-01",
      "day_name": "Mon",
      "project": "project1",
      "start_time": "2025-01-01 09:00:00",
      "end_time": "2025-01-01 12:00:00",
      "description": "Task description",
      "line_number": 2,
      "original_line": "  project1: 09:00-12:00 Task description"
    },
    {
      "level": 1,
      "date": "2025-01-01",
      "day_name": "Mon",
      "project": "day",
      "date_start_time": "2025-01-01 09:00:00",
      "date_end_time": "2025-01-01 17:00:00",
      "description": "- Mon 2025-01-01 09:00-17:00",
      "line_number": 1,
      "original_line": "- Mon 2025-01-01 09:00-17:00"
    }
  ]
}
```

## Error Types

The script detects and reports the following error types:

### Errors

- `invalid_date_format`: Date format is not recognized
- `invalid_start_time_format`: Start time format is invalid
- `invalid_end_time_format`: End time format is invalid
- `time_overlap_with_previous`: Time range overlaps with previous entry
- `end_time_before_start_time`: End time is before start time
- `datetime_out_of_sequence`: Events are not in chronological order
- `event_before_date_start_time`: Event starts before the date's start time constraint
- `event_after_date_end_time`: Event ends after the date's end time constraint

### Warnings

- `date_in_description`: Date pattern found in task description

## Features

### Time Validation

- Validates time formats (HH:MM, H:MM, HH:MM:SS)
- Detects time overlaps between consecutive entries
- Handles next-day morning hours (for configured users)
- Validates chronological sequence of events
- Validates event times against date constraints (if present)

#### Date Time Constraints Validation

When a date entry includes time constraints, all events under that date are validated against them:

- **date_start_time**: All events must not start before this time (if specified)
- **date_end_time**: All events must not end after this time (if specified)
- Events outside these constraints will generate appropriate error messages
- Date constraints are optional - if not specified, events can be at any time

### Context-Aware Parsing

- Two-pass parsing system for better context understanding
- Smart indentation-based hierarchy detection
- Support for both project-based and inline time entry formats
- Correctly handles short project names (e.g., "ai", "nc", "ui")

### Error Reporting

- Shows first 6 errors per category
- Provides context lines before and after errors
- Reduces excessive context with ellipsis (...) when needed
- Color-coded output for better readability
- Error messages include parsed times on the same line

### Output Management

- JSON files written to current working directory
- Filename based on input file (e.g., `pasi-hours-2025-01-01.md` → `pasi-hours-2025-01-01.json`)
- Summary statistics included in output

## Configuration

### User Settings

- `nextDayUserIdx`: Users allowed to have next-day morning hours
- `morningHourCutoff`: Morning hour cutoff for next-day detection (default: 8 AM)

### File Processing

- Input files are hardcoded in the `parseTimeSheet()` function
- Currently processes:
  - `/Volumes/nc/nc-doc/memo/pasi/pasi-hours-2025-01-01.md`
  - `/Volumes/nc/nc-doc/memo/Paavo/paavo-tunnit-2024-08-07-alkaen.md`

## Dependencies

- `fs`: File system operations
- `peg`: Pattern matching library
- `dt`: Date/time utilities
- `util`: General utilities
- `ansicolor`: Color output formatting
- `start`: Path configuration

## Error Display Format

The error output provides clear context for each error:

```text
- nc-frontend: 14:30-14:20 <<< end_time_before_start_time, parsed: 14:30-14:20
- calc-server: 14:20-14:25 <<< datetime_out_of_sequence
    - fix: "action_limit": false
- ai: 16:30-19:20 <<< invalid_date_format, parsed: 16:30-19:20
```

### Error Format Details

- **Context lines**: Shows parent, siblings, and children around the error (reduced with "..." if too many)
- **Overlap errors**: Shows previous line with time, then current line with `<<< previous hh:mm-hh:mm`
- **Datetime sequence errors**: Include fix suggestion: `- fix: "action_limit": false`
- **Time format errors**: Include parsed times on same line: `, parsed: hh:mm-hh:mm`
- **Maximum errors**: Shows up to 6 errors per category (configurable with `maxErrorPrintCount`)

## Current Configuration

### File Processing

Currently configured to process:

- `/Volumes/nc/nc-doc/memo/pasi/pasi-hours-2025-01-01.md`

Other files are commented out for future use.

## Notes

- The script follows strict naming conventions from AGENTS.md
- No fallback values are used - errors are reported immediately
- All paths must be absolute (starting with `/Volumes/nc/`)
- Output is concise but comprehensive, focusing on actionable error information
- Short project names (2-3 characters) are correctly identified and processed as project entries, not day entries
- **Multiple spaces between date and time are handled correctly** (e.g., `- Mon 2025-01-01  09:00-17:00`)
- **Date time constraints are properly tracked in context** - events are validated against parent date constraints to avoid duplicate error messages
- Events within date time ranges are NOT considered errors - only events outside the range trigger validation errors

## Error Context Requirement

### Requirements for printSingleError function

1. Find all upper events until level 1 time event (loop backwards)
2. Collect all events after that until error event (loop forwards)
3. Add error line
4. Add next line
5. Print all collected lines in order

### Simple Algorithm

```lua
local function printSingleError(eventList, errorIndex, errorType, isWarning)
    local errorEvent = eventList[errorIndex]
    if not errorEvent then return end

    -- Collect context lines
    local contextArray = {}

    -- 1. Find level 1 parent (loop backwards)
    local level1Index = nil
    for i = errorIndex - 1, 1, -1 do
        local event = eventList[i]
        if event and event.start_time and (event.level or 1) <= (errorEvent.level or 1) then
            level1Index = i
            break
        end
    end

    -- 2. Collect from level1 to error
    local startIdx = level1Index or errorIndex
    for i = startIdx, errorIndex do
        local event = eventList[i]
        if event and event.original_line then
            contextArray[#contextArray + 1] = event.original_line
        end
    end

    -- 3. Add error with marker
    if errorEvent.original_line then
        contextArray[#contextArray + 1] = errorEvent.original_line .. " " .. color(isWarning and "yellow" or "red", "<<<") .. " " .. getErrorSuffix(errorType, errorEvent)
    end

    -- 4. Add next line
    if errorIndex < #eventList then
        local nextEvent = eventList[errorIndex + 1]
        if nextEvent and nextEvent.original_line then
            contextArray[#contextArray + 1] = nextEvent.original_line
        end
    end

    -- 5. Print all
    for _, line in ipairs(contextArray) do
        util.print("%s", line)
    end
end
```

## PEG Pattern Requirements

### IMPORTANT: All PEG patterns must be at file level

- No inline string concatenation for patterns
- Use peg.set() for character sets (e.g., time separators, dashes)
- Use peg.pattern() for reusable patterns
- Handle user errors gracefully with flexible matching

### Example of WRONG approach

```lua
local timeRangeStr = timeStart .. "-" .. timeEnd  -- BAD: String concatenation
```

### Example of CORRECT approach

```lua
-- At file level:
local timeRangePattern = timePattern * dash * timePattern

-- In code:
if peg.found(line, timeRangePattern) then
    -- Extract with peg.parseBefore/After
end
```

### Time separator pattern

```lua
local timeSeparator = peg.set(":.")  -- Accept both : and .
```

### Dash pattern

```lua
local dashPattern = peg.pattern("-")  -- Use pattern, not string
```

### IMPORTANT: Use peg.capture(), not parseBefore/After

```lua
-- WRONG approach:
local start = peg.parseBefore(text, "-")
local end = peg.parseAfter(text, "-")

-- CORRECT approach with PEG captures:
local dash, notDash = peg.patternAntiPattern("-")
local timeRangePattern = peg.capture(timePattern) * dash * peg.capture(timePattern)
local startTime, endTime = peg.match(text, timeRangePattern)

-- For flexible matching with spaces:
local dashWithSpaces = peg.most(spacePattern, 0) * dash * peg.most(spacePattern, 0)
local flexibleTimeRange = peg.capture(timePattern) * dashWithSpaces * peg.capture(timePattern)
```

## Error Context Display Requirements

### Hierarchical Context Rule

When displaying errors, show:

1. Find the MOST RECENT level 1 event BEFORE the error (lines with no space on left)
2. Show that level 1 event first
3. Show siblings between level 1 and error
4. Show error line (marked with <<<)
5. Show next line after error

### Sibling Truncation

If there are MORE THAN 3 sibling events between level 1 and error:

- Show level 1 event
- Show first sibling event
- Show "..." (ellipsis)
- Show last sibling BEFORE error
- Show error row (marked with <<<)
- Show next row after error

### This provides proper hierarchical context while keeping output concise

### Example output

```text
- pe 2024-11-01 9:05-16:20
  - work-center: 11:15-18:30
  - ai: 10:00-11:00
  ...
  - admin: 9:05-9:20 <<< end_time_before_start_time, parsed: 09:05-09:20
    - moovy-kuitti ja coreseer-lasku ostoreskontraan
  - mail 9:20-9:30
```

## Troubleshooting

### Common Issues and Solutions

#### Duplicate Error Messages
If you're seeing the same error message multiple times for a single event, ensure that:
1. Date entries with time constraints are properly formatted
2. Context tracking is working (check parentStartTime and parentEndTime)
3. Events are being validated against the correct parent date constraints

#### Date Constraint Validation Not Working
If events are not being validated against date constraints:
1. Check that the date entry has the correct format with time constraints
2. Verify that the date constraints are stored in `date_start_time` and `date_end_time` fields
3. Ensure the context is updated with parent date constraints

#### Invalid Date Format Errors
When seeing `invalid_date_format` for date entries:
- The date should be in YYYY-MM-DD format
- Check for extra characters or malformed dates
- Multiple spaces between date and time are now handled correctly
