use serde::Deserialize;
use serde_json::{Map, Value, json};
use std::collections::BTreeMap;
use std::sync::OnceLock;
pub const CATALOG_VERSION: &str = "properties-v1";
const PROPERTIES: &str = include_str!("properties.json");
const RISKS: &str = include_str!("risks.json");
const CHANGE_PREFIX: &str = "Two versions of one file are given, `before` and `after`. \
Answer only about what changed: does `after` show the following where `before` did not? ";
const CHANGE_PREFIX_PLAIN: &str = "Two versions of one file are given, `before` and `after`. \
Answer only about what changed: ";
const CHANGE_PRESENT: &str = "`after` shows it and `before` did not.";
const CHANGE_ABSENT: &str =
"`before` already showed it, or neither version does, or `after` no longer does.";
#[derive(Debug, Deserialize)]
pub struct Check {
pub id: String,
pub task: String,
pub present: String,
pub absent: String,
pub evidence: String,
}
pub fn properties() -> &'static [Check] {
static PARSED: OnceLock<Vec<Check>> = OnceLock::new();
PARSED.get_or_init(|| {
serde_json::from_str(PROPERTIES).expect("properties.json ships with the binary and parses")
})
}
pub fn risks() -> &'static [Check] {
static PARSED: OnceLock<Vec<Check>> = OnceLock::new();
PARSED.get_or_init(|| {
serde_json::from_str(RISKS).expect("risks.json ships with the binary and parses")
})
}
fn noul(task: String, present: &str, absent: &str) -> Value {
json!({
"type": "noul",
"instructions": { "task": task },
"criteria": { "true": present, "false": absent },
})
}
pub fn file_questions() -> Map<String, Value> {
properties()
.iter()
.map(|c| (c.id.clone(), noul(c.task.clone(), &c.present, &c.absent)))
.collect()
}
pub fn change_questions() -> Map<String, Value> {
let mut questions: Map<String, Value> = properties()
.iter()
.map(|c| {
(
c.id.clone(),
noul(
format!("{CHANGE_PREFIX}{}", c.task),
CHANGE_PRESENT,
CHANGE_ABSENT,
),
)
})
.collect();
for risk in risks() {
questions.insert(
risk.id.clone(),
noul(
format!("{CHANGE_PREFIX_PLAIN}{}", risk.task),
&risk.present,
&risk.absent,
),
);
}
questions
}
pub fn described() -> Value {
properties()
.iter()
.chain(risks())
.map(|c| json!({ "check": c.id, "asks": c.present, "evidence": c.evidence }))
.collect()
}
pub fn scope_request(repository: &str, tree: &str, manifests: &str, asking: &[String]) -> Value {
const TASK: &str = "The complete list of source files in this repository is in state.tree, \
and the manifests it declares are in state.manifests. Is the file named in this question part of \
the product this repository ships or runs in production, rather than one of: a test, a fixture, a \
build or tooling script, an example or demo, documentation, a benchmark, generated output, or a \
prototype the project does not ship? Judge it in the context of the whole tree.";
let questions: Map<String, Value> = asking
.iter()
.enumerate()
.map(|(index, path)| {
(
format!("f{index}"),
noul(
format!("{TASK}\n\nThe file: {path}"),
"It is part of the product.",
"It is a test, tooling, an example, documentation, a benchmark, \
generated output or a prototype.",
),
)
})
.collect();
json!({
"model": super::MODEL,
"state": { "repository": repository, "tree": tree, "manifests": manifests },
"questions": questions,
})
}
pub fn file_request(path: &str, source: &str) -> Value {
json!({
"model": super::MODEL,
"state": { "file": { "path": path, "source": source } },
"questions": file_questions(),
})
}
pub fn patch_request(patch: &str) -> Value {
json!({
"model": super::MODEL,
"state": { "diff": patch },
"questions": change_questions(),
})
}
pub fn change_request(before: &str, after: &str) -> Value {
json!({
"model": super::MODEL,
"state": { "before": before, "after": after },
"questions": change_questions(),
})
}
pub const PRESENT_AT: f64 = 0.6;
pub fn health(values: &BTreeMap<String, f64>) -> Option<f64> {
if values.is_empty() {
return None;
}
let mean = values.values().sum::<f64>() / values.len() as f64;
Some(((1.0 - mean) * 10.0 * 100.0).round() / 100.0)
}
pub fn present(values: &BTreeMap<String, f64>) -> Vec<(String, f64)> {
let mut fired: Vec<_> = values
.iter()
.filter(|(_, v)| **v >= PRESENT_AT)
.map(|(k, v)| (k.clone(), *v))
.collect();
fired.sort_by(|a, b| b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
fired
}
pub fn aggregate(files: &[(u64, f64)]) -> Option<f64> {
let total: u64 = files.iter().map(|(bytes, _)| *bytes).sum();
if total == 0 {
return None;
}
let weighted: f64 = files
.iter()
.map(|(bytes, health)| *bytes as f64 * health)
.sum();
Some((weighted / total as f64 * 100.0).round() / 100.0)
}