1use std::collections::BTreeMap;
4
5use powerio_core::{
6 ArtifactPath, Destination, Diagnostic, EmitResult, Error, Fidelity, MemoryArtifact, PioModule,
7};
8
9use crate::PioValue;
10
11#[derive(Clone, Debug, Default)]
14#[non_exhaustive]
15pub enum SincalContainer {
16 #[default]
18 Sqlite,
19 Archive {
26 project_name: String,
28 },
29 #[non_exhaustive]
32 ArchiveWithOptions {
33 project_name: String,
35 options: ArchiveOptions,
38 },
39}
40
41impl SincalContainer {
42 #[must_use]
46 pub fn archive_with(project_name: impl Into<String>, options: ArchiveOptions) -> Self {
47 Self::ArchiveWithOptions {
48 project_name: project_name.into(),
49 options,
50 }
51 }
52}
53
54#[derive(Clone, Debug, Default, PartialEq, Eq)]
58#[non_exhaustive]
59pub struct ArchiveOptions {}
60
61#[derive(Clone, Debug, Default)]
67#[non_exhaustive]
68pub struct SincalExperimentalOptions {
69 pub container: SincalContainer,
70 pub nominal_ll_volts: BTreeMap<String, f64>,
75}
76
77pub(crate) fn emit(
78 module: &PioModule<PioValue>,
79 format: &str,
80 options: &SincalExperimentalOptions,
81 destination: Destination,
82) -> Result<EmitResult, Error> {
83 let selected = crate::resolve_format(format).map(|f| f.token);
84 if !matches!(
85 selected,
86 Some("sincal" | "sincal-balanced" | "sincal-multiconductor")
87 ) {
88 return Err(invalid(
89 "experimental SINCAL options require a SINCAL target",
90 ));
91 }
92 let (database, mut diagnostics, retained_omitted) = match module.value() {
93 PioValue::BalancedNetwork(net) => {
94 if selected == Some("sincal-multiconductor") {
95 return Err(invalid(
96 "sincal-multiconductor cannot write a BalancedNetwork; use sincal-balanced with experimental options",
97 ));
98 }
99 if !options.nominal_ll_volts.is_empty() {
100 return Err(invalid(
101 "balanced SINCAL output uses typed bus nominal kV; nominal_ll_volts is only a multiconductor option",
102 ));
103 }
104 let output = powerio_tnd::formats::sincal::__write_sincal_balanced_experimental(net)?;
105 (
106 output.database,
107 output.diagnostics,
108 &powerio_tx::diagnostics::codes::EMIT_SINCAL_RETAINED_SOURCE_OMITTED,
109 )
110 }
111 PioValue::MulticonductorNetwork(net) => {
112 if selected == Some("sincal-balanced") {
113 return Err(invalid(
114 "sincal-balanced cannot write a MulticonductorNetwork; use sincal-multiconductor with experimental options to retain its conductor model",
115 ));
116 }
117 let mut tnd_options =
120 powerio_tnd::formats::sincal::ExperimentalMulticonductorOptions::default();
121 tnd_options
122 .nominal_ll_volts
123 .clone_from(&options.nominal_ll_volts);
124 let output = powerio_tnd::formats::sincal::__write_sincal_multiconductor_experimental(
125 net,
126 &tnd_options,
127 )?;
128 (
129 output.database,
130 output.diagnostics,
131 &powerio_dist::diagnostics::codes::EMIT_SINCAL_MULTICONDUCTOR_RETAINED_SOURCE_OMITTED,
132 )
133 }
134 value => {
135 return Err(Error::new(
136 &crate::codes::REQUEST_MODULE_WRONG_MODEL_KIND,
137 format!(
138 "experimental SINCAL output requires a BalancedNetwork or MulticonductorNetwork, received {}",
139 value.type_name()
140 ),
141 ));
142 }
143 };
144 if module.sources().iter().any(|s| {
145 s.format().is_some_and(|f| {
146 matches!(
147 f.as_str(),
148 "sincal" | "sincal-balanced" | "sincal-multiconductor"
149 )
150 })
151 }) {
152 diagnostics.push(Diagnostic::of(
153 retained_omitted,
154 "Experimental fresh output uses the typed electrical value only. Retained native fault, dynamic, protection, diagram, result and other source-only records are omitted.",
155 ));
156 }
157 let (name, bytes) = match &options.container {
158 SincalContainer::Sqlite => ("case.db", database),
159 SincalContainer::Archive { project_name }
160 | SincalContainer::ArchiveWithOptions { project_name, .. } => (
161 "case.sinx",
162 powerio_tnd::formats::sincal::storage::authoring::candidate_archive(
163 &database,
164 project_name,
165 )
166 .map_err(|e| {
167 Error::new(&crate::codes::EMIT_SINCAL_PACKAGING_FAILED, e.to_string()).with_cause(e)
168 })?,
169 ),
170 };
171 destination.__commit_artifacts(
174 false,
175 Fidelity::Canonical,
176 vec![MemoryArtifact::new(ArtifactPath::new(name)?, bytes)],
177 diagnostics,
178 )
179}
180
181fn invalid(message: &str) -> Error {
182 Error::new(&crate::codes::REQUEST_EMIT_INVALID_OPTIONS, message)
183}