#13 Import works

Sapludināts
dcovington sapludināja 1 revīzijas no xlsx_import uz master pirms 2 dienas
  1. +6
    -1
      .gitignore
  2. +593
    -110
      App/Controllers/Jurisdiction/JurisdictionController.asp
  3. +17
    -0
      App/DomainModels/JurisdictionRepository.asp
  4. +234
    -7
      App/Views/Jurisdiction/import.asp
  5. +76
    -9
      Dockerfile
  6. Binārs
      ImportService/TrackingDataImport.vbs
  7. +2
    -2
      MVC/lib.Upload.asp
  8. +188
    -0
      _bmad-output/implementation-artifacts/tech-spec-add-xlsx-jurisdiction-import.md
  9. +207
    -0
      _bmad-output/planning-artifacts/sprint-change-proposal-2026-03-13.md
  10. +90
    -0
      _bmad-output/project-context.md
  11. +0
    -3
      codex.cmd
  12. +5
    -6
      docker-compose.yml
  13. +107
    -0
      docs/api-contracts.md
  14. +131
    -0
      docs/architecture.md
  15. +138
    -0
      docs/component-inventory.md
  16. +132
    -0
      docs/data-models.md
  17. +81
    -0
      docs/development-guide.md
  18. +72
    -0
      docs/index.md
  19. +284
    -0
      docs/module-map.md
  20. +88
    -0
      docs/project-overview.md
  21. +1
    -0
      docs/project-scan-report.json
  22. +148
    -0
      docs/source-tree-analysis.md
  23. +0
    -1524
      uploads/BRM Permit Infor 1-30-26.txt

+ 6
- 1
.gitignore Parādīt failu

@@ -4,4 +4,9 @@
/volumes
/codex
*nul
gittoken.txt
gittoken.txt
/_bmad
/bmad_output
/.agents
/.claude
/uploads

+ 593
- 110
App/Controllers/Jurisdiction/JurisdictionController.asp Parādīt failu

@@ -151,7 +151,7 @@ Class JurisdictionController

End sub

Public Sub Import
Public Sub Import

dim page_size : page_size = 10
dim page_num : page_num = Choice(Len(Request.Querystring("page_num")) > 0, Request.Querystring("page_num"), 1)
@@ -163,168 +163,651 @@ Class JurisdictionController
Model.PageSize = page_size
Model.PageCount = page_count
'Model.RecordCount = record_count
HTMLSecurity.SetAntiCSRFToken "JurisdictionImportForm"
%> <!--#include file="../../Views/Jurisdiction/import.asp"--> <%

End Sub

Public Sub ImportPost
Dim Upload: Set Upload = New FreeASPUpload
Public Sub ImportPost
If UCase(Request.ServerVariables("REQUEST_METHOD") & "") <> "POST" Then
Err.Raise 1, "JurisdictionController:ImportPost", "Action only responds to POST requests."
End If

Dim Upload : Set Upload = New FreeASPUpload
Dim uploadPath, uploadedFile, fileName, fileExt, fileSize
Dim maxFileSize, dotPos, recordCount
Dim maxFileSize, dotPos, nonce
Dim savedFileName, savedLocalFileName, savedPath
Dim workbookData, worksheetName, headerIndex, missingHeaders
Dim importToken, totalRows, duplicateCount

maxFileSize = 10485760 '10 MB in bytes
uploadPath = Server.MapPath("/uploads")
'Parse upload data

Upload.Upload
'Validate file upload
nonce = Upload.Form("nonce")

If Not HTMLSecurity.IsValidAntiCSRFToken("JurisdictionImportForm", nonce) Then
HTMLSecurity.ClearAntiCSRFToken "JurisdictionImportForm"
HTMLSecurity.SetAntiCSRFToken "JurisdictionImportForm"
SendImportJsonError "Invalid form state. Please refresh and try again."
Exit Sub
End If
HTMLSecurity.ClearAntiCSRFToken "JurisdictionImportForm"
HTMLSecurity.SetAntiCSRFToken "JurisdictionImportForm"

If Upload.FileExists("filename") = False Then
Flash.AddError "No file selected for upload."
MVC.RedirectToAction "Import"
SendImportJsonError "No file selected for upload."
Exit Sub
End If
Set uploadedFile = Upload.UploadedFiles("filename")
fileName = uploadedFile.FileName
fileSize = uploadedFile.Length
'Extract file extension

dotPos = InStrRev(fileName, ".")
If dotPos > 0 Then
fileExt = LCase(Mid(fileName, dotPos))
Else
fileExt = ""
End If
'Validate file type
If fileExt <> ".csv" And fileExt <> ".txt" Then
Flash.AddError "Only .csv and .txt files are allowed."
MVC.RedirectToAction "Import"

If fileExt <> ".xlsx" Then
SendImportJsonError "Only .xlsx files are allowed."
Exit Sub
End If
'Validate file size

If fileSize > maxFileSize Then
Flash.AddError "File size exceeds 10 MB limit."
MVC.RedirectToAction "Import"
SendImportJsonError "File size exceeds 10 MB limit."
Exit Sub
End If
'Save the file to configured folder
Upload.Save uploadPath

'Remove the first line of the CSV (non-header info line)
StripFirstLine uploadedFile.Path
EnsureFolderExists uploadPath
Upload.SaveOne uploadPath, 0, savedFileName, savedLocalFileName
savedPath = uploadPath & "\" & savedLocalFileName

'Open CSV with Jet driver and iterate records
Dim conn, rs, connString, folderPath, csvFileName, fso
Set fso = Server.CreateObject("Scripting.FileSystemObject")
folderPath = fso.GetParentFolderName(uploadedFile.Path)
csvFileName = fso.GetFileName(uploadedFile.Path)
Set fso = Nothing
On Error Resume Next
workbookData = ReadWorkbookData(savedPath, worksheetName)
If Err.Number <> 0 Then
Dim workbookErr : workbookErr = Err.Description
Err.Clear
On Error GoTo 0
DeleteFileIfExists savedPath
SendImportJsonError "Unable to read the XLSX workbook. Verify Microsoft ACE OLEDB is installed and the workbook is valid. " & workbookErr
Exit Sub
End If
On Error GoTo 0

If Not WorkbookHasHeaderRows(workbookData) Then
DeleteFileIfExists savedPath
SendImportJsonError "The uploaded workbook must contain an information row and a header row."
Exit Sub
End If

Set headerIndex = BuildHeaderIndex(workbookData, 1)
missingHeaders = MissingRequiredHeaders(headerIndex)
If Len(missingHeaders) > 0 Then
DeleteFileIfExists savedPath
SendImportJsonError "Missing required header(s): " & missingHeaders
Exit Sub
End If

totalRows = CountImportRows(workbookData)
duplicateCount = CountDuplicateJCodes(workbookData, CLng(headerIndex.Item("JURISDICTION")))
importToken = CreateImportToken()

InitializeImportSession importToken, workbookData, worksheetName, fileName, savedPath, totalRows, duplicateCount, headerIndex
SendImportJsonSuccess importToken, fileName, totalRows, duplicateCount
End Sub

Public Sub ImportProgress
Dim importToken : importToken = Trim(Request.QueryString("token") & "")
If Len(importToken) = 0 Then
SendImportJsonError "Missing import token."
Exit Sub
End If

If Not ImportExists(importToken) Then
SendImportJsonError "The requested import session was not found. Please upload the workbook again."
Exit Sub
End If

Dim phase : phase = GetImportValue(importToken, "Phase")
If phase <> "complete" And phase <> "error" Then
ProcessImportChunk importToken, 25
End If

SendImportProgressJson importToken
End Sub

Dim fmtType
If fileExt = ".txt" Then
fmtType = "TabDelimited"
Private Sub ProcessImportChunk(importToken, maxRowsPerRequest)
Dim sessionKey : sessionKey = ImportSessionKey(importToken)
Dim workbookData : workbookData = Session(sessionKey & "Data")
Dim currentRow, lastRow, rowsProcessed

If Not IsArray(workbookData) Then
SetImportValue importToken, "Phase", "error"
SetImportValue importToken, "StatusMessage", "Workbook data is no longer available in session."
AppendImportError importToken, 0, "Workbook data is no longer available in session.", ""
Exit Sub
End If

currentRow = CLng(GetImportValue(importToken, "CurrentRow"))
lastRow = UBound(workbookData, 2)
rowsProcessed = 0

If currentRow < 2 Then currentRow = 2
SetImportValue importToken, "Phase", "processing"

Do While currentRow <= lastRow And rowsProcessed < maxRowsPerRequest
ProcessImportRow importToken, workbookData, currentRow
currentRow = currentRow + 1
rowsProcessed = rowsProcessed + 1
SetImportValue importToken, "CurrentRow", currentRow
SetImportValue importToken, "ProcessedRows", CLng(GetImportValue(importToken, "ProcessedRows")) + 1
Loop

If currentRow > lastRow Then
SetImportValue importToken, "Phase", "complete"
SetImportValue importToken, "StatusMessage", "Import complete."
Session.Contents.Remove sessionKey & "Data"
DeleteFileIfExists GetImportValue(importToken, "FilePath")
Session.Contents.Remove sessionKey & "FilePath"
Else
fmtType = "Delimited"
SetImportValue importToken, "StatusMessage", "Processed " & GetImportValue(importToken, "ProcessedRows") & " of " & GetImportValue(importToken, "TotalRows") & " row(s)."
End If
End Sub

Private Sub ProcessImportRow(importToken, workbookData, rowIndex)
Dim county, jurisdictionText, jCode, jurisdictionName, mailingAddress, cityTownship, zipPlusFour, mailerId
Dim csz, imbDigits, imb, jurisdictionModel, recordSummary

county = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "CountyIndex"), rowIndex))
jurisdictionText = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "JurisdictionIndex"), rowIndex))
mailingAddress = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "MailingAddressIndex"), rowIndex))
cityTownship = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "CityTownshipIndex"), rowIndex))
zipPlusFour = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "ZipPlusFourIndex"), rowIndex))
mailerId = SafeWorkbookValue(workbookData(GetImportColumnIndex(importToken, "MailerIdIndex"), rowIndex))
recordSummary = BuildImportRecordSummary(county, jurisdictionText, mailingAddress, cityTownship, zipPlusFour, mailerId)

If Len(Trim(county & jurisdictionText & mailingAddress & cityTownship & zipPlusFour & mailerId)) = 0 Then
IncrementImportCounter importToken, "InvalidCount"
AppendImportError importToken, rowIndex + 1, "Row is empty.", recordSummary
Exit Sub
End If

jCode = ExtractJurisdictionCode(jurisdictionText)
If Len(jCode) = 0 Then
IncrementImportCounter importToken, "InvalidCount"
AppendImportError importToken, rowIndex + 1, "Jurisdiction field is missing a code in parentheses.", recordSummary
Exit Sub
End If

jurisdictionName = NormalizeJurisdictionName(ExtractJurisdictionName(jurisdictionText))
If Len(jurisdictionName) = 0 Then
IncrementImportCounter importToken, "InvalidCount"
AppendImportError importToken, rowIndex + 1, "Jurisdiction name could not be parsed.", recordSummary
Exit Sub
End If

