Rehost offline building type source catalog

This commit is contained in:
Jan Petykiewicz 2026-04-19 02:56:10 -07:00
commit c21a47d60f
6 changed files with 4541 additions and 12 deletions

View file

@ -0,0 +1,148 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BuildingTypeSourceKind {
Bca,
Bty,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeSourceFile {
pub file_name: String,
pub raw_stem: String,
pub canonical_stem: String,
pub source_kind: BuildingTypeSourceKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeSourceEntry {
pub canonical_stem: String,
pub raw_stems: Vec<String>,
pub source_kinds: Vec<BuildingTypeSourceKind>,
pub file_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeSourceReport {
pub directory_path: String,
pub bca_file_count: usize,
pub bty_file_count: usize,
pub unique_canonical_stem_count: usize,
pub notes: Vec<String>,
pub files: Vec<BuildingTypeSourceFile>,
pub entries: Vec<BuildingTypeSourceEntry>,
}
pub fn inspect_building_types_dir(
path: &Path,
) -> Result<BuildingTypeSourceReport, Box<dyn std::error::Error>> {
let mut files = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if !entry.file_type()?.is_file() {
continue;
}
let file_name = entry.file_name().to_string_lossy().into_owned();
let Some(extension) = Path::new(&file_name)
.extension()
.and_then(|extension| extension.to_str())
.map(|extension| extension.to_ascii_lowercase())
else {
continue;
};
let source_kind = match extension.as_str() {
"bca" => BuildingTypeSourceKind::Bca,
"bty" => BuildingTypeSourceKind::Bty,
_ => continue,
};
let raw_stem = Path::new(&file_name)
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("")
.to_string();
if raw_stem.is_empty() {
continue;
}
files.push(BuildingTypeSourceFile {
file_name,
canonical_stem: canonicalize_building_stem(&raw_stem),
raw_stem,
source_kind,
});
}
files.sort_by(|left, right| {
left.canonical_stem
.cmp(&right.canonical_stem)
.then_with(|| left.file_name.cmp(&right.file_name))
});
let mut grouped = BTreeMap::<String, Vec<&BuildingTypeSourceFile>>::new();
for file in &files {
grouped
.entry(file.canonical_stem.clone())
.or_default()
.push(file);
}
let entries = grouped
.into_iter()
.map(|(canonical_stem, group)| BuildingTypeSourceEntry {
canonical_stem,
raw_stems: group
.iter()
.map(|file| file.raw_stem.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
source_kinds: group
.iter()
.map(|file| file.source_kind.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
file_names: group
.iter()
.map(|file| file.file_name.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect(),
})
.collect::<Vec<_>>();
let bca_file_count = files
.iter()
.filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca))
.count();
let bty_file_count = files
.iter()
.filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty))
.count();
let notes = vec![
"BuildingTypes sources are grouped by a canonical stem that lowercases and strips spaces, underscores, and hyphens so paired .bca/.bty variants collapse onto one asset token.".to_string(),
"This report is an offline asset-pool view only; it does not by itself assign live candidate ids or prove scenario candidate-table availability.".to_string(),
];
Ok(BuildingTypeSourceReport {
directory_path: path.display().to_string(),
bca_file_count,
bty_file_count,
unique_canonical_stem_count: entries.len(),
notes,
files,
entries,
})
}
fn canonicalize_building_stem(stem: &str) -> String {
stem.chars()
.filter(|ch| !matches!(ch, ' ' | '_' | '-'))
.flat_map(|ch| ch.to_lowercase())
.collect()
}

View file

@ -1,3 +1,4 @@
pub mod building;
pub mod calendar;
pub mod campaign_exe;
pub mod economy;
@ -10,6 +11,10 @@ pub mod step;
pub mod summary;
pub mod win;
pub use building::{
BuildingTypeSourceEntry, BuildingTypeSourceFile, BuildingTypeSourceKind,
BuildingTypeSourceReport, inspect_building_types_dir,
};
pub use calendar::{CalendarPoint, MONTH_SLOTS_PER_YEAR, PHASE_SLOTS_PER_MONTH, TICKS_PER_PHASE};
pub use campaign_exe::{
CAMPAIGN_SCENARIO_COUNT, CampaignExeInspectionReport, CampaignPageBand, CampaignScenarioEntry,