About
← Back to Menu

Sag Chart Creator

Converts sag chart files from various utility software formats into .vlt files compatible with the Vulcan App. Uses JSON structure for improved features and reliability.

How It Works

  1. Upload — Drop in sag chart files (.txt, .csv, .xlsx, .pdf) or images (.jpg, .png) of stringing charts
  2. Detect Format — The system auto-detects the source format (Sag10, PLS-CADD, or image/PDF). For images and scanned PDFs, AI vision reads the chart data. Text-based PDFs are read directly.
  3. Verify — Review every value, edit conductor names, fix any AI errors, reorder sections, and approve each conductor
  4. Download — Export as .vlt

Supported Input Formats

FormatFile TypesSource
Sag10.txtSouthwire Sag10 software output
PLS-CADD.csv, .xlsxPLS-CADD stringing reports
Image / PDF (AI Vision).jpg, .png, .pdfPhotos, screenshots, or scanned PDFs of stringing charts

AI Vision for Images & PDFs

When sag chart images (JPEG/PNG) or scanned PDFs are uploaded, the system uses AI vision (Claude by Anthropic) to extract the data. Text-based PDFs are read directly without AI. This is highly effective but not perfect:

  • Images and PDFs are processed via the Anthropic API (encrypted in transit, not used for training)
  • Accuracy is typically 99%+ on clean, high-resolution charts
  • All extracted values must be reviewed on the verification screen
  • Individual cells can be edited directly if the AI misreads a value

JSON Schema (Version 3)

Nests the chart as Section → Conductor → Condition → Phase → Ruling Span → span. The structure is always the same shape: containers are always present, labels are optional. A chart with no section grouping is one section without a name; a chart that doesn't break out phases is one phase entry without a phase label. Optional keys are omitted entirely when empty.

{
  "version": 3,                          // Required. Schema version.
  "title": "Chart Name",                 // Required. Display name.
  "createdBy": "Company/Person",          // Optional. Author.
  "createdDate": "2026-07-22",            // Optional. Date string.
  "loadingDistrict": "250B",              // Optional. Loading district.
  "settings": {                           // Required. Chart-level config.
    "units": {                            // Required. Unit for every value below.
      "rulingSpan": "ft"|"m",
      "spanLength": "ft"|"m",
      "temperature": "F"|"C",
      "tension": "lbf"|"kN"|"N"|"kgf",
      "sag": "ft"|"m"|"in"|"cm",
      "weight": "lb/1000ft"|"lb/ft"|"kg/m"|"kg/km"
    },
    "tolerances": {                       // Optional. File-wide default, auto-applied on load.
      "tension": {                        // Optional. If present, plus and minus both required.
        "plus":  50,                      //   Max above target.
        "minus": 30                       //   Max below target.
      },
      "sag": {                            // Optional. If present, plus and minus both required.
        "plus":  0.5,                     //   Max above target.
        "minus": 0.5                      //   Max below target.
      }
    }
  },
  "sections": [                           // Required. At least one.
    {
      "name": "1A to 3",                  // Optional. Pulling section header.
      "conductors": [                     // Required. At least one.
        {
          "name": "795 26/7 ACSR Drake",  // Required. Display name.
          "weight": 1094,                 // Optional. In settings.units.weight.
          "tolerances": {                 // Optional. Overrides settings.tolerances for this conductor.
            "tension": {
              "plus": 40,
              "minus": 25
            },
            "sag": {
              "plus": 0.25,
              "minus": 0.25
            }
          },
          "conditions": [                 // Required. 1..N, ordered. Free-form
            {                             //   names (Initial/Final/Creep/...).
              "name": "Initial",          // Required. Condition name.
              "phases": [                 // Required. At least one.
                {
                  "phase": "A",           // Optional. Phase label.
                  "rulingSpans": [        // Required. At least one.
                    {
                      "value": 500,                     // Required. Ruling span length.
                      "spanLengths": [300, 400, 500, 600],   // Required. One or more spans.
                      "spanTitles": ["1A-2", "2-3", "3-4", "4-5A"],  // Optional. Matches spanLengths count.
                      "data": [                          // Required. Ascending temp order.
                        {
                          "temperature": 0,              // Scalar. In settings.units.temperature.
                          "tension": 5100,               // Scalar. In settings.units.tension.
                          "sags": [1.42, 2.53, 3.95, 5.69]   // One value per spanLength.
                        },
                        {
                          "temperature": 60,
                          "tension": 4670,
                          "sags": [1.75, 3.11, 4.87, 7.01]
                        },
                        {
                          "temperature": 120,
                          "tension": 4210,
                          "sags": [2.19, 3.90, 6.09, 8.77]
                        }
                      ]
                    }
                  ]
                },
                {
                  "phase": "B",           // Each phase: its own spanLengths/data.
                  "rulingSpans": [ ... ]
                }
              ]
            },
            {
              "name": "Final",            // Another condition, same shape.
              "phases": [ ... ]
            }
            // ...and any others (e.g. "Creep") in the same form.
          ]
        }
      ]
    }
  ]
}