csz = BuildCSZ(cityTownship, zipPlusFour)
imbDigits = BuildIMBDigits(mailerId, zipPlusFour)
If Len(imbDigits) = 0 Then
IncrementImportCounter importToken, "InvalidCount"
AppendImportError importToken, rowIndex + 1, "Unable to build IMB digits from Mailer ID Option 1 and ZIP + 4.", recordSummary
Exit Sub
End If
connString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & folderPath & ";Extended Properties=""text;HDR=YES;FMT=" & fmtType & """;"

Set conn = Server.CreateObject("ADODB.Connection")
On Error Resume Next
conn.Open connString
imb = GetIMBCodec.EncodeDigits(imbDigits)
If Err.Number <> 0 Then
Flash.AddError "Unable to read CSV file."
MVC.RedirectToAction "Import"
Dim imbErr : imbErr = Err.Description
Err.Clear
On Error GoTo 0
IncrementImportCounter importToken, "FailedCount"
AppendImportError importToken, rowIndex + 1, "IMB generation failed for JCode " & jCode & ". " & imbErr, recordSummary
Exit Sub
End If
On Error Goto 0

Dim sql
sql = "SELECT q.[County], q.[Jurisdiction], q.JCODE, " & _
"IIf(InStr(1, UCase(q.NameRaw), ' CITY') > 0, " & _
"'CITY OF ' & Trim(Replace(UCase(q.NameRaw), ' CITY', '')), " & _
"q.NameRaw) AS [Name], " & _
"q.[Mailing Address] AS Mailing_Address, " & _
"q.[City & Township] & ' ' & q.[ZIP + 4] AS CSZ, " & _
"'' AS IMB, " & _
"'007' & q.[Mailer ID Option 1] & '000000' & Replace(q.[ZIP + 4], '-', '') AS IMB_Digits " & _
"FROM (" & _
"SELECT [County], [Jurisdiction], [Mailing Address], [City & Township], [ZIP + 4], [Mailer ID Option 1], " & _
"IIf(InStr(1,[Jurisdiction],'(') > 0 And InStr(1,[Jurisdiction],')') > InStr(1,[Jurisdiction],'('), " & _
"Trim(Mid([Jurisdiction], InStr(1,[Jurisdiction],'(') + 1, InStr(1,[Jurisdiction],')') - InStr(1,[Jurisdiction],'(') - 1)), " & _
"Null) AS JCODE, " & _
"IIf(InStr(1,[Jurisdiction],'(') > 0, " & _
"Trim(Left([Jurisdiction], InStr(1,[Jurisdiction],'(') - 1)), " & _
"Trim([Jurisdiction])) AS NameRaw " & _
"FROM [" & csvFileName & "]) AS q"

Set rs = conn.Execute(sql)

recordCount = 0
Do While Not rs.EOF
Dim jurisdiction : Set jurisdiction = New JurisdictionModel_Class
jurisdiction.JCode = Trim(rs.Fields("JCODE").Value & "")
jurisdiction.Name = Trim(rs.Fields("Name").Value & "")
jurisdiction.Mailing_Address = Trim(rs.Fields("Mailing_Address").Value & "")
jurisdiction.CSZ = Trim(rs.Fields("CSZ").Value & "")
jurisdiction.IMB_Digits = Trim(rs.Fields("IMB_Digits").Value & "")
jurisdiction.IMB = GetIMBCodec.EncodeDigits(jurisdiction.IMB_Digits)

'JurisdictionRepository.AddNew jurisdiction
recordCount = recordCount + 1
rs.MoveNext
Loop
On Error GoTo 0

On Error Resume Next
Set jurisdictionModel = JurisdictionRepository.FindByJCode(jCode)
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0
Set jurisdictionModel = New JurisdictionModel_Class
jurisdictionModel.JCode = jCode
jurisdictionModel.Name = jurisdictionName
jurisdictionModel.Mailing_Address = mailingAddress
jurisdictionModel.CSZ = csz
jurisdictionModel.IMB_Digits = imbDigits
jurisdictionModel.IMB = imb

On Error Resume Next
JurisdictionRepository.AddNewWithJCode jurisdictionModel
If Err.Number <> 0 Then
Dim insertErr : insertErr = Err.Description
Err.Clear
On Error GoTo 0
IncrementImportCounter importToken, "FailedCount"
AppendImportError importToken, rowIndex + 1, "Failed to insert new JCode " & jCode & ". " & insertErr, recordSummary
Exit Sub
End If
On Error GoTo 0

IncrementImportCounter importToken, "InsertedCount"
Exit Sub
End If
On Error GoTo 0

jurisdictionModel.Name = jurisdictionName
jurisdictionModel.Mailing_Address = mailingAddress
jurisdictionModel.CSZ = csz
jurisdictionModel.IMB_Digits = imbDigits
jurisdictionModel.IMB = imb

On Error Resume Next
JurisdictionRepository.Update jurisdictionModel
If Err.Number <> 0 Then
Dim updateErr : updateErr = Err.Description
Err.Clear
On Error GoTo 0
IncrementImportCounter importToken, "FailedCount"
AppendImportError importToken, rowIndex + 1, "Failed to update JCode " & jCode & ". " & updateErr, recordSummary
Exit Sub
End If
On Error GoTo 0

IncrementImportCounter importToken, "UpdatedCount"
End Sub

Private Function ReadWorkbookData(filePath, ByRef worksheetName)
Dim conn, rs, connString, sql

connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & filePath & ";Extended Properties=""Excel 12.0 Xml;HDR=NO;IMEX=1"";"
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open connString

worksheetName = FirstWorksheetName(conn)
If Len(worksheetName) = 0 Then
conn.Close
Set conn = Nothing
Err.Raise vbObjectError + 2010, "JurisdictionController", "No worksheet was found in the uploaded workbook."
End If

sql = "SELECT * FROM [" & worksheetName & "]"
Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open sql, conn, 3, 1

If rs.EOF And rs.BOF Then
rs.Close
conn.Close
Set rs = Nothing
Set conn = Nothing
Err.Raise vbObjectError + 2011, "JurisdictionController", "The worksheet does not contain any rows."
End If

ReadWorkbookData = rs.GetRows()

rs.Close
conn.Close
Set rs = Nothing
Set conn = Nothing
End Function

Private Function FirstWorksheetName(conn)
Dim schema, tableName

Set schema = conn.OpenSchema(20)
Do Until schema.EOF
tableName = Trim(schema("TABLE_NAME") & "")
If InStr(tableName, "$") > 0 And InStr(tableName, "_xlnm") = 0 Then
FirstWorksheetName = tableName
schema.Close
Set schema = Nothing
Exit Function
End If
schema.MoveNext
Loop

'Set success message with record count
Flash.Success = "File '" & fileName & "' uploaded successfully. Records imported: " & recordCount
MVC.RedirectToAction "Import"
If Not schema Is Nothing Then
schema.Close
Set schema = Nothing
End If
FirstWorksheetName = ""
End Function

Private Function BuildHeaderIndex(workbookData, headerRowIndex)
Dim headerMap, colIndex, headerName

Set headerMap = Server.CreateObject("Scripting.Dictionary")
For colIndex = 0 To UBound(workbookData, 1)
headerName = UCase(Trim(SafeWorkbookValue(workbookData(colIndex, headerRowIndex))))
If Len(headerName) > 0 Then
If Not headerMap.Exists(headerName) Then
headerMap.Add headerName, colIndex
End If
End If
Next

Set BuildHeaderIndex = headerMap
End Function

Private Function WorkbookHasHeaderRows(workbookData)
On Error Resume Next
WorkbookHasHeaderRows = (IsArray(workbookData) And UBound(workbookData, 2) >= 1)
If Err.Number <> 0 Then
Err.Clear
WorkbookHasHeaderRows = False
End If
On Error GoTo 0
End Function

Private Function MissingRequiredHeaders(headerIndex)
Dim requiredHeaders, missingHeaders, i

requiredHeaders = Array("County", "Jurisdiction", "Mailing Address", "City & Township", "ZIP + 4", "Mailer ID Option 1")
missingHeaders = ""

For i = 0 To UBound(requiredHeaders)
If Not headerIndex.Exists(UCase(requiredHeaders(i))) Then
If Len(missingHeaders) > 0 Then missingHeaders = missingHeaders & ", "
missingHeaders = missingHeaders & requiredHeaders(i)
End If
Next

MissingRequiredHeaders = missingHeaders
End Function

Private Function CountImportRows(workbookData)
If Not WorkbookHasHeaderRows(workbookData) Then
CountImportRows = 0
ElseIf UBound(workbookData, 2) < 2 Then
CountImportRows = 0
Else
CountImportRows = UBound(workbookData, 2) - 1
End If
End Function

Private Function CountDuplicateJCodes(workbookData, jurisdictionColumnIndex)
Dim seenCodes, rowIndex, jCode

Set seenCodes = Server.CreateObject("Scripting.Dictionary")
CountDuplicateJCodes = 0

If Not WorkbookHasHeaderRows(workbookData) Then Exit Function
If UBound(workbookData, 2) < 2 Then Exit Function

For rowIndex = 2 To UBound(workbookData, 2)
jCode = ExtractJurisdictionCode(SafeWorkbookValue(workbookData(jurisdictionColumnIndex, rowIndex)))
If Len(jCode) > 0 Then
If seenCodes.Exists(jCode) Then
CountDuplicateJCodes = CountDuplicateJCodes + 1
Else
seenCodes.Add jCode, True
End If
End If
Next
End Function

Private Sub InitializeImportSession(importToken, workbookData, worksheetName, fileName, filePath, totalRows, duplicateCount, headerIndex)
Dim sessionKey : sessionKey = ImportSessionKey(importToken)

Session(sessionKey & "Data") = workbookData
Session(sessionKey & "WorksheetName") = worksheetName
Session(sessionKey & "FileName") = fileName
Session(sessionKey & "FilePath") = filePath
Session(sessionKey & "CurrentRow") = 2
Session(sessionKey & "ProcessedRows") = 0
Session(sessionKey & "TotalRows") = totalRows
Session(sessionKey & "UpdatedCount") = 0
Session(sessionKey & "InsertedCount") = 0
Session(sessionKey & "UnmatchedCount") = 0
Session(sessionKey & "InvalidCount") = 0
Session(sessionKey & "FailedCount") = 0
Session(sessionKey & "DuplicateCount") = duplicateCount
Session(sessionKey & "Phase") = "staged"
Session(sessionKey & "StatusMessage") = "Workbook uploaded. Processing will begin shortly."
Session(sessionKey & "Errors") = ""
Session(sessionKey & "CountyIndex") = CLng(headerIndex.Item("COUNTY"))
Session(sessionKey & "JurisdictionIndex") = CLng(headerIndex.Item("JURISDICTION"))
Session(sessionKey & "MailingAddressIndex") = CLng(headerIndex.Item("MAILING ADDRESS"))
Session(sessionKey & "CityTownshipIndex") = CLng(headerIndex.Item("CITY & TOWNSHIP"))
Session(sessionKey & "ZipPlusFourIndex") = CLng(headerIndex.Item("ZIP + 4"))
Session(sessionKey & "MailerIdIndex") = CLng(headerIndex.Item("MAILER ID OPTION 1"))
End Sub

Private Function CreateImportToken()
Dim token : token = HTMLSecurity.Nonce()
token = Replace(token, "{", "")
token = Replace(token, "}", "")
CreateImportToken = token
End Function

Private Function ImportSessionKey(importToken)
ImportSessionKey = "JurisdictionImport." & importToken & "."
End Function

Private Function ImportExists(importToken)
ImportExists = (Len(GetImportValue(importToken, "Phase")) > 0)
End Function

Private Function GetImportValue(importToken, name)
GetImportValue = Session(ImportSessionKey(importToken) & name)
End Function

Private Sub SetImportValue(importToken, name, value)
Session(ImportSessionKey(importToken) & name) = value
End Sub
Private Sub StripFirstLine(filePath)
Dim fso, ts, remainingText

Set fso = Server.CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(filePath) Then
Set fso = Nothing
Exit Sub
Private Function GetImportColumnIndex(importToken, name)
GetImportColumnIndex = CLng(GetImportValue(importToken, name))
End Function

Private Sub IncrementImportCounter(importToken, counterName)
SetImportValue importToken, counterName, CLng(GetImportValue(importToken, counterName)) + 1
End Sub

Private Function ExtractJurisdictionCode(jurisdictionText)
Dim openPos, closePos

openPos = InStrRev(jurisdictionText, "(")
closePos = InStrRev(jurisdictionText, ")")

If openPos > 0 And closePos > openPos Then
ExtractJurisdictionCode = Trim(Mid(jurisdictionText, openPos + 1, closePos - openPos - 1))
Else
ExtractJurisdictionCode = ""
End If
End Function

Set ts = fso.OpenTextFile(filePath, 1) ' 1 = ForReading
If ts.AtEndOfStream Then
ts.Close
Set ts = Nothing
Set fso = Nothing
Exit Sub
Private Function ExtractJurisdictionName(jurisdictionText)
Dim openPos

openPos = InStrRev(jurisdictionText, "(")
If openPos > 0 Then
ExtractJurisdictionName = Trim(Left(jurisdictionText, openPos - 1))
Else
ExtractJurisdictionName = Trim(jurisdictionText)
End If
End Function

Private Function NormalizeJurisdictionName(jurisdictionName)
Dim normalizedName

normalizedName = Trim(jurisdictionName)
If Len(normalizedName) >= 5 Then
If UCase(Right(normalizedName, 5)) = " CITY" Then
normalizedName = "CITY OF " & Trim(Left(normalizedName, Len(normalizedName) - 5))
End If
End If

'Skip the first line
ts.ReadLine
NormalizeJurisdictionName = normalizedName
End Function

'Read the rest of the file
If Not ts.AtEndOfStream Then
remainingText = ts.ReadAll
Private Function BuildCSZ(cityTownship, zipPlusFour)
BuildCSZ = Trim(Trim(cityTownship) & " " & Trim(zipPlusFour))
End Function

Private Function BuildIMBDigits(mailerId, zipPlusFour)
Dim cleanMailerId, cleanZip

cleanMailerId = DigitsOnly(mailerId)
cleanZip = DigitsOnly(zipPlusFour)

If Len(cleanMailerId) = 0 Or Len(cleanZip) = 0 Then
BuildIMBDigits = ""
Else
BuildIMBDigits = "00778" & cleanMailerId & "000000" & cleanZip
End If
End Function

Private Function DigitsOnly(inputValue)
Dim i, ch

DigitsOnly = ""
For i = 1 To Len(inputValue)
ch = Mid(inputValue, i, 1)
If ch >= "0" And ch <= "9" Then
DigitsOnly = DigitsOnly & ch
End If
Next
End Function

Private Function SafeWorkbookValue(value)
If IsNull(value) Or IsEmpty(value) Then
SafeWorkbookValue = ""
Else
SafeWorkbookValue = Trim(CStr(value))
End If
End Function

Private Function BuildImportRecordSummary(county, jurisdictionText, mailingAddress, cityTownship, zipPlusFour, mailerId)
BuildImportRecordSummary = "County=" & DisplayImportField(county) & _
"; Jurisdiction=" & DisplayImportField(jurisdictionText) & _
"; Mailing Address=" & DisplayImportField(mailingAddress) & _
"; City & Township=" & DisplayImportField(cityTownship) & _
"; ZIP + 4=" & DisplayImportField(zipPlusFour) & _
"; Mailer ID Option 1=" & DisplayImportField(mailerId)
End Function

Private Function DisplayImportField(value)
If Len(Trim(value & "")) = 0 Then
DisplayImportField = "<blank>"
Else
DisplayImportField = value & ""
End If
End Function

Private Sub AppendImportError(importToken, rowNumber, errorMessage, recordSummary)
Dim sessionKey, currentErrors, fullMessage

sessionKey = ImportSessionKey(importToken)
currentErrors = Session(sessionKey & "Errors") & ""

If rowNumber > 0 Then
fullMessage = "Row " & rowNumber & ": " & errorMessage
Else
fullMessage = errorMessage
End If

If Len(Trim(recordSummary & "")) > 0 Then
fullMessage = fullMessage & " Record: " & recordSummary
End If

If Len(currentErrors) > 0 Then
currentErrors = currentErrors & Chr(30)
End If
currentErrors = currentErrors & fullMessage

Session(sessionKey & "Errors") = currentErrors
End Sub

Private Sub SendImportJsonSuccess(importToken, fileName, totalRows, duplicateCount)
Response.ContentType = "application/json"
Response.Write "{""ok"":true,""token"":""" & JsonEscape(importToken) & """,""fileName"":""" & JsonEscape(fileName) & """,""phase"":""staged"",""totalRows"":" & totalRows & ",""processedRows"":0,""updatedCount"":0,""insertedCount"":0,""invalidCount"":0,""failedCount"":0,""duplicateCount"":" & duplicateCount & ",""percentComplete"":0,""statusMessage"":""Workbook uploaded. Processing will begin shortly."",""nextNonce"":""" & JsonEscape(HTMLSecurity.GetAntiCSRFToken("JurisdictionImportForm")) & """,""errors"":[]}"
Response.End
End Sub

Private Sub SendImportProgressJson(importToken)
Dim phase, totalRows, processedRows, updatedCount, insertedCount, invalidCount, failedCount, duplicateCount
Dim percentComplete, statusMessage, errorsJson

phase = GetImportValue(importToken, "Phase")
totalRows = CLng(GetImportValue(importToken, "TotalRows"))
processedRows = CLng(GetImportValue(importToken, "ProcessedRows"))
updatedCount = CLng(GetImportValue(importToken, "UpdatedCount"))
insertedCount = CLng(GetImportValue(importToken, "InsertedCount"))
invalidCount = CLng(GetImportValue(importToken, "InvalidCount"))
failedCount = CLng(GetImportValue(importToken, "FailedCount"))
duplicateCount = CLng(GetImportValue(importToken, "DuplicateCount"))
statusMessage = GetImportValue(importToken, "StatusMessage") & ""
errorsJson = JsonArrayFromDelimitedString(GetImportValue(importToken, "Errors") & "")

If totalRows > 0 Then
percentComplete = Int((processedRows / totalRows) * 100)
Else
remainingText = ""
percentComplete = 100
End If
If phase = "complete" Then percentComplete = 100

Response.ContentType = "application/json"
Response.Write "{""ok"":true,""token"":""" & JsonEscape(importToken) & """,""phase"":""" & JsonEscape(phase) & """,""totalRows"":" & totalRows & ",""processedRows"":" & processedRows & ",""updatedCount"":" & updatedCount & ",""insertedCount"":" & insertedCount & ",""invalidCount"":" & invalidCount & ",""failedCount"":" & failedCount & ",""duplicateCount"":" & duplicateCount & ",""percentComplete"":" & percentComplete & ",""statusMessage"":""" & JsonEscape(statusMessage) & """,""errors"":" & errorsJson & "}"
Response.End
End Sub

Private Sub SendImportJsonError(errorMessage)
Response.ContentType = "application/json"
Response.Status = "400 Bad Request"
Response.Write "{""ok"":false,""message"":""" & JsonEscape(errorMessage) & """,""nextNonce"":""" & JsonEscape(HTMLSecurity.GetAntiCSRFToken("JurisdictionImportForm")) & """}"
Response.End
End Sub

Private Function JsonArrayFromDelimitedString(delimitedValue)
Dim items, i, result

If Len(delimitedValue) = 0 Then
JsonArrayFromDelimitedString = "[]"
Exit Function
End If
ts.Close

'Rewrite the file without the first line
Set ts = fso.OpenTextFile(filePath, 2) ' 2 = ForWriting
ts.Write remainingText
ts.Close
items = Split(delimitedValue, Chr(30))
result = "["
For i = 0 To UBound(items)
If i > 0 Then result = result & ","
result = result & """" & JsonEscape(items(i)) & """"
Next
result = result & "]"

JsonArrayFromDelimitedString = result
End Function

Private Function JsonEscape(value)
value = Replace(value & "", "\", "\\")
value = Replace(value, """", "\""")
value = Replace(value, vbCrLf, "\n")
value = Replace(value, vbCr, "\n")
value = Replace(value, vbLf, "\n")
JsonEscape = value
End Function

Private Sub EnsureFolderExists(folderPath)
Dim fso : Set fso = Server.CreateObject("Scripting.FileSystemObject")
If Not fso.FolderExists(folderPath) Then
fso.CreateFolder folderPath
End If
Set fso = Nothing
End Sub

Private Sub DeleteFileIfExists(filePath)
Dim fso
If Len(Trim(filePath & "")) = 0 Then Exit Sub

Set ts = Nothing
Set fso = Server.CreateObject("Scripting.FileSystemObject")
If fso.FileExists(filePath) Then
On Error Resume Next
fso.DeleteFile filePath, True
On Error GoTo 0
End If
Set fso = Nothing
End Sub
End Class


+ 17
- 0
App/DomainModels/JurisdictionRepository.asp Parādīt failu

@@ -205,6 +205,23 @@ Class JurisdictionRepository_Class
Destroy rs
End Sub

Public Sub AddNewWithJCode(ByRef model)
dim sql : sql = "INSERT INTO [Jurisdiction] (" &_
"[JCode]," &_
"[Name]," &_
"[Mailing_Address]," &_
"[CSZ]," &_
"[IMB]," &_
"[IMB_Digits])" &_
"VALUES (?,?,?,?,?,?)"
DAL.Execute sql, Array(model.JCode, _
model.Name, _
model.Mailing_Address, _
model.CSZ, _
model.IMB, _
model.IMB_Digits)
End Sub

Public Sub Update(model)
dim sql : sql = "UPDATE [Jurisdiction] SET [Name] = ?," &_
"[Mailing_Address] = ?," &_


+ 234
- 7
App/Views/Jurisdiction/import.asp Parādīt failu

@@ -3,23 +3,250 @@
<% Flash().ShowErrorsIfPresent %>
<% Flash().ShowSuccessIfPresent %>

<%= HTML.FormTag("Jurisdiction", "ImportPost", empty, Array("enctype","multipart/form-data")) %>
<%= HTML.FormTag("Jurisdiction", "ImportPost", Array("_P","1"), Array("enctype","multipart/form-data","id","jurisdiction-import-form")) %>
<%= HTML.Hidden("nonce", HTMLSecurity.GetAntiCSRFToken("JurisdictionImportForm")) %>
<hr />
<div><p>Upload a CSV (.csv) or tab-delimited (.txt) file to import jurisdiction data</p></div>
<div>
<p>Upload the BRM permit workbook as an Excel file. Row 1 is ignored, row 2 must contain the expected headers, and jurisdictions are updated or inserted using the JCode inside the <code>Jurisdiction</code> column.</p>
</div>

<div class="form-group">
<div class="row">
<div class="col-md-4">
<div class="col-md-5">
<div class="form-group">
<label for="filename">Select File</label>
<input type="file" id="filename" name="filename" accept=".csv,.txt" required>
<label for="filename">Select XLSX File</label>
<input type="file" id="filename" name="filename" accept=".xlsx" required class="form-control">
</div>
</div>
</div>
<p></p>
<%= HTML.Button("submit", "<i class='glyphicon glyphicon-ok'></i> Upload", "btn-primary") %>
<small class="form-text text-muted">Accepted formats: .csv, .txt | Max size: 10 MB</small>
<button type="submit" id="import-submit" class="btn btn-primary"><i class="glyphicon glyphicon-upload"></i> Start Import</button>
<small class="form-text text-muted">Accepted format: .xlsx | Max size: 10 MB</small>
</div>

</form>

<div id="import-feedback" class="panel panel-default" style="display:none; margin-top:20px;">
<div class="panel-heading">
<strong>Import Progress</strong>
</div>
<div class="panel-body">
<p id="import-status">Waiting to start.</p>
<div class="progress">
<div id="import-progress-bar" class="progress-bar progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" style="width:0%;">
0%
</div>
</div>
<div class="row">
<div class="col-sm-2"><strong>Total</strong><div id="summary-total">0</div></div>
<div class="col-sm-2"><strong>Processed</strong><div id="summary-processed">0</div></div>
<div class="col-sm-2"><strong>Updated</strong><div id="summary-updated">0</div></div>
<div class="col-sm-2"><strong>Inserted</strong><div id="summary-inserted">0</div></div>
<div class="col-sm-2"><strong>Invalid</strong><div id="summary-invalid">0</div></div>
<div class="col-sm-2"><strong>Failed</strong><div id="summary-failed">0</div></div>
</div>
<div class="row" style="margin-top:12px;">
<div class="col-sm-2"><strong>Duplicates</strong><div id="summary-duplicates">0</div></div>
</div>
<div id="import-error-panel" class="alert alert-warning" style="display:none; margin-top:15px;">
<strong>Row Issues</strong>
<ul id="import-error-list" style="margin-top:10px; margin-bottom:0;"></ul>
</div>
</div>
</div>

<div id="import-message" class="alert" style="display:none; margin-top:20px;"></div>

<script type="text/javascript">
(function () {
var form = document.getElementById('jurisdiction-import-form');
var fileInput = document.getElementById('filename');
var submitButton = document.getElementById('import-submit');
var nonceField = form.querySelector('input[name="nonce"]');
var feedbackPanel = document.getElementById('import-feedback');
var messageBox = document.getElementById('import-message');
var statusText = document.getElementById('import-status');
var progressBar = document.getElementById('import-progress-bar');
var pollTimer = null;

var summaryFields = {
totalRows: document.getElementById('summary-total'),
processedRows: document.getElementById('summary-processed'),
updatedCount: document.getElementById('summary-updated'),
insertedCount: document.getElementById('summary-inserted'),
invalidCount: document.getElementById('summary-invalid'),
failedCount: document.getElementById('summary-failed'),
duplicateCount: document.getElementById('summary-duplicates')
};

var errorPanel = document.getElementById('import-error-panel');
var errorList = document.getElementById('import-error-list');
var pollUrlBase = '<%= Routes.UrlTo("Jurisdiction", "ImportProgress", Array("_P","1")) %>';

function setMessage(cssClass, message) {
messageBox.className = 'alert ' + cssClass;
messageBox.textContent = message;
messageBox.style.display = 'block';
}

function clearMessage() {
messageBox.style.display = 'none';
messageBox.textContent = '';
}

function updateSummary(data) {
summaryFields.totalRows.textContent = data.totalRows || 0;
summaryFields.processedRows.textContent = data.processedRows || 0;
summaryFields.updatedCount.textContent = data.updatedCount || 0;
summaryFields.insertedCount.textContent = data.insertedCount || 0;
summaryFields.invalidCount.textContent = data.invalidCount || 0;
summaryFields.failedCount.textContent = data.failedCount || 0;
summaryFields.duplicateCount.textContent = data.duplicateCount || 0;
}

function refreshNonce(data) {
if (nonceField && data && data.nextNonce) {
nonceField.value = data.nextNonce;
}
}

function updateProgress(data) {
var percent = data.percentComplete || 0;
progressBar.style.width = percent + '%';
progressBar.setAttribute('aria-valuenow', percent);
progressBar.textContent = percent + '%';
statusText.textContent = data.statusMessage || 'Processing import.';
updateSummary(data);
renderErrors(data.errors || []);

if (data.phase === 'complete') {
progressBar.className = 'progress-bar';
setMessage('alert-success', 'Jurisdiction import complete.');
} else if (data.phase === 'error') {
progressBar.className = 'progress-bar';
setMessage('alert-danger', data.statusMessage || 'Import failed.');
}
}

function renderErrors(errors) {
errorList.innerHTML = '';
if (!errors.length) {
errorPanel.style.display = 'none';
return;
}

for (var i = 0; i < errors.length; i += 1) {
var item = document.createElement('li');
item.textContent = errors[i];
errorList.appendChild(item);
}
errorPanel.style.display = 'block';
}

function stopPolling() {
if (pollTimer) {
window.clearTimeout(pollTimer);
pollTimer = null;
}
submitButton.disabled = false;
}

function sendJsonRequest(url, options, onSuccess, onError) {
var xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

xhr.onreadystatechange = function () {
var data;
if (xhr.readyState !== 4) {
return;
}

try {
data = JSON.parse(xhr.responseText || '{}');
} catch (parseError) {
onError('The server returned an invalid JSON response.');
return;
}

if (xhr.status < 200 || xhr.status >= 300 || !data.ok) {
refreshNonce(data);
onError((data && data.message) || 'The request failed.');
return;
}

refreshNonce(data);
onSuccess(data);
};

xhr.onerror = function () {
onError('The request failed.');
};

xhr.send(options.body || null);
}

function pollProgress(token) {
sendJsonRequest(
pollUrlBase + '&token=' + encodeURIComponent(token),
{ method: 'GET' },
function (data) {
updateProgress(data);
if (data.phase === 'complete' || data.phase === 'error') {
stopPolling();
return;
}

pollTimer = window.setTimeout(function () {
pollProgress(token);
}, 1200);
},
function (error) {
stopPolling();
setMessage('alert-danger', error || 'Import progress request failed.');
}
);
}

form.addEventListener('submit', function (event) {
event.preventDefault();
clearMessage();
renderErrors([]);

if (!fileInput.files.length) {
setMessage('alert-danger', 'Select an .xlsx file before starting the import.');
return;
}

submitButton.disabled = true;
feedbackPanel.style.display = 'block';
statusText.textContent = 'Uploading workbook...';
progressBar.className = 'progress-bar progress-bar-striped active';
updateSummary({
totalRows: 0,
processedRows: 0,
updatedCount: 0,
insertedCount: 0,
invalidCount: 0,
failedCount: 0,
duplicateCount: 0,
percentComplete: 0
});
updateProgress({ percentComplete: 0, statusMessage: 'Uploading workbook...', errors: [] });

var formData = new FormData(form);
sendJsonRequest(
form.action,
{ method: 'POST', body: formData },
function (data) {
updateProgress(data);
pollProgress(data.token);
},
function (error) {
stopPolling();
setMessage('alert-danger', error || 'Import request failed.');
}
);
});
})();
</script>

+ 76
- 9
Dockerfile Parādīt failu

@@ -1,14 +1,81 @@
FROM node:20-bookworm
FROM node:lts

# Install GitHub Copilot CLI
RUN npm install -g @github/copilot
# Install PHP, Python, DB clients, and general utilities
RUN apt-get update && apt-get install -y --no-install-recommends \
php php-cli php-common php-mbstring php-xml php-curl php-zip \
php-pgsql php-mysql php-sqlite3 php-redis \
python3 python3-pip python3-venv \
wget apt-transport-https \
git make jq unzip zip \
openssh-client \
default-mysql-client postgresql-client sqlite3 \
&& rm -rf /var/lib/apt/lists/*

# Install useful dev tools
RUN apt-get update && apt-get install -y \
git \
curl \
# Install Docker CLI
RUN curl -fsSL https://download.docker.com/linux/debian/gpg \
| gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update && apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /workspace
# Install GitHub CLI
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& apt-get update && apt-get install -y --no-install-recommends gh \
&& rm -rf /var/lib/apt/lists/*

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

CMD ["bash"]
# Install Python tools
RUN pip install --break-system-packages poetry uv

# Install Spec Kit (specify CLI)
RUN uv tool install specify-cli --from git+https://github.com/github/spec-kit.git

# Install .NET 10
RUN wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb \
&& apt-get update && apt-get install -y --no-install-recommends dotnet-sdk-10.0 \
&& rm -rf /var/lib/apt/lists/*

# Install PowerShell
RUN apt-get update && apt-get install -y --no-install-recommends powershell \
&& rm -rf /var/lib/apt/lists/*

# Install .NET global tools
RUN dotnet tool install --global dotnet-ef \
&& dotnet tool install --global dotnet-aspnet-codegenerator
ENV PATH="$PATH:/root/.dotnet/tools:/root/.local/bin"

RUN git config --global user.name "Daniel Covington"
RUN git config --global user.email "danielcovington@comcast.net"


# Install Node.js global tools
RUN npm install -g @bonsai-ai/cli
RUN npm install -g @abacus-ai/cli
RUN npm install -g @openai/codex
RUN npm install -g @github/copilot

# Install ttyd (web terminal)
RUN ARCH=$(dpkg --print-architecture) && \
case "$ARCH" in \
amd64) TTYD_ARCH="x86_64" ;; \
arm64) TTYD_ARCH="aarch64" ;; \
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
esac && \
wget -O /usr/local/bin/ttyd \
"https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.${TTYD_ARCH}" && \
chmod +x /usr/local/bin/ttyd

# Install BMAD-METHOD CLI
RUN npm install -g bmad-method

WORKDIR /workspace
EXPOSE 7681
CMD ["ttyd", "-W", "-p", "7681", "bash"]

Binārs
ImportService/TrackingDataImport.vbs Parādīt failu


+ 2
- 2
MVC/lib.Upload.asp Parādīt failu

@@ -120,7 +120,7 @@ set fs = Server.CreateObject("Scripting.FileSystemObject")
streamFile.SaveToFile path & outLocalFileName, 2
streamFile.close
Set streamFile = Nothing
fileItem.Path = path & filename
fileItem.Path = path & outLocalFileName
end if
end sub

@@ -402,4 +402,4 @@ NewFullPath = strSaveToPath & "\" & strTempFileName & Counter & "." & FileExt
End If
Loop
End Function
%>
%>

+ 188
- 0
_bmad-output/implementation-artifacts/tech-spec-add-xlsx-jurisdiction-import.md Parādīt failu

@@ -0,0 +1,188 @@
---
title: 'Add XLSX Jurisdiction Import'
slug: 'add-xlsx-jurisdiction-import'
created: '2026-03-13'
status: 'implemented'
stepsCompleted: [1, 2, 3, 4]
tech_stack: ['Classic ASP', 'VBScript', 'IIS', 'ADO', 'Microsoft Jet/ACE OLEDB', 'FreeASPUpload', 'Session-backed MVC helpers']
files_to_modify: ['/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp', '/workspace/App/Views/Jurisdiction/import.asp', '/workspace/App/DomainModels/JurisdictionRepository.asp']
code_patterns: ['Controller action with inline file-processing logic', 'Repository-based persistence with raw SQL and Automapper models', 'Session-backed Flash/FormCache/CSRF helpers', 'Server-rendered forms with HTML helper methods']
test_patterns: ['ASPUnit helper tests only', 'No existing controller/import coverage', 'Manual IIS verification required for upload/import flows']
---

# Tech-Spec: Add XLSX Jurisdiction Import

**Created:** 2026-03-13

## Overview

### Problem Statement

The current jurisdiction import only supports `.csv` and `.txt`, relies on the Jet text driver, and does not persist the imported jurisdiction updates. The business now needs an `.xlsx`-only import flow for a known spreadsheet format that updates the `Jurisdiction` table and gives the user progress feedback while the import runs.

### Solution

Replace the current jurisdiction import implementation with an XLSX-based import pipeline that reads a fixed workbook format, skips row 1, uses row 2 as headers, extracts `JCode` and normalized `Name` from the `Jurisdiction` column, joins `City & Township` and `ZIP + 4` into `CSZ`, regenerates `IMB_Digits` and `IMB` using existing logic, updates existing `Jurisdiction` rows by extracted `JCode`, inserts missing `JCode` rows, and exposes AJAX progress updates in the import UI.

### Scope

**In Scope:**
- Replace the current import UI and backend flow with `.xlsx`-only support
- Support the known workbook shape represented by `Data\BRM Permit Info February '26 updated.xlsx`
- Ignore row 1 and treat row 2 as the header row
- Extract `JCode` from text inside parentheses in the `Jurisdiction` column
- Normalize `Name` from the text before parentheses
- If the normalized name ends with `CITY`, transform it to `CITY OF <name without trailing CITY>`
- Build `CSZ` from `City & Township` plus `ZIP + 4`
- Update `Mailing_Address`, `CSZ`, `IMB_Digits`, and `IMB`
- Regenerate `IMB` through the existing `GetIMBCodec.EncodeDigits(...)` path
- Add AJAX-driven progress feedback to the import screen

**Out of Scope:**
- Supporting `.csv` or `.txt` imports on the jurisdiction screen
- Supporting arbitrary workbook layouts or multiple worksheet formats
- Broad refactors to unrelated jurisdiction CRUD features

## Context for Development

### Codebase Patterns

- The current jurisdiction import lives in `JurisdictionController.ImportPost` and uses `FreeASPUpload` plus server-side file processing.
- The current CSV/TXT path already contains business logic for `JCode`, `Name`, `CSZ`, `IMB_Digits`, and `IMB` derivation.
- The repo is a Classic ASP / VBScript application running on IIS with existing shared helpers in `MVC/`.
- Environment-sensitive behavior should stay minimal and localized because this codebase is tightly coupled to Windows/IIS/runtime dependencies.
- Controller methods are allowed to contain substantial workflow logic in this codebase; there is no separate service layer pattern to follow.
- Persistence updates should still flow through `JurisdictionRepository` instead of ad hoc SQL inside views.
- Session-backed helpers already exist for flash state and CSRF tokens, which makes Session a viable anchor for import progress state if the implementation stays inside the web app.
- `MVC/lib.json.asp` exists, so lightweight JSON responses for AJAX polling fit the current stack without introducing a new dependency.

### Files to Reference

| File | Purpose |
| ---- | ------- |
| `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp` | Current import entry point and existing CSV/TXT derivation logic |
| `/workspace/App/Views/Jurisdiction/import.asp` | Current upload UI to be changed for XLSX and progress feedback |
| `/workspace/App/DomainModels/JurisdictionRepository.asp` | Jurisdiction lookup and update persistence path |
| `/workspace/MVC/lib.Upload.asp` | Existing upload helper used by controller imports |
| `/workspace/MVC/lib.json.asp` | Existing JSON helper if the progress endpoint returns polling status |
| `/workspace/Data/BRM Permit Info February '26 updated.xlsx` | Sample workbook shape to anchor header and column mapping |
| `/workspace/_bmad-output/project-context.md` | Brownfield implementation rules and constraints |

### Technical Decisions

- The import screen will become XLSX-only.
- Matching is by extracted `JCode` from the `Jurisdiction` column.
- The implementation must preserve and reuse the existing IMB encoding path rather than inventing a new barcode algorithm.
- Progress feedback is required and should be exposed through AJAX rather than a synchronous full-page post only.
- The current Jet text-driver import path cannot read `.xlsx`; the implementation needs a Windows/IIS-compatible Excel-reading strategy.
- Primary workbook-read strategy should use `Microsoft.ACE.OLEDB.12.0` on Windows/IIS. If ACE is unavailable, the import should fail with a clear user-facing error.
- The controller will likely need to split import work into at least two responsibilities: kickoff/upload and progress/status reporting.
- The repository currently supports `FindByJCode` and `Update`, which is enough for update-by-`JCode` if the controller loads and mutates each row model before persisting.
- There is no existing automated controller/import test pattern, so verification will rely on manual IIS-based import testing plus any helper-level tests that can be isolated.
- The import page should use a kickoff action plus an AJAX polling endpoint. Progress state can be stored in `Session` under a generated import token.
- Because this runs under Classic ASP/IIS, true live per-row progress may be constrained by request/session locking behavior. The implementation should use a two-phase design if needed and may expose checkpoint-based progress if that is the most reliable bounded solution.
- Required workbook headers should be validated before processing begins: `County`, `Jurisdiction`, `Mailing Address`, `City & Township`, `ZIP + 4`, and `Mailer ID Option 1`.
- Rows with extracted `JCode` values not found in `Jurisdiction` should be inserted and counted separately in the final summary.
- If duplicate `JCode` rows appear in the workbook, process them in file order and let the last valid row win while counting duplicates in the final summary.
- Name normalization should trim whitespace and apply the `CITY` rewrite only when the extracted name ends with the standalone word `CITY`.
- Row-level failures should be isolated, counted, and reported in the completion summary rather than aborting the entire import after partial progress.
- Progress state should remain session-scoped rather than being persisted to the database or temp files.
- Persistence should prefer the existing `FindByJCode` + mutate + `JurisdictionRepository.Update` flow before introducing a new repository bulk-update abstraction.
- Header binding should validate the expected row-2 header names and then resolve columns by header names rather than raw column positions only.
- The final import summary should include at minimum: total rows seen, rows updated, rows inserted, duplicate rows encountered, invalid rows skipped, and row-level failures.
- The final result should also expose row-level operator feedback with row number, failure reason, and full imported record details for malformed `Jurisdiction` values, `IMB_Digits` build failures, ACE read failures, and other row-level processing errors.

## Implementation Plan

### Tasks

- [x] Task 1: Replace the current jurisdiction import contract with XLSX-only validation
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- Action: Update `ImportPost` so it only accepts `.xlsx` uploads, removes the CSV/TXT-specific text-driver path, and returns a clear validation error for any non-XLSX file.
- Notes: Keep the existing `FreeASPUpload` flow for multipart upload handling unless the ACE-based workbook read requires a different saved-file path convention.

- [x] Task 2: Add workbook-reading and header-validation logic for the known spreadsheet format
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- Action: Read the uploaded `.xlsx` workbook through `Microsoft.ACE.OLEDB.12.0`, target the first worksheet, ignore row 1, treat row 2 as headers, and validate the required headers before row processing begins.
- Notes: Fail fast with a user-facing error if ACE is unavailable, the workbook cannot be opened, or required headers are missing.

- [x] Task 3: Implement row-mapping and normalization rules for jurisdiction updates
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- Action: For each workbook row, extract `JCode` from the `Jurisdiction` value inside parentheses, derive normalized `Name` from the text before parentheses, apply the `CITY` to `CITY OF ...` rewrite when appropriate, join `City & Township` and `ZIP + 4` into `CSZ`, compute `IMB_Digits`, and regenerate `IMB` via `GetIMBCodec.EncodeDigits(...)`.
- Notes: Trim whitespace on all derived values and treat malformed or unparseable rows as row-level failures instead of fatal process errors.

- [x] Task 4: Persist updates by existing `JCode` and track import result counters
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- File: `/workspace/App/DomainModels/JurisdictionRepository.asp`
- Action: For each valid parsed row, load the existing jurisdiction by `JCode`, mutate the model fields, and call `JurisdictionRepository.Update`; insert missing `JCode` rows with the parsed values; detect duplicates in workbook order and allow the last valid row to win.
- Notes: Only add repository support if a small helper materially reduces controller duplication; otherwise stay with the existing `FindByJCode` + `Update` pattern.

- [x] Task 5: Introduce bounded progress-state tracking and a polling endpoint
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- Action: Split the import flow into a kickoff path and a progress/status path, storing progress state in `Session` under an import token and returning JSON-compatible status for AJAX polling.
- Notes: If Classic ASP session locking prevents true live row-by-row progress, expose checkpoint-based phases such as `uploaded`, `opening workbook`, `validating headers`, `processing rows`, and `complete`.

- [x] Task 6: Replace the import page UI with XLSX-only upload and progress display
- File: `/workspace/App/Views/Jurisdiction/import.asp`
- Action: Update the form copy and file input to XLSX-only, add client-side AJAX kickoff/polling behavior, render a progress bar/status area, and display final summary counts plus row-level error details.
- Notes: The UX should remain usable if progress is phase-based rather than exact per-row percentage.

- [x] Task 7: Define the final operator summary and failure reporting
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- File: `/workspace/App/Views/Jurisdiction/import.asp`
- Action: Return or render a completion result that includes total rows seen, rows updated, rows inserted, duplicate rows encountered, invalid rows skipped, row-level failures, and row-specific error details.
- Notes: Include row number, failure reason, and full record details for malformed `Jurisdiction` values, IMB digit failures, insert/update failures, and workbook-read errors where applicable.

- [x] Task 8: Update user-facing messaging and implementation notes for runtime dependency risk
- File: `/workspace/App/Views/Jurisdiction/import.asp`
- File: `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- Action: Make sure operator-facing errors clearly describe ACE dependency issues and workbook validation failures.
- Notes: This keeps deployment/runtime failures diagnosable without digging through server logs first.

### Acceptance Criteria

- [x] AC 1: Given the user opens the jurisdiction import page, when the screen renders, then it only advertises and accepts `.xlsx` uploads.
- [x] AC 2: Given a non-`.xlsx` file is submitted, when the import starts, then the user receives a validation error and no jurisdiction rows are changed.
- [x] AC 3: Given the uploaded workbook cannot be opened through ACE/OLEDB, when the import starts, then the user receives a clear workbook/ACE error and no jurisdiction rows are changed.
- [x] AC 4: Given the workbook is missing one of the required row-2 headers, when validation runs, then the import stops before row processing and reports the missing header(s).
- [x] AC 5: Given a row contains `Jurisdiction` text in the format `ALCONA TOWNSHIP (01040)`, when the row is processed, then `JCode` is parsed as `01040` and `Name` is parsed as `ALCONA TOWNSHIP`.
- [x] AC 6: Given a parsed jurisdiction name ends with the standalone word `CITY`, when normalization runs, then the stored `Name` becomes `CITY OF <name without trailing CITY>`.
- [x] AC 7: Given a workbook row has `City & Township` and `ZIP + 4` values, when the row is processed, then the database `CSZ` field is updated with those values joined in the same import flow.
- [x] AC 8: Given a valid workbook row matches an existing jurisdiction by extracted `JCode`, when the row is processed, then `Name`, `Mailing_Address`, `CSZ`, `IMB_Digits`, and `IMB` are updated in the `Jurisdiction` table.
- [x] AC 9: Given a valid workbook row is processed, when `IMB_Digits` is derived, then `IMB` is regenerated using the existing `GetIMBCodec.EncodeDigits(...)` functionality.
- [x] AC 10: Given a workbook row has an extracted `JCode` that does not exist in the database, when the row is processed, then a new `Jurisdiction` row is inserted with the parsed/imported values.
- [x] AC 11: Given the same `JCode` appears more than once in the workbook, when processing completes, then the last valid occurrence wins and duplicates are counted in the final summary.
- [x] AC 12: Given a row has malformed `Jurisdiction` text that cannot produce a valid `JCode`, when the row is processed, then that row is counted as failed or invalid, the import continues with subsequent rows, and the final output includes the full imported record details.
- [x] AC 13: Given the user starts an import, when the operation is in progress, then the page shows AJAX-driven progress feedback for the current import token.
- [x] AC 14: Given true live row-by-row progress is not technically reliable under Classic ASP/IIS, when the import runs, then the user still sees checkpoint-based progress states instead of a frozen screen.
- [x] AC 15: Given the import completes, when final results are shown, then the user sees total rows seen, rows updated, rows inserted, duplicates encountered, invalid rows skipped, and failures.
- [x] AC 16: Given any malformed `Jurisdiction` or `IMB_Digits` build failures occur, when results are shown, then the user can see row numbers, failure reasons, and the full source record for those rows.

## Additional Context

### Dependencies

- Windows/IIS runtime with Classic ASP enabled
- `FreeASPUpload` remains the upload mechanism unless implementation constraints force a localized alternative
- `Microsoft.ACE.OLEDB.12.0` must be installed and available on the target server for workbook access
- Existing `GetIMBCodec.EncodeDigits(...)` functionality must remain available during import processing
- Existing MVC JSON helper in `/workspace/MVC/lib.json.asp` can support polling responses

### Testing Strategy

- Manual IIS test: upload a valid copy of [BRM Permit Info February '26 updated.xlsx](/workspace/Data/BRM Permit%20Info%20February%20'26%20updated.xlsx) and verify updated jurisdiction rows in the database
- Manual IIS test: upload a non-XLSX file and verify validation failure
- Manual IIS test: upload a workbook with one required header removed and verify fast-fail header validation
- Manual IIS test: upload a workbook containing malformed `Jurisdiction` values and verify row-level error reporting with continued processing and full source-record details
- Manual IIS test: upload a workbook containing missing `JCode` rows and verify the rows are inserted into `Jurisdiction`
- Manual IIS test: upload a workbook containing invalid `Mailer ID Option 1` or `ZIP + 4` values and verify the full source record is shown in the error output
- Manual IIS test: upload a workbook containing duplicate `JCode` rows and verify the last valid row wins
- Manual IIS test: verify progress bar/status polling updates during import and resolves to a final summary state
- Optional helper-level verification: isolate and test any extracted parsing/normalization helper routines if they are moved out of the controller into reusable VBScript functions

### Notes

- Highest implementation risk is the interaction between Classic ASP request lifecycle, Session locking, and AJAX polling; the implementation should prefer a reliable bounded progress model over an unreliable “live” illusion.
- ACE/OLEDB availability is a deployment/runtime dependency, not just a coding detail; missing-provider handling must be first-class.
- Keep the change bounded to the existing jurisdiction import workflow and avoid turning this into a generalized spreadsheet-import framework.
- If the controller becomes too large, extraction into small local helper functions is acceptable, but avoid broad architectural refactors.

+ 207
- 0
_bmad-output/planning-artifacts/sprint-change-proposal-2026-03-13.md Parādīt failu

@@ -0,0 +1,207 @@
# Sprint Change Proposal

**Date:** 2026-03-13
**Feature:** XLSX Jurisdiction Import
**Change Type:** Correct Course during implementation
**Scope Classification:** Minor

## 1. Issue Summary

The XLSX jurisdiction import feature was implemented from the original quick spec, but the business rules changed during implementation review.

New required behavior:

- malformed `Jurisdiction` rows must show the full imported record, not just row number and reason
- unmatched `JCode` rows must now be inserted into `Jurisdiction` instead of being skipped
- `IMB_Digits` build failures must also show the full imported record

Context:

- the current quick spec explicitly says unmatched `JCode` rows should be skipped
- the current implementation summary/error reporting is too terse for operators to identify the bad row without manually reopening the workbook

Evidence:

- current spec says unmatched `JCode` rows are skipped
- current implementation logs row-level errors as short messages only
- stakeholder clarified the corrected behavior after implementation had already started

## 2. Impact Analysis

### Epic Impact

No epics or stories artifacts exist for this quick-flow change, so there is no epic backlog to rewrite.

### Story Impact

No formal story artifacts exist. The impact is limited to the current quick-spec-driven implementation.

### Artifact Conflicts

Affected artifacts:

- `/workspace/_bmad-output/implementation-artifacts/tech-spec-add-xlsx-jurisdiction-import.md`
- `/workspace/App/Controllers/Jurisdiction/JurisdictionController.asp`
- `/workspace/App/Views/Jurisdiction/import.asp`
- `/workspace/App/DomainModels/JurisdictionRepository.asp` if insert support needs refinement

Conflicts discovered:

- current spec says unmatched `JCode` rows should be skipped, but new requirement says they must be inserted
- current spec requires row number and failure reason, but new requirement also needs full row content displayed for specific failures

### Technical Impact

- controller logic must change from update-or-skip to update-or-insert
- import summary counters must distinguish `updated` and `inserted`
- row error formatting must include the full workbook row payload for:
- malformed `Jurisdiction`
- IMB digit build failure
- UI summary labels must align with the new counters
- acceptance criteria and task tracking in the quick spec must be corrected

## 3. Recommended Approach

### Chosen Path

**Direct Adjustment**

### Rationale

This is a bounded feature correction, not a broad replanning event. The existing implementation is already concentrated in one controller and one view, so the safest path is to update the quick spec and then amend the implementation directly.

### Effort / Risk

- **Effort:** Low to Medium
- **Risk:** Medium

Primary risk areas:

- insert path must not create malformed or partial `Jurisdiction` records
- operator-facing error rendering must remain readable when full row payloads are shown
- summary counters and wording must stay consistent with the new behavior

### Timeline Impact

Small extension to the current implementation pass. No broader program impact identified.

## 4. Detailed Change Proposals

### A. Quick Spec Updates

#### A1. Unmatched `JCode` behavior

**OLD**

- Rows with extracted `JCode` values not found in `Jurisdiction` should be skipped and counted in the final summary instead of inserted.
- Acceptance criteria describe unmatched rows as skipped with no insert.

**NEW**

- Rows with extracted `JCode` values not found in `Jurisdiction` should be inserted into `Jurisdiction`.
- Final summary should track inserted rows separately from updated rows.

**Rationale**

This aligns the artifact with the corrected business rule.

#### A2. Full-record row errors

**OLD**

- Final result exposes row number and failure reason.

**NEW**

- Final result exposes row number, failure reason, and the full imported record for failure cases where operator review is needed.

**Rationale**

Operators need enough detail to identify and repair bad workbook rows quickly.

#### A3. IMB digit failure reporting

**OLD**

- `IMB_Digits` failures are treated as row-level failures with summary text.

**NEW**

- `IMB_Digits` failures must include the full row content in the error output.

**Rationale**

The source data causing the failure must be visible without cross-referencing the spreadsheet manually.

### B. Implementation Updates

#### B1. Insert-on-missing `JCode`

**OLD**

- `FindByJCode` failure increments unmatched count and records an error.

**NEW**

- `FindByJCode` failure creates a new jurisdiction model and inserts it using the imported values.
- Import summary tracks `insertedCount`.

**Rationale**

This is the main behavioral correction.

#### B2. Full-row error formatting

**OLD**

- Example: `Row 14: Jurisdiction field is missing a code in parentheses.`

**NEW**

- Example: `Row 14: Jurisdiction field is missing a code in parentheses. Record: County=..., Jurisdiction=..., Mailing Address=..., City & Township=..., ZIP + 4=..., Mailer ID Option 1=...`

**Rationale**

This directly satisfies the new operational requirement.

#### B3. UI counter updates

**OLD**

- `updated`, `unmatched`, `invalid`, `failed`, `duplicates`

**NEW**

- `updated`, `inserted`, `invalid`, `failed`, `duplicates`

**Rationale**

The UI should reflect what the import actually does.

## 5. Implementation Handoff

### Scope

**Minor**

### Handoff Recipients

- Development implementation pass
- Optional technical writer/doc refresh after code is aligned

### Responsibilities

- Update quick spec to reflect corrected behavior
- Amend controller logic to insert on missing `JCode`
- Add full-record context to specified error cases
- Update import UI summary wording/counters
- Verify with manual IIS test using the sample workbook and crafted failure rows

### Success Criteria

- malformed `Jurisdiction` rows display full record details
- missing `JCode` rows insert new jurisdictions successfully
- IMB digit failures display full record details
- import summary distinguishes updated vs inserted
- quick spec reflects final corrected behavior and task state


+ 90
- 0
_bmad-output/project-context.md Parādīt failu

@@ -0,0 +1,90 @@
---
project_name: 'workspace'
user_name: 'Daniel Covington'
date: '2026-03-13'
sections_completed: ['technology_stack', 'language_rules', 'framework_rules', 'testing_rules', 'quality_rules', 'workflow_rules', 'anti_patterns']
status: 'complete'
rule_count: 23
optimized_for_llm: true
---

# Project Context for AI Agents

_This file contains critical rules and patterns that AI agents must follow when implementing code in this project. Focus on unobvious details that agents might otherwise miss._

---

## Technology Stack & Versions

- Runtime: IIS + Classic ASP on Windows
- Language: VBScript / `.asp`
- Framework: custom MVC helpers under `MVC/`
- Data access: ADO via `App/DAL/lib.DAL.asp`
- Database: Access MDB via Jet/ACE providers
- Reporting/export: ReportMan, Debenu PDF Library, Chilkat COM components
- Testing: ASPUnit served through IIS
- Automation: VBScript and PowerShell scripts outside the web app

## Critical Implementation Rules

### Language-Specific Rules

- Start application `.asp` and `.vbs` files with `Option Explicit`.
- Keep `<!--#include file="..."-->` directives near the top; include order matters because the app relies on side-effect loading.
- Follow the existing singleton-style factory pattern for repositories instead of introducing new instantiation conventions mid-module.
- Match the nearby VBScript style exactly; there is no formatter protecting consistency.

### Framework-Specific Rules

- Web entry starts at `index.asp`, but the real bootstrap is `App/include_all.asp`; changes that depend on shared libraries should be wired there only if they are globally required.
- Routes are initialized by `Routes.Initialize "/App/"`; avoid changing route roots casually because controller links assume that base.
- Controllers typically orchestrate directly and include views inline; do not introduce partial modern abstractions unless they clearly reduce duplication.
- Use existing MVC helpers such as `Automapper`, `Flash`, `FormCache`, and `HTMLSecurity` before inventing parallel helpers.

### Testing Rules

- Existing automated coverage is strongest for helper libraries, not end-to-end operational workflows.
- Any change in controller export/proof logic requires manual verification on Windows/IIS even if tests pass.
- Run `Tests/Test_All.asp` through IIS for regression checks when changing shared MVC helpers or utility behavior.

### Code Quality & Style Rules

- Preserve PascalCase class and file naming used across controllers, repositories, and view models.
- Keep repository SQL in the repository layer; avoid scattering new raw SQL into views.
- When changing a screen, trace controller, repository, view model, and view together because behavior is distributed across those layers.
- Prefer minimal, local edits over broad refactors; this codebase is highly environment-coupled.

### Development Workflow Rules

- Check `App/app.config.asp` before assuming path behavior; `dev` mode changes export locations and runtime assumptions.
- Search both `App/` and `ImportService/` when changing statuses, file names, export behavior, or schema-related fields.
- For schema changes, add a numbered migration under `Data/Migrations/` and then update the affected repository/model code.
- Treat report templates in `Data/*.rep` as part of the runtime contract, not incidental assets.

### Critical Don't-Miss Rules

- Do not commit new secrets, unlock keys, credentials, or machine-specific paths. Existing hard-coded values are legacy constraints, not a pattern to extend.
- Do not assume Linux validation is enough. COM, IIS, Jet/ACE, report generation, and network shares are Windows-only concerns here.
- Do not rename or normalize status strings without global search; automation scripts appear to branch on exact textual statuses.
- Do not change export filenames or folder structures without auditing downstream consumers such as SnailWorks and import-service scripts.
- Do not move shared includes or bootstrap files casually; many files rely on implicit availability of globals and helpers.
- Be careful with view file names: there are similarly named templates such as `CreateKit.asp` and `CreateTrackingKit.asp`, and controllers include them directly.

---

## Usage Guidelines

**For AI Agents:**

- Read this file before implementing any code.
- Prefer the existing Classic ASP patterns over introducing modern architecture unless explicitly requested.
- When unsure, choose the option that minimizes environment risk and preserves current operational behavior.
- Update this file if you discover a repeated brownfield rule that would prevent future mistakes.

**For Humans:**

- Keep this file lean and focused on non-obvious implementation constraints.
- Update it when environment behavior, schema patterns, or core workflow conventions change.
- Re-check it before delegating larger brownfield changes to an AI agent.

Last Updated: 2026-03-13

+ 0
- 3
codex.cmd Parādīt failu

@@ -1,3 +0,0 @@
mkdir .\volumes\codex_home
docker compose up -d --build
docker compose exec codex bash

+ 5
- 6
docker-compose.yml Parādīt failu

@@ -1,10 +1,9 @@
services:
devcontainer:
bonsai:
build: .
container_name: tracking-dev
container_name: ai-bmad-tracking-kits
volumes:
- .:/workspace
working_dir: /workspace
stdin_open: true
tty: true

- /var/run/docker.sock:/var/run/docker.sock
ports:
- "7681:7681"

+ 107
- 0
docs/api-contracts.md Parādīt failu

@@ -0,0 +1,107 @@
# tracking_kits - Route and Integration Contracts

**Date:** 2026-03-13

## Important Note

This project does not expose a conventional JSON REST API. Its primary contract surface is a set of Classic ASP controller routes that render HTML pages and process form submissions. In addition, it has file-based export/import interfaces used by batch automation.

## Web Route Surface

Routing is initialized by [../App/app.config.asp](../App/app.config.asp), and requests are dispatched from controller ASP files under `App/Controllers`.

### HomeController

- `Index`
- Purpose: render the switchboard/home screen
- `CreateKit`
- Purpose: list jurisdictions with pagination for kit creation
- `Search`
- Purpose: search jurisdictions by `JCode`, `Name`, address fields, and IMB values
- `Print`
- Purpose: render a sample/report PDF using `Label_Report.rep`

### KitController

- `Index`
- Purpose: paged list of kits
- `Search`
- Purpose: search kits by `ID`, `JobNumber`, and `JCode`
- `SwitchBoardIndex`
- Purpose: operational list for label-based kits
- `SwitchBoardEdit`
- Purpose: edit/view a single kit switchboard entry
- `SwitchBoardPurpleEnvelopsIndex`
- Purpose: list purple-envelope kits
- `SwitchBoardPurpleEnvelopeEdit`
- Purpose: edit purple-envelope kit details, color options, and precinct color data
- `SwitchBoardPurpleEnvelopeEditPost`
- Purpose: update purple-envelope form fields and status
- `AssignKitColorPost`
- Purpose: assign a color to all labels/records in a kit
- `AssignPrecinctColorsPost`
- Purpose: assign colors at the precinct level
- `ExportTrackingLabels(id)`
- Purpose: generate and move a kit label PDF
- `ExportSnailWorksTracking(id)`
- Purpose: generate SnailWorks CSV export data

### Other Controllers

The repository structure implies standard CRUD routes for:

- `JurisdictionController`
- `ContactsController`
- `InkjetRecordsController`
- `SettingsController`
- `CustomOfficeCopyJobController`
- `KitLabelsController`

These are primarily view-rendering and form-post endpoints.

## Form / POST Contracts

Observed POST patterns:

- Classic HTML forms
- anti-CSRF tokens managed by `HTMLSecurity.SetAntiCSRFToken`
- redirect-on-success flow with flash messaging
- model hydration via `Automapper.AutoMap(Request.Form, model)`

## File-Based Integration Contracts

### Tracking Label PDF Export

- Triggered from kit workflows
- Uses `Data/Label_Report.rep`
- Writes PDF output to app-local `Data/` temporarily
- Moves final artifact into `ExportDirectory\<Jurisdiction>\...`

### SnailWorks Export

- Generates CSV files per kit
- Includes a header section plus detail records
- Output path depends on configured `ExportDirectory`

### Inkjet Export / Proof / Import Processing

- `ImportService/TrackingDataImport.vbs` performs status-driven processing
- Outputs CSV and PDF artifacts
- Reads and writes from configured directories and network shares
- Uses COM-based CSV/report/SFTP functionality

## Security and Validation Notes

- CSRF checks are present for important form posts.
- Input validation exists unevenly; some validation calls are commented out in controllers.
- There is no API schema enforcement layer; the contract is defined by forms, SQL expectations, and downstream file formats.

## Change Guidance

- Treat controller action names, form field names, status strings, and export file names as part of the operational contract.
- Before changing an export format, inspect both web controllers and import-service scripts.
- Before changing a status value, search for every status-dependent branch in both `App/` and `ImportService/`.

---

_Generated for brownfield analysis._

+ 131
- 0
docs/architecture.md Parādīt failu

@@ -0,0 +1,131 @@
# tracking_kits - Architecture

**Date:** 2026-03-13
**Application Type:** Single-part Classic ASP web application

## Executive Summary

`tracking_kits` is a server-rendered IIS application that manages operational workflows around election mail pieces. It combines browser-driven CRUD screens with file-system side effects and batch automation. The architecture is straightforward but environment-sensitive: controllers render views and call repositories, while proofs, inkjet exports, and tracking exports rely on COM libraries, report templates, and Windows file shares.

## Runtime Flow

1. IIS serves [../index.asp](../index.asp).
2. `index.asp` redirects to [../App/Controllers/Home/HomeController.asp](../App/Controllers/Home/HomeController.asp).
3. The controller includes [../App/include_all.asp](../App/include_all.asp), which loads the MVC framework, DAL, app config, and all repositories.
4. `Routes.Initialize "/App/"` from [../App/app.config.asp](../App/app.config.asp) establishes route resolution for controller files under `App/`.
5. Controller actions populate view-model objects and include `.asp` view templates.
6. Repositories issue SQL through `DAL.Query`, `DAL.Execute`, and `DAL.PagedQuery`.
7. Some controller actions and automation scripts create files, call report engines, and move outputs into export directories.

## Major Architectural Pieces

### Presentation Layer

- Controller classes live under `App/Controllers/*`.
- Views live under `App/Views/*`.
- View-model types live under `App/ViewModels/*`.
- Shared layout fragments live in `App/Views/Shared/`.

This layer is synchronous and page-oriented. There is no client-side SPA architecture in the codebase.

### Application/Workflow Layer

Controller actions contain most orchestration logic. Key examples:

- `HomeController.CreateKit` and `HomeController.Search` drive jurisdiction lookup for kit creation.
- `KitController.SwitchBoardIndex` and `SwitchBoardPurpleEnvelopsIndex` drive operational dashboards.
- `KitController.ExportTrackingLabels` renders a report to PDF and moves it into an export folder.
- `KitController.ExportSnailWorksTracking` writes CSV exports from repository-provided data.
- Purple-envelope color assignment logic is handled directly in controller POST actions.

### Persistence Layer

- ADO access is centralized in `App/DAL/lib.DAL.asp`.
- Repositories under `App/DomainModels/` contain raw SQL and map records into model classes.
- Pagination and search are implemented inside repositories rather than a service layer.
- The database appears to be an Access MDB with migrations tracked in `Data/Migrations`.

### Shared Framework Layer

The `MVC/` directory provides reusable infrastructure:

- `lib.MVC.asp` and `lib.Routes.asp` for dispatch/routing
- `lib.Automapper.asp` for record/form mapping
- `lib.HTMLSecurity.asp` for anti-CSRF helpers
- `lib.Flash.asp`, `lib.FormCache.asp`, `lib.Helpers.asp`, `lib.Collections.asp`, and related helpers

This shared code is effectively the application's internal framework.

### Batch / Integration Layer

- `ImportService/TrackingDataImport.vbs` handles import/export/proof workflows outside HTTP requests.
- PowerShell and VBScript utilities exist for address updates, service installation, and batch processing.
- COM libraries provide CSV, SFTP, report, and PDF functionality.

## Data Architecture

The schema is migration-driven. Core tables inferred from migrations and repositories:

- `Jurisdiction`
- `Contact`
- `Settings`
- `Kit`
- `Kit_Labels` / `KitLabels`
- `InkJetRecords`
- `CustomOfficeCopyJob`
- `Colors`

Relationships are implied through foreign-key migrations and repository joins, especially around kits, labels, inkjet records, contacts, and jurisdictions.

## Route / Action Design

This app exposes controller-action routes rather than a versioned API. The practical contract surface is:

- User-facing pages under `Home`, `Kit`, `Jurisdiction`, `Contacts`, `Settings`, `InkjetRecords`, and `CustomOfficeCopyJob`
- Form POST actions guarded by anti-CSRF tokens
- Operational actions that change status and produce files

There is no evidence of JSON APIs being a primary integration mechanism.

## External Dependencies and Constraints

### Windows / IIS

- Runtime assumes IIS + Classic ASP.
- Paths in config and scripts reference drive letters and UNC shares.
- End-to-end behavior depends on Windows COM registration.

### COM / ActiveX

- ReportMan for report rendering
- Debenu PDF Library for PDF operations
- Chilkat libraries for unlockable COM features and SFTP/CSV support

### File-System Coupling

- Export folders are created on demand.
- Existing files may be deleted and replaced.
- Generated artifacts move between local app paths and shared export directories.

## Security and Operational Risks

- Environment-specific values and secrets are embedded in code/config scripts today.
- File-system writes and network share access are central to the workflow and are hard to test in isolation.
- Business logic is distributed across controllers and external scripts, so changes often require cross-file tracing.

## Testing Strategy

- Helper/framework tests exist through ASPUnit.
- There is little evidence of automated coverage for end-to-end kit/proof/export workflows.
- High-confidence changes in controller/export logic will require manual IIS-based verification on Windows.

## Change Guidance

- For UI flow changes, inspect the controller, view model, view, and repository together.
- For export/proof changes, inspect both web controllers and `ImportService/TrackingDataImport.vbs`.
- For schema changes, add a migration under `Data/Migrations` and update the affected repository/model classes.
- For environment changes, audit `App/app.config.asp`, import scripts, and any hard-coded paths or COM usage.

---

_Generated for brownfield analysis._

+ 138
- 0
docs/component-inventory.md Parādīt failu

@@ -0,0 +1,138 @@
# tracking_kits - Component Inventory

**Date:** 2026-03-13

## Controllers

### Home

- [../App/Controllers/Home/HomeController.asp](../App/Controllers/Home/HomeController.asp)
- Responsibilities:
- Render the switchboard
- Search jurisdictions
- Start the create-kit flow
- Demo PDF/report generation

### Kit

- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- Responsibilities:
- Kit switchboard pages
- Purple-envelope operational screens
- Kit edit/update flows
- Label PDF export
- SnailWorks export generation
- Color assignment and related POST actions

### Other Domain Controllers

- `JurisdictionController`
- `InkjetRecordsController`
- `ContactsController`
- `SettingsController`
- `CustomOfficeCopyJobController`
- `KitLabelsController`

These appear to follow the same CRUD-oriented MVC pattern used elsewhere in the app.

## Repository Components

### Core Repositories

- `JurisdictionRepository`
- `KitRepository`
- `KitLabelsRepository`
- `InkjetRecordsRepository`
- `SettingsRepository`
- `ContactsRepository`
- `SnailWorksRepository`
- `CustomOfficeCopyJobRepository`
- `ColorsRepository`

### Shared Characteristics

- Model classes and repository classes live in the same file.
- SQL is embedded directly in repository methods.
- Pagination, search, and mapping are implemented per repository.
- Repositories are exposed through singleton-style factory functions.

## View Models

View models under `App/ViewModels/` shape data for server-side rendering. Notable classes are used for:

- paged index screens
- switchboard screens
- per-domain CRUD forms
- purple envelope and inkjet workflows

## UI Surfaces

### Shared Layout

- [../App/Views/Shared/layout.header.asp](../App/Views/Shared/layout.header.asp)
- [../App/Views/Shared/layout.footer.asp](../App/Views/Shared/layout.footer.asp)

### Home Screens

- switchboard index
- create kit
- create tracking kit

### Kit Screens

- kit CRUD pages
- switchboard list/edit pages
- purple envelope switchboard list/edit pages

### Other Screens

- jurisdiction CRUD/import pages
- contacts CRUD pages
- inkjet record CRUD pages
- settings CRUD pages
- custom office copy job pages

## Shared Framework Components

The `MVC/` directory provides reusable infrastructure rather than business-domain components:

- routing and dispatch
- automapper
- HTML helpers
- anti-CSRF/security helpers
- flash messages and form cache
- collections and enumerable helpers
- upload, strings, validations, JSON support

## Automation / Batch Components

### Import Service

- [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs)
- Handles polling/status-driven operations outside the web request lifecycle.

### Supporting Scripts

- `InstallService.vbs`
- `Data/Update_Addresses.ps1`
- `App/ScaffoldRepo.vbs`

## Reporting Assets

- `Data/Label_Report.rep`
- `Data/Proofs.rep`
- `Data/Office-Copy-Proof.rep`
- `Data/Custom Office Copies Proof.rep`

These files are operational assets, not just documentation or samples. Controller and script behavior depends on them.

## Reuse Guidance

- New CRUD-style features should follow the existing controller/view/repository/view-model grouping pattern.
- Reuse shared layout includes and MVC helpers instead of inventing alternate rendering patterns.
- Reuse repository paging/search conventions if extending list screens.
- Reuse migration numbering conventions for schema changes.

---

_Generated for brownfield analysis._

+ 132
- 0
docs/data-models.md Parādīt failu

@@ -0,0 +1,132 @@
# tracking_kits - Data Models

**Date:** 2026-03-13

## Database Overview

The project uses an Access database accessed through ADO. The authoritative schema history lives in [../Data/Migrations](../Data/Migrations). Repository SQL and migration names indicate a relational model centered on jurisdictions, kits, labels, contacts, settings, inkjet records, and custom office copy jobs.

## Core Tables

### Jurisdiction

Primary fields observed in `JurisdictionRepository`:

- `JCode`
- `Name`
- `Mailing_Address`
- `CSZ`
- `IMB`
- `IMB_Digits`

Later migrations indicate additional fields such as `Title`.

**Role:** Master data for election jurisdictions and mailing identity information.

### Contact

Created in migration 02 and joined from import/export logic.

**Role:** Contact and addressing data associated with a jurisdiction.

### Settings

Created in migration 03 and used by `SettingsRepository`.

**Role:** Application configuration stored in the database, including STID-related values referenced by kit workflows.

### Kit

Observed fields in `KitRepository` and migrations:

- `ID`
- `JobNumber`
- `JCode`
- `CreatedOn`
- `LabelsPrinted`
- `ExportedToSnailWorks`
- `InkJetJob`
- `JobType`
- `Filename`
- `Cass`
- `Status`
- `OutboundSTID`
- `InboundSTID`
- `OfficeCopiesAmount`

**Role:** Central workflow entity representing a print/export job for a jurisdiction.

### KitLabels

Created in migration 05 and counted in kit switchboard queries.

**Role:** Individual label records associated with a kit.

### InkJetRecords

Created in migration 09 and extended later with `KitLabelID` and `ColorId`.

**Role:** Inkjet output records tied to kits and, later, colors and label relationships.

### CustomOfficeCopyJob

Created in migration 18.

**Role:** Tracks custom office copy proof/export work outside the standard label path.

### Colors

Created in migration 19.

**Role:** Lookup table used for purple-envelope color assignment and inkjet record coloring.

## Relationship Summary

- `Kit.JCode` links kits to a jurisdiction.
- `KitLabels.KitId` links labels to a kit.
- `InkJetRecords.KitId` links inkjet records to a kit.
- Later migrations add relationship and foreign-key support between key tables.
- Contacts appear tied to jurisdictions through a jurisdiction code field.
- Custom office copy jobs appear tied to jurisdiction and batch-processing state.

## Migration History

Notable schema evolution based on migration names:

1. Create `Jurisdiction`
2. Create `Contact`
3. Create `Settings`
4. Create `Kit`
5. Create `Kit_Labels`
6. Alter `Kit`
7. Alter `Kit` labels/settings behavior
8. Add inkjet job to `Kit`
9. Create `InkJetRecords`
10. Add table relations
11. Add foreign keys
12. Add type to `Kit`
13. Add file/cass fields to `Kit`
14. Add STIDs to `Kit`
15. Add `KitLabelID` to `InkJetRecords`
16. Add title to `Jurisdiction`
17. Add office copies amount to `Kit`
18. Create `CustomOfficeCopyJob`
19. Create `Colors`
20. Add `ColorId` to `InkJetRecords`

## Data Access Patterns

- Repositories use direct SQL strings and `Automapper.AutoMap`.
- Search methods build `LIKE` queries from controller-provided values.
- Pagination relies on `DAL.PagedQuery`.
- Validation is light and often commented out in controller flows.

## Change Guidance

- Treat migrations as the source of truth for schema evolution.
- If a repository query changes shape, verify all views and export scripts using that entity.
- Search both `App/` and `ImportService/` before renaming fields or changing status semantics.

---

_Generated for brownfield analysis._

+ 81
- 0
docs/development-guide.md Parādīt failu

@@ -0,0 +1,81 @@
# tracking_kits - Development Guide

**Date:** 2026-03-13

## Prerequisites

- Windows host with IIS
- Classic ASP enabled in IIS
- Access/Jet or ACE provider available
- COM dependencies installed if you need to exercise print/proof/export features
- Access to required local drives or network shares for non-dev environments

## Local Setup

1. Configure an IIS site whose root points to the repository root.
2. Confirm `index.asp` is configured as the default document. The repo `web.config` already adds it.
3. Enable Classic ASP in IIS.
4. Review [../App/app.config.asp](../App/app.config.asp):
- `Routes.Initialize "/App/"`
- `dev` flag selection
- `ExportDirectory` mapping
- external CSS/JS URLs
5. If you need export/proof behavior, ensure the dependencies in [../Dependancies](../Dependancies) are present and registered appropriately on the machine.

## Running the Application

- Browse the IIS site root.
- The browser is redirected from `index.asp` into the Home controller.
- Use the switchboard UI to access kit creation, tracking kits, purple envelope jobs, and custom office copy jobs.

## Running Tests

- Open [../Tests/Test_All.asp](../Tests/Test_All.asp) through IIS.
- Expect coverage to focus on helper libraries and low-level utilities rather than full application workflows.

## Working with the Database

- The app uses repositories and ADO, not an ORM.
- Sample MDB files exist in `Data/`.
- Schema evolution is implemented in [../Data/Migrations](../Data/Migrations).
- Before changing a repository query, inspect the corresponding migration history and view usage.

## Working with Reports and Exports

- Report definitions live in `Data/*.rep`.
- PDF/export code appears in `KitController` and import-service scripts.
- Many of these flows depend on file-system writes and COM objects, so static edits should be followed by manual Windows verification.

## Common Brownfield Change Workflow

1. Identify the entry controller action and matching view.
2. Trace the repository calls and any related view-model class.
3. Check whether the same business rule also exists in `ImportService/`.
4. If persistence changes are needed, add a migration and update repository SQL.
5. Run helper tests if applicable.
6. Manually verify the affected page or export flow in IIS on Windows.

## Code Conventions Observed

- Use `Option Explicit` at the top of ASP/VBScript files.
- Keep `<!--#include file="..."-->` directives near the top.
- Follow existing PascalCase naming for classes and descriptive file names.
- Match the surrounding indentation and spacing style.
- Avoid introducing new abstractions unless they reduce repeated controller/repository patterns.

## Environment Caveats

- `App/app.config.asp` embeds environment switching and COM unlock behavior.
- `ImportService/TrackingDataImport.vbs` contains separate path/config handling from the web app.
- This workspace cannot validate IIS, COM registration, Jet/ACE, or network-share access.

## Recommended Validation by Change Type

- **View-only changes:** Manual browser verification in IIS.
- **Controller/repository changes:** Browser verification plus targeted test runner pass if helper code changes.
- **Schema changes:** Migration review, repository validation, and manual smoke test.
- **Proof/export changes:** Windows-only manual verification of generated files and downstream handoff behavior.

---

_Generated for brownfield analysis._

+ 72
- 0
docs/index.md Parādīt failu

@@ -0,0 +1,72 @@
# tracking_kits Documentation Index

**Type:** monolith
**Primary Language:** Classic ASP / VBScript
**Architecture:** MVC-style server-rendered web application with repository-backed data access
**Last Updated:** 2026-03-13

## Project Overview

`tracking_kits` is a Windows-hosted Classic ASP application for election mail operations. It helps staff create mail tracking kits, generate proofs, prepare inkjet output files for printers, and export tracking data for downstream mail-tracking systems such as SnailWorks.

The application is not a modern REST service. It is a server-rendered IIS site that routes requests into controller `.asp` files, renders views from `App/Views`, and accesses an Access/Jet database through repository classes under `App/DomainModels`.

## Quick Reference

- **Entry Point:** [../index.asp](../index.asp)
- **Primary Bootstrap:** [../App/include_all.asp](../App/include_all.asp)
- **Route Initialization:** [../App/app.config.asp](../App/app.config.asp)
- **MVC Framework:** [../MVC](../MVC)
- **Application Code:** [../App](../App)
- **Database Migrations:** [../Data/Migrations](../Data/Migrations)
- **Import/Automation:** [../ImportService](../ImportService)
- **Tests:** [../Tests](../Tests)

## Generated Documentation

- [Project Overview](./project-overview.md) - Purpose, classification, stack, and major capabilities.
- [Source Tree Analysis](./source-tree-analysis.md) - Annotated repo layout and critical folders.
- [Architecture](./architecture.md) - Runtime architecture, request flow, dependencies, and constraints.
- [Development Guide](./development-guide.md) - Local setup, IIS assumptions, test execution, and change workflow.
- [Component Inventory](./component-inventory.md) - Controllers, repositories, UI surfaces, shared libraries, and automation components.
- [Data Models](./data-models.md) - Core tables, relationships, and migration history.
- [API Contracts](./api-contracts.md) - Route/action surface and non-HTTP export interfaces.
- [Module Map](./module-map.md) - Feature-by-feature guide to the files you should open first when making changes.

## Existing Documentation

- [README.md](../README.md) - Short project summary and current running notes.
- [AGENTS.md](../AGENTS.md) - Repository conventions and BMAD skill instructions used in this workspace.

## Getting Started

### Runtime Prerequisites

- Windows with IIS and Classic ASP enabled
- Access/Jet or ACE database provider
- COM components used by the app registered on the host if you need printing/export features
- Access to the configured export/import network shares in non-dev environments

### Local Development

1. Point an IIS site at the repository root.
2. Ensure `index.asp` is a default document and Classic ASP is enabled.
3. Review [../App/app.config.asp](../App/app.config.asp) for the active `dev` mode and export directory mapping.
4. If you need printer/proof/export behavior, verify the COM dependencies under [../Dependancies](../Dependancies) are installed and licensed.

### Tests

- Open [../Tests/Test_All.asp](../Tests/Test_All.asp) through IIS to run the ASPUnit suite.
- The automated tests primarily cover helper libraries, not the full application workflow.

## For Brownfield Changes

- Read [architecture.md](./architecture.md) first for the request/data flow.
- Read [module-map.md](./module-map.md) next if you already know which feature area you need to change.
- Read [data-models.md](./data-models.md) before touching repositories or migrations.
- Read [development-guide.md](./development-guide.md) before changing environment-sensitive code.
- Read [_bmad-output/project-context.md](../_bmad-output/project-context.md) before asking an AI agent to implement changes.

---

_Documentation generated for brownfield analysis._

+ 284
- 0
docs/module-map.md Parādīt failu

@@ -0,0 +1,284 @@
# tracking_kits - Module Map

**Date:** 2026-03-13

This guide is the shortest path into the codebase when you need to change a specific feature area. It is organized by module and operational workflow rather than by folder alone.

## Fastest Starting Points

If you need to make a change, start here first:

- **Kit creation / label workflows:** [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- **Jurisdiction search / import:** [../App/Controllers/Jurisdiction/JurisdictionController.asp](../App/Controllers/Jurisdiction/JurisdictionController.asp)
- **Proof/export behavior:** [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp) and [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs)
- **Inkjet / color assignment:** [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp) and [../App/DomainModels/InkjetRecordsRepository.asp](../App/DomainModels/InkjetRecordsRepository.asp)
- **Database/schema behavior:** [../Data/Migrations](../Data/Migrations) and the matching repository under [../App/DomainModels](../App/DomainModels)

## 1. Kit Module

### What it owns

- creating tracking kits
- creating label records for a kit
- operational switchboard screens
- purple-envelope workflows
- SnailWorks export
- label PDF export
- status transitions on kits

### Main files

- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- [../App/DomainModels/KitRepository.asp](../App/DomainModels/KitRepository.asp)
- [../App/DomainModels/KitLabelsRepository.asp](../App/DomainModels/KitLabelsRepository.asp)
- [../App/DomainModels/SnailWorksRepository.asp](../App/DomainModels/SnailWorksRepository.asp)
- [../App/Views/Kit/CreateTrackingKit.asp](../App/Views/Kit/CreateTrackingKit.asp)
- [../App/Views/Kit/SwitchBoardIndex.asp](../App/Views/Kit/SwitchBoardIndex.asp)
- [../App/Views/Kit/SwitchBoardEdit.asp](../App/Views/Kit/SwitchBoardEdit.asp)
- [../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp](../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp)

### Read order

1. `KitController.CreateTrackingKit` / `CreatePost` / `CreateTrackingKitPost`
2. `KitRepository.AddNew`
3. `KitLabelsRepository.BulkAdd`
4. `ExportTrackingLabels` and `ExportSnailWorksTracking`
5. matching view template

### Important coupling

- `CreatePost` creates a kit, bulk-creates labels, then immediately generates export artifacts.
- `CreateTrackingKitPost` creates the kit and labels but does not call the export methods.
- `SwitchBoardEdit` is the main inspection screen for label-based kits.
- `SwitchBoardPurpleEnvelopeEdit` combines kit data, STID choices, color lists, and precinct-level color data in one page model.
- Purple-envelope color changes are persisted through `InkjetRecordsRepository`, not `KitRepository`.

### Hotspots to treat carefully

- status strings such as `Ready to Assign Labels` and `Ready To Assign STIDS`
- export filenames and folder structure
- any change to `JobType`, `OutboundSTID`, `InboundSTID`, or `OfficeCopiesAmount`
- label creation count and bulk-add behavior

## 2. Jurisdiction Module

### What it owns

- jurisdiction CRUD
- jurisdiction search
- jurisdiction-linked contact display
- jurisdiction file import
- entry path into kit creation

### Main files

- [../App/Controllers/Jurisdiction/JurisdictionController.asp](../App/Controllers/Jurisdiction/JurisdictionController.asp)
- [../App/DomainModels/JurisdictionRepository.asp](../App/DomainModels/JurisdictionRepository.asp)
- [../App/DomainModels/ContactsRepository.asp](../App/DomainModels/ContactsRepository.asp)
- [../App/Views/Jurisdiction/index.asp](../App/Views/Jurisdiction/index.asp)
- [../App/Views/Jurisdiction/edit.asp](../App/Views/Jurisdiction/edit.asp)
- [../App/Views/Jurisdiction/import.asp](../App/Views/Jurisdiction/import.asp)
- [../App/Views/Home/CreateTrackingKit.asp](../App/Views/Home/CreateTrackingKit.asp)

### Read order

1. `JurisdictionController.Index` / `Search`
2. `JurisdictionRepository.FindPaged` / `SearchTablePaged`
3. `JurisdictionController.ImportPost` if the change involves file import
4. related edit/create views

### Important coupling

- Search fields are hard-coded in both `HomeController.Search` and `JurisdictionController.Search`.
- `JurisdictionController.Edit` also loads related contacts.
- `ImportPost` is one of the denser controller methods in the app: it handles file upload validation, file rewrite, Jet text-driver parsing, SQL projection, and then persistence/import behavior.
- The kit-creation chooser in the Home area links into `KitController.createTrackingKit` by `JCode`.

### Hotspots to treat carefully

- import file format assumptions: accepted extensions, header handling, tab-vs-delimited mode
- SQL expressions that derive `JCODE`, `Name`, `CSZ`, and `IMB_Digits`
- any field rename touching both repository SQL and import mapping

## 3. Inkjet / Purple Envelope Module

### What it owns

- inkjet record CRUD
- precinct-level data for purple-envelope jobs
- color assignment per kit or precinct

### Main files

- [../App/Controllers/InkjetRecords/InkjetRecordsController.asp](../App/Controllers/InkjetRecords/InkjetRecordsController.asp)
- [../App/DomainModels/InkjetRecordsRepository.asp](../App/DomainModels/InkjetRecordsRepository.asp)
- [../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp](../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp)
- [../App/DomainModels/ColorsRepository.asp](../App/DomainModels/ColorsRepository.asp)

### Read order

1. `KitController.SwitchBoardPurpleEnvelopeEdit`
2. `InkjetRecordsRepository.GetDistinctPrecinctsByKitId`
3. `AssignKitColorPost` / `AssignPrecinctColorsPost`
4. `InkjetRecordsRepository.UpdateColorForKit` / `UpdateColorForPrecinct`

### Important coupling

- purple-envelope UI reads colors and precinct data live in the view
- current color names are resolved inside the template using `ColorsRepository.FindByID`
- the kit edit flow and the precinct color flow share the same screen but use separate forms and anti-CSRF tokens

### Hotspots to treat carefully

- precinct key naming convention: `PrecinctColor_<PRECINCT>`
- `ColorId` propagation rules
- query performance if precinct count grows, because the view performs color lookups during rendering

## 4. Export / Proof Module

### What it owns

- label PDF generation
- SnailWorks CSV export
- report template usage
- file movement into export directories

### Main files

- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- [../App/DomainModels/SnailWorksRepository.asp](../App/DomainModels/SnailWorksRepository.asp)
- [../Data/Label_Report.rep](../Data/Label_Report.rep)
- [../Data/Proofs.rep](../Data/Proofs.rep)
- [../Data/Office-Copy-Proof.rep](../Data/Office-Copy-Proof.rep)
- [../Data/Custom Office Copies Proof.rep](../Data/Custom%20Office%20Copies%20Proof.rep)

### Read order

1. `ExportTrackingLabels`
2. `ExportSnailWorksTracking`
3. `SnailWorksRepository.GetSnailWorksExportById`
4. report template file referenced by the action

### Important coupling

- export methods depend on `ExportDirectory` from [../App/app.config.asp](../App/app.config.asp)
- label PDF generation uses a temporary file in `Data\` before moving it to the final folder
- SnailWorks header values are partially hard-coded inside repository SQL
- SnailWorks detail records join `InkjetRecords`, `KitLabels`, and `Kit`

### Hotspots to treat carefully

- COM object availability and provider connection strings
- jurisdiction-based folder naming
- hard-coded user IDs, emails, and export metadata in `SnailWorksRepository`
- file overwrite/delete behavior before new export generation

## 5. Import Service / Batch Automation Module

### What it owns

- status-driven processing outside the web app
- custom office copy proof generation
- export/import batch processing
- downstream handoff logic such as SFTP and file polling

### Main files

- [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs)
- [../InstallService.vbs](../InstallService.vbs)
- [../CiCd](../CiCd)

### What to know before editing

- this script is effectively a second application with its own environment config
- it contains hard-coded path, provider, and external integration behavior separate from `App/app.config.asp`
- the file is encoded differently from most repo files and is harder to inspect casually
- status transitions used here overlap with values set in web controllers

### Hotspots to treat carefully

- any status name change
- any path or folder naming change
- proof/export file naming conventions
- any change to custom office copy jobs

## 6. Shared Framework Module

### What it owns

- routing and dispatch
- HTML helpers and form generation
- anti-CSRF tokens
- automapping of forms and recordsets
- utility collections/helpers used across the app

### Main files

- [../MVC/lib.MVC.asp](../MVC/lib.MVC.asp)
- [../MVC/lib.Routes.asp](../MVC/lib.Routes.asp)
- [../MVC/lib.HTML.asp](../MVC/lib.HTML.asp)
- [../MVC/lib.HTMLSecurity.asp](../MVC/lib.HTMLSecurity.asp)
- [../MVC/lib.Automapper.asp](../MVC/lib.Automapper.asp)
- [../MVC/lib.all.asp](../MVC/lib.all.asp)

### When to open this module

- form helpers render incorrectly
- anti-CSRF handling breaks
- controller dispatch or route generation is wrong
- multiple features fail in the same infrastructural way

## 7. Schema / Data Evolution Module

### What it owns

- database creation and schema changes
- historical meaning of fields added to kits, jurisdictions, and inkjet records

### Main files

- [../Data/Migrations/migrate.asp](../Data/Migrations/migrate.asp)
- [../Data/Migrations/lib.Migrations.asp](../Data/Migrations/lib.Migrations.asp)
- [../Data/Migrations](../Data/Migrations)

### Practical rule

If you add or reinterpret a field in a repository, search migrations first and then search controllers and import scripts for that field name. In this project, schema semantics leak into UI, export logic, and batch automation very quickly.

## Recommended Read Paths By Change Type

### “Add a new field to kit workflow”

Open:
- [../Data/Migrations](../Data/Migrations)
- [../App/DomainModels/KitRepository.asp](../App/DomainModels/KitRepository.asp)
- [../App/ViewModels/KitViewModels.asp](../App/ViewModels/KitViewModels.asp)
- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- [../App/Views/Kit](../App/Views/Kit)

### “Change jurisdiction import behavior”

Open:
- [../App/Controllers/Jurisdiction/JurisdictionController.asp](../App/Controllers/Jurisdiction/JurisdictionController.asp)
- [../App/Views/Jurisdiction/import.asp](../App/Views/Jurisdiction/import.asp)
- [../App/DomainModels/JurisdictionRepository.asp](../App/DomainModels/JurisdictionRepository.asp)

### “Change export file format or proof output”

Open:
- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- [../App/DomainModels/SnailWorksRepository.asp](../App/DomainModels/SnailWorksRepository.asp)
- [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs)
- [../Data](../Data)

### “Change purple-envelope or inkjet assignment logic”

Open:
- [../App/Controllers/Kit/KitController.asp](../App/Controllers/Kit/KitController.asp)
- [../App/DomainModels/InkjetRecordsRepository.asp](../App/DomainModels/InkjetRecordsRepository.asp)
- [../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp](../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp)
- [../App/DomainModels/ColorsRepository.asp](../App/DomainModels/ColorsRepository.asp)

---

_Generated as a focused brownfield navigation guide._

+ 88
- 0
docs/project-overview.md Parādīt failu

@@ -0,0 +1,88 @@
# tracking_kits - Project Overview

**Date:** 2026-03-13
**Type:** Web application
**Architecture:** Classic ASP MVC-style monolith

## Executive Summary

This project manages operational workflows for election mail tracking kits. Users browse jurisdictions, create tracking kits, print or export proof artifacts, manage purple envelope and custom office copy jobs, generate inkjet-ready files, and produce export files for online mail tracking. The app is tightly coupled to Windows infrastructure, IIS, COM/ActiveX components, and an Access database.

## Project Classification

- **Repository Type:** Monolith
- **Project Type:** Classic ASP server-rendered web app
- **Primary Languages:** VBScript, ASP, SQL, VBScript automation
- **Architecture Pattern:** Controller + View + Repository over shared MVC helper libraries

## Technology Stack Summary

| Category | Technology | Notes |
| --- | --- | --- |
| Web runtime | IIS + Classic ASP | `index.asp` redirects into controller ASP files |
| Server language | VBScript | `Option Explicit` is used in application files |
| MVC framework | Custom `/MVC` helpers | Routing, form cache, HTML security, flash, automapper |
| Data access | ADO via `lib.DAL.asp` | Repository classes issue SQL directly |
| Database | Access MDB via Jet/ACE | Sample MDB files exist in `Data/`; migrations are ASP/SQL scripts |
| Reporting | ReportMan ActiveX | Used for proofs and label PDFs |
| PDF/export | Debenu PDF Library ActiveX | Used by import/export automation |
| External integration | Chilkat COM components | FTP/SFTP and CSV-related utilities appear in automation/config |
| Testing | ASPUnit | Test runner is served through IIS |
| Automation | VBScript and PowerShell | Import service and helper scripts live outside the web request path |

## Key Features

- Jurisdiction browsing and search by `JCode`, `Name`, address, and IMB-related fields
- Tracking kit creation and maintenance
- Switchboard views for tracking kits and purple envelope kits
- Label and proof generation through report templates in `Data/*.rep`
- Inkjet record management and color assignment
- SnailWorks export generation
- Import-service automation for processing tracking data outside the web app

## Architecture Highlights

- Requests enter through controller `.asp` files directly, not a centralized front controller.
- Shared includes in [../App/include_all.asp](../App/include_all.asp) act as the real application bootstrap.
- Repositories own SQL and data mapping responsibilities.
- Views are server-side `.asp` templates rendered from controller methods.
- The runtime assumes file-system side effects: creating folders, writing PDFs/CSVs, and moving export files.
- The app has environment-specific behavior embedded in code, especially in [../App/app.config.asp](../App/app.config.asp) and [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs).

## Development Overview

### Prerequisites

- Windows + IIS + Classic ASP
- Access/Jet or ACE provider
- Registered COM dependencies for reporting/export features

### Getting Started

The repo can be edited anywhere, but meaningful runtime verification requires IIS on Windows. The Linux workspace is sufficient for code review and documentation generation, not end-to-end proof/export execution.

### Key Commands

- **Run app:** Serve repo root from IIS
- **Run tests:** Open `Tests/Test_All.asp`
- **Run import service manually:** `cscript ImportService/TrackingDataImport.vbs`

## Repository Structure

- `App/` contains the application logic, views, and repositories.
- `MVC/` contains the shared Classic ASP framework code.
- `Data/` contains migrations, report templates, and sample assets.
- `ImportService/` contains out-of-band processing logic.
- `Tests/` contains helper-library tests via ASPUnit.
- `Dependancies/` holds external binaries and COM-related artifacts needed in production-like environments.

## Documentation Map

- [index.md](./index.md) - master documentation index
- [architecture.md](./architecture.md) - detailed technical architecture
- [source-tree-analysis.md](./source-tree-analysis.md) - directory structure and entry points
- [development-guide.md](./development-guide.md) - local development workflow

---

_Generated for brownfield analysis._

+ 1
- 0
docs/project-scan-report.json Parādīt failu

@@ -0,0 +1 @@
{"workflow_version":"1.2.0","timestamps":{"started":"2026-03-13T00:00:00Z","last_updated":"2026-03-13T00:00:00Z"},"mode":"initial_scan","scan_level":"deep","project_root":"/workspace","project_knowledge":"/workspace/docs","completed_steps":[{"step":"step_1","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Classified as monolith with 1 part: Classic ASP web application"},{"step":"step_2","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Discovered existing repo README and AGENTS guidance"},{"step":"step_3","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Identified Classic ASP MVC, ADO/Access, IIS, ASPUnit, VBScript automation, and COM dependencies"},{"step":"step_4","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Documented controller routes, data model tables, UI surfaces, config patterns, import/export flows, and Windows-only integrations"},{"step":"step_5","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Generated annotated source tree analysis"},{"step":"step_6","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Generated development and deployment guidance"},{"step":"step_8","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Generated single-part architecture documentation"},{"step":"step_9","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Generated supporting documents for overview, components, APIs, and data models"},{"step":"step_10","status":"completed","timestamp":"2026-03-13T00:00:00Z","summary":"Generated master index for brownfield documentation"}],"current_step":"complete","findings":{"project_classification":"Monolithic Classic ASP web application with MVC-style folder layout and Windows-specific automation.","technology_stack":"VBScript/Classic ASP, IIS, ADO with Access/Jet or ACE, ReportMan, Chilkat, Debenu PDF Library, ASPUnit.","batches_completed":[{"path":"/workspace/App","files_scanned":61,"summary":"Core MVC application: controllers, views, repositories, DAL, and view models."},{"path":"/workspace/Data","files_scanned":33,"summary":"Report templates, sample assets, migrations, and sample MDB data."},{"path":"/workspace/ImportService","files_scanned":2,"summary":"VBScript automation for imports, proofs, and export processing."},{"path":"/workspace/MVC","files_scanned":19,"summary":"Shared Classic ASP MVC helper framework and utility libraries."},{"path":"/workspace/Tests","files_scanned":16,"summary":"ASPUnit runner plus helper/library tests and an import test harness."}],"project_types":[{"part_id":"app","project_type_id":"web","display_name":"Classic ASP web app"}]},"outputs_generated":["project-scan-report.json","index.md","project-overview.md","source-tree-analysis.md","architecture.md","development-guide.md","component-inventory.md","data-models.md","api-contracts.md"],"resume_instructions":"Documentation run completed. Re-run document-project to rescan or add deep-dive docs for a specific module."}

+ 148
- 0
docs/source-tree-analysis.md Parādīt failu

@@ -0,0 +1,148 @@
# tracking_kits - Source Tree Analysis

**Date:** 2026-03-13

## Overview

This repository is organized as a single Classic ASP application plus supporting automation and documentation assets. The web app lives under `App/` and depends on shared infrastructure in `MVC/`, database/report assets in `Data/`, and Windows-specific automation under `ImportService/`.

## Complete Directory Structure

```text
/workspace
|-- index.asp
|-- web.config
|-- README.md
|-- App/
| |-- app.config.asp
| |-- include_all.asp
| |-- Controllers/
| |-- DAL/
| |-- DomainModels/
| |-- ViewModels/
| `-- Views/
|-- MVC/
|-- Data/
| |-- Migrations/
| `-- *.rep / sample assets / sample MDB files
|-- ImportService/
|-- Tests/
| |-- ASPUnit/
| `-- Test_*.asp / TestCase_*.asp
|-- Dependancies/
|-- dist/
|-- uploads/
`-- _bmad-output/
```

## Critical Directories

### `App/Controllers`

**Purpose:** HTTP request handling and orchestration.

**Contains:** 8 controller files for `Home`, `Kit`, `Jurisdiction`, `InkjetRecords`, `Contacts`, `Settings`, `CustomOfficeCopyJob`, and `KitLabels`.

**Entry Points:** `App/Controllers/Home/HomeController.asp`, `App/Controllers/Kit/KitController.asp`

### `App/DomainModels`

**Purpose:** Repository layer and model classes.

**Contains:** SQL-backed repositories for jurisdictions, kits, labels, inkjet records, settings, contacts, SnailWorks exports, custom office copy jobs, and colors.

### `App/Views`

**Purpose:** Server-rendered UI templates.

**Contains:** View folders that mirror controllers, plus shared header/footer layout includes.

### `App/DAL`

**Purpose:** Database connectivity and low-level query execution.

**Contains:** `lib.DAL.asp`, the ADO abstraction used by repositories.

### `MVC`

**Purpose:** Shared framework and utility code.

**Contains:** Routing, HTML helpers, CSRF handling, flash messages, automapper, collections, strings, validation, upload, and JSON support.

### `Data/Migrations`

**Purpose:** Database schema evolution.

**Contains:** 20 numbered migration scripts, migration helpers, and bootstrap SQL/scripts.

### `Data`

**Purpose:** Reporting templates and sample data artifacts.

**Contains:** `.rep` templates, images, PDFs, PowerShell utilities, and sample MDB files.

### `ImportService`

**Purpose:** Out-of-band workflow processing.

**Contains:** `TrackingDataImport.vbs` and related service installers for importing, proving, and exporting jobs outside web requests.

### `Tests`

**Purpose:** ASPUnit-based verification.

**Contains:** Test runner, helper tests, and a VBScript harness for import-related behavior.

## Entry Points

- **Main web entry:** [../index.asp](../index.asp)
- **Route bootstrap:** [../App/app.config.asp](../App/app.config.asp)
- **Shared include bootstrap:** [../App/include_all.asp](../App/include_all.asp)
- **Import automation entry:** [../ImportService/TrackingDataImport.vbs](../ImportService/TrackingDataImport.vbs)
- **Test runner entry:** [../Tests/Test_All.asp](../Tests/Test_All.asp)

## File Organization Patterns

- Controllers, view models, views, and repositories are grouped by domain area.
- Most business logic is controller-driven, with repositories focused on persistence and record mapping.
- Shared runtime behavior is hidden in framework includes rather than dependency injection or explicit composition.
- The app uses direct file includes extensively; load order matters.
- Environment configuration is code-based rather than environment-variable based.

## Key File Types

### `.asp`

- **Pattern:** `App/**/*.asp`, `MVC/*.asp`, `Tests/*.asp`
- **Purpose:** Web controllers, views, repositories, framework helpers, and test pages.

### `.vbs`

- **Pattern:** `ImportService/*.vbs`, `App/ScaffoldRepo.vbs`
- **Purpose:** Batch automation and helper tooling.

### `.rep`

- **Pattern:** `Data/*.rep`
- **Purpose:** Report definitions used for proofs and PDF generation.

### `Migration_*.asp`

- **Pattern:** `Data/Migrations/Migration_*.asp`
- **Purpose:** Incremental schema changes implemented in Classic ASP/VBScript.

## Configuration Files

- [../web.config](../web.config) - IIS default document configuration.
- [../App/app.config.asp](../App/app.config.asp) - Route init, UI asset URLs, `dev` mode, export directory, and COM unlock setup.
- [../_bmad/bmm/config.yaml](../_bmad/bmm/config.yaml) - BMAD workflow configuration for documentation generation.

## Notes for Development

- `dist/` and `uploads/` are runtime-facing folders but not the core of the app logic.
- `Dependancies/` is critical for operational parity even though the application code can be read without it.
- Several workflows depend on network shares and Windows paths that are not testable in this workspace.

---

_Generated for brownfield analysis._

+ 0
- 1524
uploads/BRM Permit Infor 1-30-26.txt
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


Notiek ielāde…
Atcelt
Saglabāt

Powered by TurnKey Linux.