///|
/// Parse one RFC-4180-style row. Quoted fields may contain commas and escaped
/// quotes. Newlines are handled by the caller so row diagnostics remain clear.
pub fn parse_csv_row(row : String) -> Array[String] raise ScoreError {
if row == "" {
raise EmptyInput
}
let fields : Array[String] = []
let mut builder = StringBuilder::new()
let mut quoted = false
let mut escaped_quote = false
for char in row {
if escaped_quote {
if char == '"' {
builder.write_char('"')
escaped_quote = false
} else {
raise InvalidRow(row=0, reason="unexpected character after quote")
}
} else if char == '"' {
quoted = !quoted
} else if char == ',' && !quoted {
fields.push(builder.to_string())
builder = StringBuilder::new()
} else {
builder.write_char(char)
}
}
if quoted || escaped_quote {
raise InvalidRow(row=0, reason="unterminated quoted field")
}
fields.push(builder.to_string())
fields
}
///|
pub struct BatchSummary {
total : Int
successes : Int
failures : Int
records : Array[EvaluationRecord]
} derive(Debug)
///|
pub fn evaluate_csv_rows(
scale~ : String,
rows~ : Array[String],
) -> BatchSummary {
let records : Array[EvaluationRecord] = []
let mut successes = 0
let mut failures = 0
for index, row in rows {
let record = try {
let columns = parse_csv_row(row)
let fields = columns.map(fn(value) {
score_input(
label="value",
value=@string.parse_int(value) catch { _ => 0 },
)
})
evaluate_scale(case_id="\{scale}-\{index}", scale~, fields~)
} catch {
error => evaluation_failure(case_id="\{scale}-\{index}", scale~, error~)
}
records.push(record)
if record.is_success() {
successes += 1
} else {
failures += 1
}
}
{ total: rows.length(), successes, failures, records }
}
///|
pub fn score_result_fhir_observation(
result : ScoreResult,
identifier : String,
) -> String {
"{\"resourceType\":\"Observation\",\"id\":\"\{identifier}\",\"status\":\"final\",\"code\":{\"text\":\"\{result.name}\"},\"valueInteger\":\{result.total},\"interpretation\":{\"text\":\"\{band_name(result.band)}\"}}"
}