Schema Notes

Units are required (v3)
settings.units is required and declares the unit of every numeric value in the file. Imperial values are ft, °F, lbf, and lb/1000ft. The app converts to internal units on import and handles display conversion. (Legacy version 2 files may omit units and default to imperial.)
Tolerance sides are paired
A tolerances block is optional, and each metric (tension, sag) within it is optional — but when a metric is present, both plus and minus are required.
Array counts must match
Each sags array must have the same number of entries as spanLengths for that ruling span. If spanTitles is present, it must also match.
Temperature rows in ascending order
Data rows should be sorted by temperature (low to high) for interpolation to work correctly.
Conditions are an arbitrary named list
A conductor's conditions array holds one or more independent conditions, each with a free-form name — not limited to Initial/Final. Any label (Creep, Everyday, After Load) is preserved verbatim, and each condition carries its own phases / rulingSpans / data (never interleaved columns). Order is Initial, then Final, then any others.
One file, many conductors
A single .vlt file can contain multiple conductors, each with multiple conditions and ruling spans.
One shape, always (v3)
The nesting sections → conductors → conditions → phases → rulingSpans is identical in every file — containers are never skipped or flattened. What varies is the optional labels: a chart with no section grouping is one section without name; an unphased chart is one phase entry without phase. A reader is a single loop with no shape branching.
Blank optional keys are omitted (v3)
Optional keys are left out entirely when they have no value — never written as null. No section grouping → no name; unphased → no phase label; no structure numbers → no spanTitles.
Per-conductor tolerances (v3)
settings.tolerances is the file-wide default. A conductor may optionally carry its own tolerances object (same asymmetric plus/minus shape); when present it replaces the file default entirely for that conductor — no per-metric merging. A reader should check the conductor first, then fall back to settings.tolerances.
Phase sits above ruling span (v3)
Because the three phases hang slightly differently, each phase's ruling span can differ. Each phases[] entry therefore carries its own rulingSpans, and each of those its own value, spanLengths, spanTitles, and data.
Reader back-compatibility (v3)
Version 2 files use the top-level conductors array; version 3 uses sections. A reader should branch on version: walk sections when it is 3 or higher, otherwise the existing conductors path.
Legacy Schema (Version 2)

Flat conductor list — no sections or phases. Still read by the app. Each sag chart file contains one or more conductors, each with conditions, ruling spans, and temperature data rows:

{
  "version": 2,                          // Required. Schema version.
  "title": "Chart Name",                 // Optional. Display name.
  "createdBy": "Company/Person",          // Optional. Author.
  "createdDate": "2026-03-30",            // Optional. Date string.
  "loadingDistrict": "250B",              // Optional. Loading district.
  "settings": {                           // Optional. Chart-level config.
    "units": {                            // Optional. Defaults to imperial.
      "rulingSpan": "ft"|"m",
      "spanLength": "ft"|"m",
      "temperature": "F"|"C",
      "tension": "lbf"|"kN"|"kgf",
      "sag": "ft"|"m"|"in"|"cm",
      "weight": "lb/1000ft"|"lb/ft"|"kg/m"|"kg/km"
    },
    "tolerances": {                       // Optional. Auto-applied on load.
      "tension": {                        // Optional. Asymmetric per direction.
        "plus":  50,                      //   Max above target. Optional.
        "minus": 30                       //   Max below target. Optional.
      },
      "sag": {                            // Optional. Asymmetric per direction.
        "plus":  0.5,                     //   Max above target. Optional.
        "minus": 0.5                      //   Max below target. Optional.
      }
    }
  },
  "conductors": [                         // Required. At least one.
    {
      "name": "336.4 26/7 ACSR",          // Required. Display name.
      "weight": 462,                      // Optional. In settings.units.weight.
      "conditions": [                     // Required. At least one.
        {
          "name": "Initial",              // Required. Condition name.
          "rulingSpans": [                 // Required. At least one.
            {
              "value": 500,                           // Required. Ruling span length.
              "spanLengths": [300, 400, 500, 600],    // Required. Span lengths.
              "spanTitles": ["1-2", "2-3", "3-4", "4-5"],  // Optional. Must match spanLengths count.
              "data": [                               // Required. Rows in ascending temp order.
                {
                  "temperature": 60,                  // In settings.units.temperature.
                  "tension": 4670,                    // In settings.units.tension.
                  "sags": [1.75, 3.11, 4.87, 7.01]   // In settings.units.sag. Must match spanLengths count.
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Need the legacy CSV format? CSV Converter