///|
pub(all) struct DocumentAnalytics {
id : String
input_length : Int
output_length : Int
finding_count : Int
risk_score : Int
redaction_ratio : Float
average_confidence : Float
kinds : Map[String, Int]
} derive(Debug)
///|
pub(all) struct AnalyticsComparison {
left_id : String
right_id : String
finding_delta : Int
risk_delta : Int
length_delta : Int
ratio_delta : Float
} derive(Debug)
///|
pub fn analyze_document(
id : String,
input : String,
result : DeidResult,
) -> DocumentAnalytics {
{
id,
input_length: input.length(),
output_length: result.text.length(),
finding_count: result.findings.length(),
risk_score: risk_score(result.findings),
redaction_ratio: redaction_ratio(result),
average_confidence: average_confidence(result.findings),
kinds: finding_kind_counts(result.findings),
}
}
///|
pub fn analyze_documents(
items : Array[BatchItem],
config : RedactionConfig,
) -> Array[DocumentAnalytics] raise DeidError {
items.map(item => {
analyze_document(item.id, item.text, redact_with_config(item.text, config))
})
}
///|
pub fn compare_analytics(
left : DocumentAnalytics,
right : DocumentAnalytics,
) -> AnalyticsComparison {
{
left_id: left.id,
right_id: right.id,
finding_delta: right.finding_count - left.finding_count,
risk_delta: right.risk_score - left.risk_score,
length_delta: right.output_length - left.output_length,
ratio_delta: right.redaction_ratio - left.redaction_ratio,
}
}
///|
pub fn analytics_kind_totals(
items : Array[DocumentAnalytics],
) -> Map[String, Int] {
let result : Map[String, Int] = Map([])
for item in items {
merge_counts(result, item.kinds)
}
result
}
///|
pub fn analytics_risk_total(items : Array[DocumentAnalytics]) -> Int {
items.fold(init=0, (sum, item) => sum + item.risk_score)
}
///|
pub fn analytics_average_ratio(items : Array[DocumentAnalytics]) -> Float {
if items.is_empty() {
0.0
} else {
items.fold(init=0.0, (sum, item) => sum + item.redaction_ratio) /
Float::from_int(items.length())
}
}
///|
pub fn analytics_average_confidence(items : Array[DocumentAnalytics]) -> Float {
if items.is_empty() {
0.0
} else {
items.fold(init=0.0, (sum, item) => sum + item.average_confidence) /
Float::from_int(items.length())
}
}
///|
pub fn analytics_max_risk(
items : Array[DocumentAnalytics],
) -> DocumentAnalytics? {
let mut result = None
for item in items {
match result {
None => result = Some(item)
Some(previous) =>
if item.risk_score > previous.risk_score {
result = Some(item)
}
}
}
result
}
///|
pub fn analytics_min_risk(
items : Array[DocumentAnalytics],
) -> DocumentAnalytics? {
let mut result = None
for item in items {
match result {
None => result = Some(item)
Some(previous) =>
if item.risk_score < previous.risk_score {
result = Some(item)
}
}
}
result
}
///|
pub fn analytics_to_csv(items : Array[DocumentAnalytics]) -> String {
let lines = [
"id,input_length,output_length,findings,risk_score,redaction_ratio,average_confidence",
]
for item in items {
lines.push(
[
csv_cell(item.id),
"\{item.input_length}",
"\{item.output_length}",
"\{item.finding_count}",
"\{item.risk_score}",
"\{item.redaction_ratio}",
"\{item.average_confidence}",
].join(","),
)
}
lines.join("\n")
}
///|
pub fn analytics_to_json(items : Array[DocumentAnalytics]) -> String {
"[" +
items
.map(fn(item) {
"{" +
"\"id\":\{json_string(item.id)}," +
"\"input_length\":\{item.input_length}," +
"\"output_length\":\{item.output_length}," +
"\"finding_count\":\{item.finding_count}," +
"\"risk_score\":\{item.risk_score}," +
"\"redaction_ratio\":\{item.redaction_ratio}" +
"}"
})
.join(",") +
"]"
}
///|
pub fn analytics_markdown(items : Array[DocumentAnalytics]) -> String {
let lines = [
"| ID | Findings | Risk | Ratio | Confidence |", "| --- | ---: | ---: | ---: | ---: |",
]
for item in items {
lines.push(
"| \{item.id} | \{item.finding_count} | \{item.risk_score} | \{item.redaction_ratio} | \{item.average_confidence} |",
)
}
lines.join("\n")
}
///|
pub fn analytics_threshold(
items : Array[DocumentAnalytics],
risk : Int,
) -> Array[DocumentAnalytics] {
items.filter(fn(item) { item.risk_score >= risk })
}
///|
pub fn analytics_with_findings(
items : Array[DocumentAnalytics],
) -> Array[DocumentAnalytics] {
items.filter(fn(item) { item.finding_count > 0 })
}
///|
pub fn analytics_without_findings(
items : Array[DocumentAnalytics],
) -> Array[DocumentAnalytics] {
items.filter(fn(item) { item.finding_count == 0 })
}
///|
pub fn analytics_summary(items : Array[DocumentAnalytics]) -> String {
[
"documents=\{items.length()}",
"with_findings=\{analytics_with_findings(items).length()}",
"risk_total=\{analytics_risk_total(items)}",
"average_ratio=\{analytics_average_ratio(items)}",
"average_confidence=\{analytics_average_confidence(items)}",
].join("\n")
}