Legacy Pipeline Atlas worker-1snapshot 2026-07-15

MDClarity · task primitive

ScanDataFile

Overview

ScanDataFileTask analyzes a delimited data file's structure and automatically infers column definitions (name, type, width, nullability) by scanning the file's contents. It creates and saves a DataFileSpec entity that can be reused for subsequent file imports with LoadDataFileTask.

Use this task when: You need to discover the schema of an unknown delimited file, create reusable file specifications for recurring imports, or validate file structure before loading data.

Don't use this task when: You already have a DataFileSpec configured and just need to load the file (use LoadDataFileTask), the file structure changes frequently making specs impractical, or you want to both scan and load in a single operation (use ProcessDataFileTask).


Parameters

Parameter Type Required Default Description
DataFileSpecKey string Yes None Unique identifier for the DataFileSpec entity to create or update. This key is used to retrieve the spec for subsequent file loads.
FilePath string Yes None Full path to the delimited file to scan. Can be a local filesystem path or S3 URI (e.g., s3://bucket/file.csv).
TableName string Yes None Name of the database table where this file's data will be loaded. Stored in the DataFileSpec for use by LoadDataFileTask.
RowTerminator string No \r\n Character sequence that terminates each row. Common values: \r\n (Windows), \n (Unix), \r (legacy Mac).
ColumnSeparator string No , Character that separates columns. Common values: , (CSV), \t (TSV), `
TextQualified bool No false Whether column values are enclosed in quotes. Set to true for files where text is quoted (e.g., "value1","value2").
HasHeader bool No true Whether the first row contains column names. If true, column names are read from the header; if false, columns are named Column1, Column2, etc.
MaxInvalidRows int No 0 Maximum number of invalid rows to tolerate during scanning before throwing an error. Set to 0 to fail on first invalid row.

Execution Flow

flowchart TD
    A[Start ScanDataFile] --> B[Create ReadDataFileSettings]
    B --> C[Create DelimitedStreamScanner]
    C --> D[Open file stream]
    D --> E{Read first row}
    E -->|Empty file| Z[Throw: Stream is empty]
    E -->|Success| F{HasHeader?}
    F -->|Yes| G[Use row values as column names]
    F -->|No| H[Generate Column1, Column2, etc.]
    G --> I[Initialize ColumnScanInfo for each column]
    H --> I
    I --> J{Read next data row}
    J -->|More rows| K[Update column scan info<br/>- Check if Boolean/Integer/Date/etc.<br/>- Track max width<br/>- Count nulls]
    K --> L{Log every 10,000 rows?}
    L -->|Yes| M[Log progress]
    L -->|No| J
    M --> J
    J -->|No more rows| N[Finalize column types based on scan results]
    N --> O[Determine type priority:<br/>Boolean > Integer > NumericString ><br/>Money > Measure > Date > DateTime > String]
    O --> P[Round up column widths to nearest 100]
    P --> Q[Create DataFileSpec with inferred columns]
    Q --> R[Save DataFileSpec to database]
    R --> S[End]

    style D fill:#e1f5ff
    style K fill:#e1f5ff
    style N fill:#fff4e1
    style R fill:#e1f5ff
    style Z fill:#ffe1e1

Processing Algorithm

Column Type Inference

The scanner reads every row in the file and tests each column value against multiple type parsers. A column can only be typed as a specific type if all non-null values in that column successfully parse as that type.

Type Priority (highest to lowest):

  1. Boolean - All values are "true", "false", "1", "0", "yes", "no" (case-insensitive)
  2. Integer - All values parse as whole numbers
  3. NumericString - Values with leading zeros like "00123" (must be stored as strings)
  4. Money - All values parse as currency (may include $, commas)
  5. Measure - All values parse as decimal numbers
  6. Date - All values parse as dates (no time component)
  7. DateTime - All values parse as dates with time
  8. String - Default fallback type

Width Calculation

Column widths are determined by the maximum observed width across all rows, then rounded up to the nearest 100 characters for database efficiency.

Example: If the longest value in a column is 127 characters, the width is set to 200.

Example Execution

For a file with 50,000 rows:


Usage Examples

Example 1: Basic CSV scan with default settings

var task = new ScanDataFileTask
{
    DataFileSpecKey = "PatientDemographics",
    FilePath = @"C:\Imports\patients.csv",
    TableName = "ImportPatientDemographics"
    // HasHeader defaults to true
    // ColumnSeparator defaults to ","
    // RowTerminator defaults to "\r\n"
};

Example 2: Pipe-delimited file without header

var task = new ScanDataFileTask
{
    DataFileSpecKey = "ClaimsData",
    FilePath = @"C:\Imports\claims.txt",
    TableName = "ImportClaims",
    ColumnSeparator = "|",
    HasHeader = false, // Columns will be named Column1, Column2, etc.
    MaxInvalidRows = 10 // Allow up to 10 invalid rows before failing
};

Example 3: S3 file with text qualification

var task = new ScanDataFileTask
{
    DataFileSpecKey = "VendorData",
    FilePath = "s3://mdclarity-imports/vendor_file.csv",
    TableName = "ImportVendorData",
    TextQualified = true, // Values are quoted: "value1","value2"
    ColumnSeparator = ",",
    RowTerminator = "\n" // Unix-style line endings
};

Example 4: Tab-delimited file from external system

var task = new ScanDataFileTask
{
    DataFileSpecKey = "ExternalCharges",
    FilePath = @"\\fileserver\imports\charges.tsv",
    TableName = "ImportCharges",
    ColumnSeparator = "\t", // Tab-separated values
    HasHeader = true,
    MaxInvalidRows = 0 // Fail immediately on invalid data
};

Related Tasks Comparison

Task Use Case Scans File Structure Loads Data Requires DataFileSpec Saves DataFileSpec
ScanDataFile Only discover file structure Yes No No Yes
LoadDataFileTask Load known file structure No Yes Yes (existing) No
ProcessDataFileTask Scan + load + merge in one task Yes (automatic) Yes No No (inline only)

When to use each task:


Performance Considerations

Database Impact

File Scanning Impact

Optimization Tips

Scenario Recommendation Rationale
Large files (millions of rows) Run once and reuse DataFileSpec Scanning takes time; reuse the spec for subsequent imports
Varying file structures Consider ProcessDataFileTask instead Scanning each time is unavoidable if structure changes
Slow network/S3 files Run during off-hours File streaming may take time for large remote files
Development/testing Scan a sample file first Validate structure with smaller file before production scan

Error Handling

The task uses a fail-fast approach for critical errors but allows configurable tolerance for invalid rows:

// From DelimitedStreamScanner.cs
if (!reader.ReadRow())
{
    throw new Exception($"Stream is empty.");
}

if (reader.DataCount == 0)
{
    throw new Exception($"Stream contained no data.");
}

Important error behaviors:

Note: The task does not validate that inferred types are correct for your business logic - it only ensures the file is parseable. Always validate the saved DataFileSpec before using it for production imports.


Common Pitfalls

Issue Problem Solution
Column type too generic File has mixed data types in a column (e.g., "123" and "ABC"), so it's inferred as String Clean source data or manually edit DataFileSpec after scanning to set correct type
Incorrect delimiter detected Task hangs or creates single-column spec because delimiter doesn't match file format Verify ColumnSeparator and RowTerminator match the actual file format
NumericString not detected Leading zeros in identifiers (like "00123") are lost when imported as integers Scanner should detect this, but verify DataFileSpec shows ColumnType.String for these columns
Date/DateTime parsing fails Non-US date formats (DD/MM/YYYY) may not parse correctly, causing columns to be typed as String Consider pre-processing files to use ISO 8601 format (YYYY-MM-DD)
Reserved column names Column names like "Key", "Value", "User" conflict with SQL reserved words Scanner automatically appends suffix to reserved names (see ColumnSpec.ReservedColumnNames)
Width too large All column widths rounded to nearest 100, wasting database space Acceptable tradeoff for simplicity; manually edit DataFileSpec if storage is critical
Large file scanning timeout Scanning millions of rows takes too long Increase task timeout or use ProcessDataFileTask with smaller batch sizes instead

Code References