///|
priv struct LinkageCandidateBatch {
candidates : Array[LinkCandidate]
blocked_candidate_count : Int
evaluated_pair_count : Int
}
///|
fn linkage_candidate_order(left : LinkCandidate, right : LinkCandidate) -> Int {
if left.score > right.score {
-1
} else if left.score < right.score {
1
} else if left.left_id != right.left_id {
left.left_id.lexical_compare(right.left_id)
} else if left.right_id != right.right_id {
left.right_id.lexical_compare(right.right_id)
} else {
0
}
}
///|
fn linkage_evaluation_limit(
candidate_count : Int,
configured_limit : Int,
) -> Int {
if candidate_count < configured_limit {
candidate_count
} else {
configured_limit
}
}
///|
fn evaluate_linkage_pair(
left : LinkRecord,
right : NameRecord,
config : LinkageConfig,
) -> Result[LinkCandidate, LinkageError] {
let evidence = match match_names(left.name, right.name, config.match_config) {
Err(error) => return Err(LinkageMatchFailed(left.id, right.id, error))
Ok(value) => value
}
let accepted = evidence.matched && evidence.score >= config.minimum_score
Ok({
left_id: left.id,
right_id: right.id,
score: evidence.score,
accepted,
evidence,
})
}
///|
fn collect_left_linkage_candidates(
prepared : PreparedLinkageInputs,
left : LinkRecord,
config : LinkageConfig,
) -> Result[(Array[LinkCandidate], Int, Int), LinkageError] {
let candidate_set = match prepared.right_index.candidate_records(left.name) {
Err(error) => return Err(LinkageIndexFailed(LinkageLeft, left.id, error))
Ok(value) => value
}
let unscored = candidate_set.records.copy()
unscored.sort_by(fn(first, second) {
first.record.id.lexical_compare(second.record.id)
})
let blocked_count = unscored.length()
let evaluation_count = linkage_evaluation_limit(
blocked_count,
config.max_candidates_per_left,
)
let retained : Array[LinkCandidate] = []
for index = 0; index < evaluation_count; index = index + 1 {
let candidate = match
evaluate_linkage_pair(left, unscored[index].record, config) {
Err(error) => return Err(error)
Ok(value) => value
}
if candidate.accepted || config.retain_rejected {
retained.push(candidate)
}
}
Ok((retained, blocked_count, evaluation_count))
}
///|
fn collect_linkage_candidates(
prepared : PreparedLinkageInputs,
config : LinkageConfig,
) -> Result[LinkageCandidateBatch, LinkageError] {
let candidates : Array[LinkCandidate] = []
let mut blocked_candidate_count = 0
let mut evaluated_pair_count = 0
for left in prepared.left {
let result = match collect_left_linkage_candidates(prepared, left, config) {
Err(error) => return Err(error)
Ok(value) => value
}
for candidate in result.0 {
candidates.push(candidate)
}
blocked_candidate_count = blocked_candidate_count + result.1
evaluated_pair_count = evaluated_pair_count + result.2
}
candidates.sort_by(linkage_candidate_order)
Ok({ candidates, blocked_candidate_count, evaluated_pair_count })
}