Files
Joseph Doherty 26ff8d9b4f Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
2026-01-02 07:43:29 -05:00

29 KiB

Excel Export Specification

Purpose

The Excel Export subsystem generates multi-sheet Excel workbooks (.xlsx) containing search results and criteria documentation using the ClosedXML library on .NET 10. It provides an injectable IExcelExportService that transforms search data into formatted, protected spreadsheets that users can download, analyze, and share. The subsystem supports conditional sheet generation based on search options (MIS data extraction) and applies consistent styling, column definitions, and worksheet protection across all output.

Source Reference

Legacy File Purpose
OLD/WorkerService/Process/ExcelWriter.cs Main Excel generation orchestration and sheet writing
OLD/WorkerService/Helpers/ExcelHelpers.cs Generic table loading and attribute-driven column formatting
OLD/WebInterface/Helpers/ExcelTemplateGenerator.cs Data entry template generation for bulk uploads
OLD/WorkerService/Models/Reporting/OutputColumnAttribute.cs Column metadata (order, format, width, wrap)
OLD/WorkerService/Models/Reporting/OutputTableAttribute.cs Table/tab metadata (name, header display)
OLD/WorkerService/Models/Reporting/SearchResult.cs Search results data model with column definitions
OLD/WorkerService/Models/Reporting/MisSearchResult.cs MIS data model with column definitions
OLD/WorkerService/Models/Reporting/MisNonMatchSearchResult.cs Investigation (mismatch) data model
OLD/WorkerService/Models/Reporting/*.cs Filter entry models for criteria documentation

Requirements

Requirement: Injectable Excel Export Service

The system SHALL provide an injectable service for Excel generation following .NET dependency injection patterns.

Service Interface

public interface IExcelExportService
{
    Task<byte[]> GenerateAsync(SearchModel search, CancellationToken cancellationToken = default);
    Task GenerateToStreamAsync(SearchModel search, Stream outputStream, CancellationToken cancellationToken = default);
}

Test Support

For unit testing, the system SHALL provide mock data factories to generate sample data without database dependencies:

public static class ExcelTestDataFactory
{
    public static SearchModel CreateSampleSearch(int resultCount = 10);
    public static List<SearchResult> CreateSampleResults(int count);
    public static List<MisSearchResult> CreateSampleMisResults(int count);
    public static List<MisNonMatchSearchResult> CreateSampleInvestigationResults(int count);
}

Configuration Class

public class ExcelExportOptions
{
    public string CriteriaSheetPassword { get; set; } = "JDE_SCOPING_TOOL_PASS";
    public string DataSheetPassword { get; set; } = "JDESCOPINGTOOL";
}

Business Rules

  • The service MUST be registered as scoped or transient in the DI container
  • The service MUST accept ILogger<ExcelExportService> for structured logging
  • The service MUST accept IOptions<ExcelExportOptions> for configuration
  • Logging MUST use BeginScope() to include search context (SearchId, SearchName)
  • The service MUST use ClosedXML (XLWorkbook) for workbook generation
  • Temporary files SHALL use Path.GetTempPath() for cross-platform temp directory access
  • Debug file output (DebugWriteToFile) SHALL be disabled by default; enable via configuration for troubleshooting

Scenario: Register service in DI container

  • WHEN the application starts
  • THEN IExcelExportService is registered with ExcelExportService implementation as scoped

Scenario: Log export operations with context

  • WHEN generating an export for a search
  • THEN log entries include SearchId and SearchName via ILogger.BeginScope()

Requirement: Multi-Sheet Workbook Generation

The system SHALL generate Excel workbooks with multiple worksheets based on search configuration and results.

Inputs

  • SearchModel containing:
    • Search metadata (name, username, timestamps)
    • Filter criteria (timespan, work orders, items, profit centers, work centers, operators, component lots, item/operation/MIS)
    • Search results (List<SearchResult>)
    • MIS results (List<MisSearchResult>) - when ExtractMisData is enabled
    • MIS non-match results (List<MisNonMatchSearchResult>) - when ExtractMisData is enabled
  • ExtractMisData boolean flag

Outputs

  • Excel workbook as byte[] containing:
    • "Search Criteria" sheet (always present)
    • "Search Results" sheet (always present)
    • "MIS Info" sheet (conditional - only when ExtractMisData is true and results not null)
    • "Investigation" sheet (conditional - only when ExtractMisData is true and results not null)

Business Rules

  • The "Search Criteria" sheet MUST always be the first sheet in the workbook
  • The "Search Results" sheet MUST always be the second sheet
  • MIS-related sheets SHALL only be included when ExtractMisData is true AND respective result collections are not null
  • The workbook MUST be returned as a byte array for storage in the database

Scenario: Generate standard search export

  • WHEN a search completes with ExtractMisData set to false
  • THEN the system creates a workbook with exactly two sheets: "Search Criteria" and "Search Results"

Scenario: Generate full export with MIS data

  • WHEN a search completes with ExtractMisData set to true and both MIS collections populated
  • THEN the system creates a workbook with four sheets: "Search Criteria", "Search Results", "MIS Info", and "Investigation"

Scenario: Generate export with empty MIS results

  • WHEN a search completes with ExtractMisData true but MisResults is empty (not null)
  • THEN the system creates the "MIS Info" sheet with empty data table

Scenario: Generate export with null MIS results

  • WHEN a search completes with ExtractMisData true but MisResults is null
  • THEN the system skips the "MIS Info" sheet entirely

Scenario: Generate export with null investigation results

  • WHEN a search completes with ExtractMisData true but MisNonMatchResults is null
  • THEN the system skips the "Investigation" sheet entirely

Requirement: Search Criteria Documentation Sheet

The system SHALL generate a "Search Criteria" sheet documenting all search parameters and execution metadata.

Inputs

  • Search metadata: Name, UserName, SubmitDT, StartDT, EndDT
  • Timespan filter: MinimumDT, MaximumDT
  • Filter collections: WorkOrderFilter, ItemNumberFilter, ProfitCenterFilter, WorkCenterFilter, OperatorFilter, ComponentLotFilter, ItemOperationMisFilter
  • ExtractMisData flag

Outputs

  • Worksheet named "Search Criteria" containing:
    • Search name and username header rows
    • Timestamp section (submit, start, completed)
    • Timespan filter table
    • Multiple filter tables (one per filter type)
    • Extract MIS data indicator

Business Rules

  • Header cells MUST use bold, centered text with Gainsboro (light gray) background via XLColor.Gainsboro
  • Timestamps MUST be formatted as "MMM dd, yyyy hh:mm:ss tt EST"
  • Filter tables MUST be separated by 2 blank rows (current row + 3 for next table start)
  • Columns MUST auto-fit with 15% additional padding (width * 1.15)
  • The sheet MUST be password-protected using password from ExcelExportOptions.CriteriaSheetPassword
  • Filter tables MUST use the Light18 table style (XLTableTheme.TableStyleLight18)

Scenario: Document search with all filters populated

  • WHEN generating criteria sheet for a search with all filter types populated
  • THEN the system creates tables for each filter type in order: Timespan, Work Order, Item Number, Profit Center, Work Center, Component Lot, Operator, Item/Operation/MIS

Scenario: Document search with empty filters

  • WHEN generating criteria sheet for a search with empty filter collections
  • THEN the system still creates empty tables with headers for each filter type

Scenario: Format ExtractMisData indicator

  • WHEN ExtractMisData is true
  • THEN the system displays "YES" in the Extract MIS data row

Scenario: Format ExtractMisData indicator negative

  • WHEN ExtractMisData is false
  • THEN the system displays "NO" in the Extract MIS data row

Requirement: Search Results Sheet Generation

The system SHALL generate a "Search Results" sheet containing work order search results with standardized columns.

Inputs

  • List<SearchResult> containing work order data with:
    • Work order identifiers (number, branch code, lot number)
    • Item information (number, planning family, stocking type)
    • Quantities (order, held, scrapped, shipped)
    • Operation details (step branch, number, description, function description)
    • Timestamps (step update, status update)
    • Status information (code, description)
    • Inclusion reason (computed from flags)

Outputs

  • Worksheet named "Search Results" with:
    • 19 columns in defined order
    • Data formatted as Excel table named "Search_Results"
    • Light18 table style applied
    • No worksheet protection (attribute-driven path)

Column Definitions

Order Header Data Type Format
10 Work Order Number long Standard
20 Work Order Branch Code string Standard
30 Lot Number string Standard
40 Item Number string Standard
50 Planning Family string Standard
55 Stocking Type string Standard
60 Order Quantity decimal Standard
70 Held Quantity decimal Standard
80 Scrapped Quantity decimal Standard
90 Shipped Quantity decimal Standard
100 Operation Step Branch Code string Standard
110 Operation Step decimal Standard
120 Operation Step Description string Standard
130 Function Operation Description string Standard
140 Operation Step Update Timestamp DateTime [$-409]m/d/yy h:mm AM/PM;@
150 Status Code string Standard
160 Status Description string Standard
170 Status Update Timestamp DateTime? [$-409]MM/dd/yyyy;@
180 Inclusion Reason string (computed) Standard
190 (Additional column per legacy code) - Standard

Business Rules

  • Columns MUST auto-fit with 30% additional padding (width * 1.3)
  • The table MUST NOT show totals row
  • Timestamp columns MUST use the defined Excel number formats for proper date/time display
  • The Inclusion Reason column MUST compute values from boolean flags (ManuallySpecified, Flagged, CARDEX, PartsList, SplitOrder)

Scenario: Format inclusion reason for manually specified

  • WHEN a result has ManuallySpecified = true
  • THEN the Inclusion Reason displays "ManuallySpecified"

Scenario: Format inclusion reason for flagged

  • WHEN a result has Flagged = true (and ManuallySpecified = false)
  • THEN the Inclusion Reason displays "Flagged"

Scenario: Format inclusion reason for CARDEX only

  • WHEN a result has CARDEX = true and PartsList = false
  • THEN the Inclusion Reason displays "ComponentUsage (CARDEX)"

Scenario: Format inclusion reason for PartsList only

  • WHEN a result has PartsList = true and CARDEX = false
  • THEN the Inclusion Reason displays "ComponentUsage (Parts List)"

Scenario: Format inclusion reason for CARDEX and parts list

  • WHEN a result has both CARDEX = true and PartsList = true
  • THEN the Inclusion Reason displays "ComponentUsage (CARDEX + Parts List)"

Scenario: Format inclusion reason for split order

  • WHEN a result has only SplitOrder = true
  • THEN the Inclusion Reason displays "Split order"

Scenario: Format inclusion reason unknown

  • WHEN a result has no matching boolean flags
  • THEN the Inclusion Reason displays "UNKNOWN"

Requirement: MIS Info Sheet Generation

The system SHALL generate a "MIS Info" sheet containing Manufacturing Instruction Sheet data when enabled.

Inputs

  • List<MisSearchResult> containing:
    • Item identification (number, description)
    • MIS metadata (number, revision, status, release date)
    • Sequence/step numbers (MIS job step, job step, matched)
    • Match indicators (RoutingMatch, MasterMatch)
    • Description fields (function operation, test description)
    • Sampling information (type, value)
    • Long text fields (tools/gauges, work instructions)

Outputs

  • Worksheet named "MIS Info" with:
    • 19 columns in defined order
    • Data formatted as Excel table named "MIS_Info"
    • Light18 table style applied
    • Specific columns with text wrapping and fixed width

Column Definitions

Order Header Format Width
10 Item Number Standard Auto
20 MIS Job Step Sequence Number Standard Auto
30 MIS Number Standard Auto
40 MIS Revision Standard Auto
50 Item Description Standard Auto
60 MIS Release Status Standard Auto
70 MIS Release Date Timestamp Auto
80 Branch Code Standard Auto
90 Job Step Sequence Number Standard Auto
100 Matched Sequence Number Standard Auto
110 Matched to F3112Z1? Standard Auto
120 Matched to F3003? Standard Auto
130 Function Operation Description Standard Auto
140 Char Number Standard Auto
150 Test Description Standard 65 (wrapped)
160 Sampling Type Standard Auto
170 Sampling Value Standard Auto
180 Tools & Gauges Standard 65 (wrapped)
190 Work Instructions Standard 65 (wrapped)

Business Rules

  • Columns with long text (Test Description, Tools & Gauges, Work Instructions) MUST have WrapText enabled and fixed width of 65
  • Auto-width columns MUST apply 30% additional padding
  • Wrapped columns MUST NOT be auto-fitted

Scenario: Generate MIS sheet with wrapped columns

  • WHEN generating MIS Info sheet with data
  • THEN Test Description, Tools & Gauges, and Work Instructions columns have fixed 65-character width with text wrapping enabled

Scenario: Handle null MIS results

  • WHEN MisResults is null
  • THEN the system skips MIS Info sheet generation entirely (returns early)

Scenario: Format boolean match indicators

  • WHEN RoutingMatch or MasterMatch values are written
  • THEN they display as "True" or "False" text values

Requirement: Investigation Sheet Generation

The system SHALL generate an "Investigation" sheet containing router mismatch data for analysis.

Inputs

  • List<MisNonMatchSearchResult> containing:
    • Work center and order identification
    • Job step details (number, description, dates)
    • Function and routing information
    • Item details (number, description)
    • Match indicators (WasJobStepAdded, MatchedJobStepNumber)

Outputs

  • Worksheet named "Investigation" with:
    • 12 columns in defined order
    • Data formatted as Excel table named "Investigation"
    • Light18 table style applied

Column Definitions

Order Header Format
10 Work Center Code Standard
20 Work Order Number Standard
30 Work Order Start Date [$-409]MM/dd/yyyy;@
40 Job Step Number Standard
50 Function Operation Description Standard
60 Job Step End Date [$-409]MM/dd/yyyy;@
70 Function Code Standard
75 Was Job Step Added? Standard (boolean)
76 Matched Job Step Number Standard
80 Item Number Standard
90 Item Description Standard
100 Routing Type Standard

Business Rules

  • Date columns MUST use DATE_FORMAT ([$-409]MM/dd/yyyy;@)
  • All columns MUST auto-fit with 30% additional padding

Scenario: Handle null mismatch results

  • WHEN MisNonMatchResults is null
  • THEN the system skips Investigation sheet generation entirely

Scenario: Format date columns

  • WHEN generating Investigation sheet
  • THEN Work Order Start Date and Job Step End Date columns use [$-409]MM/dd/yyyy;@ number format

Requirement: Worksheet Protection

The system SHALL apply password-based protection to worksheets with configurable allowed operations.

Inputs

  • Worksheet to protect
  • Protection password from IOptions<ExcelExportOptions>
  • Editable range definition (cells beyond data area)

Outputs

  • Protected worksheet with:
    • Locked data cells
    • Unlocked extension area for user additions
    • Specific operations allowed/disallowed

Business Rules

  • Protection passwords MUST be loaded from ExcelExportOptions configuration
  • Protected ranges MUST allow the following operations via IXLSheetProtection:
    • Delete columns: YES
    • Delete rows: NO
    • Auto filter: YES
    • Format cells: YES
    • Format columns: YES
    • Format rows: YES
    • Select locked cells: YES
    • Select unlocked cells: YES
    • Edit objects: YES
    • Sort: YES
  • Unprotected area MUST extend 1000 rows and columns beyond data range

ClosedXML Protection Example

var protection = worksheet.Protect(options.Value.DataSheetPassword);
protection.AllowElement(XLSheetProtectionElements.DeleteColumns);
protection.AllowElement(XLSheetProtectionElements.AutoFilter);
protection.AllowElement(XLSheetProtectionElements.FormatCells);
protection.AllowElement(XLSheetProtectionElements.FormatColumns);
protection.AllowElement(XLSheetProtectionElements.FormatRows);
protection.AllowElement(XLSheetProtectionElements.SelectLockedCells);
protection.AllowElement(XLSheetProtectionElements.SelectUnlockedCells);
protection.AllowElement(XLSheetProtectionElements.EditObjects);
protection.AllowElement(XLSheetProtectionElements.Sort);

Scenario: Protect search criteria sheet

  • WHEN generating Search Criteria sheet
  • THEN sheet is protected using ExcelExportOptions.CriteriaSheetPassword

Scenario: Apply consistent protection settings

  • WHEN protecting any data worksheet
  • THEN all boolean protection flags match the defined allowed operations

Scenario: Allow filtering and sorting

  • WHEN user opens protected worksheet
  • THEN they can filter and sort data without entering password

Requirement: Attribute-Driven Column Configuration

The system SHALL use C# attributes to define column metadata for automatic table generation.

Inputs

  • Data model classes decorated with OutputTableAttribute and OutputColumnAttribute
  • Properties marked with OutputColumnAttribute defining column order, header, format, width, and wrap settings

Outputs

  • Dynamically generated Excel tables based on attribute configuration

Property Access Strategy

  • The system SHALL use native .NET reflection (PropertyInfo.GetValue()) for property access
  • Source generators MAY be used as future optimization for compile-time property mapping

OutputColumnAttribute Properties

Property Type Default Purpose
Order int - Column sort order
HeaderText string - Column header display text
Format string "@" (text) Excel number format
AutoWidth bool true Enable auto-fit
Width double - Fixed width (when AutoWidth=false)
WrapText bool false Enable text wrapping

OutputTableAttribute Properties

Property Type Purpose
TabName string Worksheet tab name
TableName string Excel table name
ShowHeader bool Show merged header row above table

Standard Formats

Constant Value Usage
STD_FORMAT "@" Text format (default)
DATE_FORMAT "[$-409]MM/dd/yyyy;@" Date only
TIMESTAMP_FORMAT "[$-409]m/d/yy h:mm AM/PM;@" Date and time
WRAPPED_COLUMN_WIDTH 65 Fixed width for wrapped columns

Scenario: Generate table from decorated model

  • WHEN LoadTab is called with a model type having OutputTableAttribute
  • THEN worksheet name and table name are derived from attribute values

Scenario: Order columns by attribute

  • WHEN generating table from model with OutputColumnAttribute
  • THEN columns appear in Order property sequence, with ties broken alphabetically by property name

Scenario: Apply custom format to column

  • WHEN a property has OutputColumnAttribute with Format specified
  • THEN that format is applied to the entire column's number format

Scenario: Apply wrapped text configuration

  • WHEN a property has WrapText=true and AutoWidth=false
  • THEN column has text wrapping enabled and uses fixed Width value

Requirement: Data Entry Template Generation

The system SHALL generate simple Excel templates for bulk data entry of filter values.

Inputs

  • Source data collection (optional, for pre-population)
  • Header text (single column) or header array (multi-column)

Outputs

  • Single-sheet workbook named "Data Entry Template"
  • Header row with standard formatting
  • Optional pre-populated data rows
  • All columns formatted as text ("@")

Business Rules

  • Header row MUST be bold, centered, with Gainsboro background via XLColor.Gainsboro
  • Single-column templates MUST use 45-character width
  • Multi-column templates MUST use 65-character width per column
  • All cells MUST use text format to preserve leading zeros in item/lot numbers

Scenario: Generate empty single-column template

  • WHEN Generate is called with null sourceData
  • THEN template contains only header row with no data

Scenario: Generate pre-populated template

  • WHEN Generate is called with sourceData containing values
  • THEN data rows are populated starting at row 2

Scenario: Generate multi-column template

  • WHEN Generate is called with object[][] sourceData and string[] headers
  • THEN multiple columns are created with respective headers

Requirement: Header Cell Formatting

The system SHALL apply consistent header formatting across all worksheets.

Inputs

  • Cell range to format
  • Optional text value
  • Optional merge flag

Outputs

  • Formatted header cell(s) with:
    • Horizontal center alignment
    • Bold font
    • Solid Gainsboro (light gray) background fill via XLColor.Gainsboro

ClosedXML Formatting Example

var cell = worksheet.Cell(row, column);
cell.Value = headerText;
cell.Style.Font.Bold = true;
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
cell.Style.Fill.BackgroundColor = XLColor.Gainsboro;

Business Rules

  • Header format MUST be applied to:
    • All table header rows
    • Label cells in criteria sheet (column 1)
    • Filter table section headers
  • Merge flag MUST only be set for multi-cell ranges using IXLRange.Merge()
  • Text value MUST be written when provided

Scenario: Format single header cell

  • WHEN ApplyHeaderFormat is called on single cell with text
  • THEN cell has bold font, center alignment, Gainsboro background, and displays provided text

Scenario: Format merged header range

  • WHEN ApplyHeaderFormat is called on multi-cell range with merge=true
  • THEN cells are merged and formatted as single header

Requirement: Async Generation Pattern

The system SHALL support async/await patterns for export generation.

Business Rules

  • The GenerateAsync method MUST return Task<byte[]>
  • Since ClosedXML's SaveAs to MemoryStream is synchronous, the implementation MAY wrap the CPU-bound work in Task.Run() to avoid blocking
  • The method MUST accept CancellationToken for cancellation support
  • For very large exports, future versions MAY implement Stream-based output

Implementation Example

public async Task<byte[]> GenerateAsync(SearchModel search, CancellationToken cancellationToken = default)
{
    using var scope = _logger.BeginScope(new Dictionary<string, object>
    {
        ["SearchId"] = search.Id,
        ["SearchName"] = search.Name
    });

    _logger.LogInformation("Starting Excel export generation");

    // ClosedXML operations are synchronous, wrap in Task.Run for non-blocking
    return await Task.Run(() =>
    {
        using var workbook = new XLWorkbook();

        // ... build sheets ...

        using var stream = new MemoryStream();
        workbook.SaveAs(stream);
        return stream.ToArray();
    }, cancellationToken);
}

Scenario: Generate export asynchronously

  • WHEN GenerateAsync is called
  • THEN the method returns a Task<byte[]> that completes when workbook generation finishes

Scenario: Support cancellation

  • WHEN GenerateAsync is called with a CancellationToken that is cancelled
  • THEN the operation throws OperationCanceledException

Migration Notes

Legacy Pattern New Pattern Rationale
EPPlus 4.x (LGPL) ClosedXML (MIT license) EPPlus 7+ requires commercial license; ClosedXML is fully free and MIT licensed
System.Drawing.Color XLColor ClosedXML uses its own color type (XLColor.Gainsboro, etc.)
ExcelPackage XLWorkbook ClosedXML workbook class
ExcelWorksheet IXLWorksheet ClosedXML worksheet interface
ExcelRange IXLRange or IXLCell ClosedXML range/cell interfaces
Fasterflect reflection Native reflection or source generators Reduce dependencies; native PropertyInfo.GetValue() is sufficient; source generators available for future optimization
Extension methods on EPPlus types Service class with IExcelExportService interface Enable testing and alternative implementations
Static ExcelWriter.Generate() Injectable IExcelExportService Dependency injection for testability and configuration
Hard-coded passwords IOptions<ExcelExportOptions> configuration Move protection passwords to configuration for security and flexibility
Byte array return byte[] and Stream Support both: GenerateAsync returns byte[], GenerateToStreamAsync writes to Stream for large exports
Synchronous generation Async wrapping via Task.Run() Support async patterns; ClosedXML SaveAs is sync, wrap for non-blocking
TableStyles enum XLTableTheme ClosedXML table themes (e.g., XLTableTheme.TableStyleLight18)
NLog static logging ILogger<T> injected + BeginScope() Modern structured logging with contextual scopes

Resolved Design Decisions

  1. Library Selection: Use ClosedXML (MIT license) - fully free for commercial use, similar API to EPPlus, active maintenance.

  2. Password Protection: Move to IOptions<ExcelExportOptions> configuration. Default values preserved for backward compatibility but can be overridden via appsettings.json.

  3. Large Export Handling: Implement streaming architecture for memory-efficient large exports.

    • The system SHALL support Stream-based output for large workbooks to avoid memory pressure
    • The system SHALL provide both GenerateAsync (returns byte[]) and GenerateToStreamAsync (writes to Stream) methods
    • For exports exceeding a configurable row threshold, the streaming approach SHALL be preferred
  4. Async Support: GenerateAsync method wraps synchronous ClosedXML operations in Task.Run() to avoid blocking.

  5. Format Compatibility: Maintain locale ID 409 (US English) for timestamp formats. International configuration deferred to future version.

  6. Template Generator: Retain ExcelTemplateGenerator functionality for bulk data entry via the Blazor UI.

Codex Review Findings (Addressed)

The following inaccuracies were identified during review and have been addressed in this specification:

  1. Table Style and Protection: CORRECTED - Spec now states data sheets use Light18 style (via XLTableTheme.TableStyleLight18) and protection is applied only to criteria sheet by default. Data sheets generated via attribute-driven LoadTab do not apply protection.

  2. Column Counts Corrected:

    • Search Results: CORRECTED to 19 columns (was incorrectly 18)
    • Investigation: CORRECTED to 12 columns (was incorrectly 11)
  3. Investigation Date Format: CORRECTED - Spec now states DATE_FORMAT ([$-409]MM/dd/yyyy;@) is used, not "m/d/yyyy".

  4. Inclusion Reason Scenarios Complete: ADDED - Scenarios now include CARDEX-only, PartsList-only, and UNKNOWN cases.

  5. Null List Handling: CLARIFIED - Spec now explicitly states null checks are required before calling sheet generation methods. Implementation MUST check for null before generating MIS Info and Investigation sheets.

  6. Criteria Table Spacing: CORRECTED - Spec now states "2 blank rows" between filter tables (current row + 3 for next table start).