html`
<style>
.header-flex {
display: flex;
align-items: center;
margin-bottom: 0em;
}
.header-title {
font-size: 1.5em;
font-weight: bold;
}
</style>
`
Ammonia kalkylator
html`
<details>
<summary class="instruktion-toggle">Instruktion</summary>
<div class="instruktion-body">
Detta verktyg beräknar behandlingstiden för en 5-log<sub>10</sub> reduktion av Escherichia coli baserat på ureados och temperatur i behandlingsbrunn/reaktor. E. coli är en mag-tarm bakterie som alltid förekommer i klosettvatten och inaktiveringen av denna är ett bra mått på inaktiveringen av bakteriella smittämnen. En 5-log10 reduktion innebär att bakterien inaktiverats med 99,999%.
Vid en sådan inaktivering av E.coli bör klosettvattnet vara fritt från salmonella vilken förekommer i mycket lägre startkoncentrationer, om den förekommer. Saknar du startvärden kan du ange månad i rulllist och få en förväntad minimitemperatur i brunnen. Tid för nedbrytning av urea är inkluderad i behandingstiden och baseras på angiven temperatur och förutsätter omblandning. Dessa uppskattningar är tänkta att underlätta planering av ett system för kretslopp från klosettvatten. Inför gödsling bör innehållet av E. coli och salmonella analyseras.
</div>
</details>
<hr style="border-top:5px #03865a;">
`html`<div>
<h4><strong>Klosettvatten startvärden:</strong></h4>
</div>`
viewof ammokvave = Inputs.range([0, 10], {value: 0.3, step: 0.1, label: html`NH<sub>3</sub>/NH<sub>4</sub>+-N (g/L)`})
html`<p>Torrubstans, estimerad (%):<strong>${dry.toFixed(2)}</strong></p>`
html`<p>Total-N, estimerad (g/L): <strong>${kvave.toFixed(2)}</strong></p>`
html`<div>
<h4>Behandling:</h4>
</div>`
viewof va_vol = Inputs.range([0, 5000], {value: 1000,step: 100,label: "Klosettvatten volym (m³)"})
viewof urea = Inputs.range([0.3, 5], {value: 1,step: 0.1,label: "Urea-dos (%)"})
viewof pH = Inputs.range([0, 14], {value: 9,step: 0.1,label: "pH"})
options = [
{label: "Okt-mars", value: 10},
{label: "April-Juni", value: 15},
{label: "Juli-Sept", value: 20}
]
viewof manualTemp = Inputs.number({
value: selectedOption.value,
label: "Lagringstemperatur (°C), kan ändras manuellt:"
})
viewof selectedOption = Inputs.select(options, {
label: "Månad (välj i rullist):",
format: d => d.label // Shows label in dropdown
})
temp = manualTemp
dry = (ammokvave * 6.89622641509434) / 10
kvave = ammokvave * 1.17924528301887
tillsats = urea * 10
tillsatskg = tillsats * va_vol
sackar = tillsatskg / 750
NH3_NH4 = tillsats * 0.46 + ammokvave
pKa = (2729.92 / (temp + 273.15)) + 0.090181
f_nh3 = 1 / (10 ** (pKa - pH) + 1) * 100
nh3_gl = f_nh3 / 100 * NH3_NH4
// Import Observable Plot
import {Plot} from "@observablehq/plot"
// Coefficient for the day-dependent urea ramp (Excel: -0.8349 + 0.1617*temp)
ureaCoef = -0.8349 + 0.1617 * temp
// Safety cap on iterations in case the curve never reaches 0 (e.g. very low temp)
maxDays = 3650
// Day-by-day recursive decay (Excel formula):
// H_new = H_prev + (-0.8 * (0.46 * min(ureaCoef*day, tillsats) / 14.01 * 10 * f_nh3) * 0.0037 * exp(0.066*temp))
decayResult = {
let y = 5;
const xs = [0];
const ys = [5];
let day = 0;
while (y > 0 && day < maxDays) {
day += 1;
const ramp = ureaCoef * day;
const capped = Math.min(ramp, tillsats);
const increment = -0.8 * (0.46 * capped / 14.01 * 10 * f_nh3) * 0.0037 * Math.exp(0.066 * temp);
y = y + increment;
xs.push(day);
ys.push(y);
}
const n = xs.length;
const dagarVal = day < maxDays
? xs[n - 2] + (0 - ys[n - 2]) / (ys[n - 1] - ys[n - 2])
: NaN;
return {xvals: xs, yvals: ys, dagar: dagarVal};
}
// Total days for the 5-log reduction (interpolated day the curve crosses 0)
dagar = decayResult.dagar
//| echo: false
// x and y values for the plot
xvals = decayResult.xvals
yvals = decayResult.yvals
// Prepare data for Plot
plotData = xvals.map((x, i) => ({x: x, y: yvals[i]}))
// Create your plot (no container-dependent width)
myPlot = Plot.plot({
x: {
label: "Tid (dagar)",
labelFontSize: 20,
labelAnchor: "center",
tickSize: 8,
tickFormat: d => d.toFixed(0),
labelOffset: 40,
domain: [0, dagar + 2],
nice: false
},
y: {
label: "E.coli (log<sub>10</sub>/ml)",
labelFontSize: 8,
labelAnchor: "center",
tickSize: 6,
tickFormat: d => d.toFixed(1),
labelOffset: 60
},
marks: [
Plot.line(plotData, {x: "x", y: "y", stroke: "#03865a", strokeWidth: 3}),
Plot.ruleY([0])
],
width: 400,
height: 350,
marginLeft: 70,
marginBottom: 50,
style: { fontSize: "18px" },
grid: true
})
html`
<p class="print-timestamp"></p>
<div class="gray-box">
<h4>Hygienisering:</h4>
Tid för 5-log10 reduktion av E. coli: <mark>${dagar.toFixed(0)} dagar</mark> <br>
<br>
${myPlot}
</div>
<div class="gray-box">
<h4>Ureabehandling:</h4>
Tillsatsalinitet i rötreste:<mark> ${tillsats.toFixed(1)}</mark> (kg/m<sub>3</sub>)<br>
Tillsats: <mark>${tillsatskg.toFixed(0)}</mark> (kg)<br>
Antal säckar: <mark>${sackar.toFixed(1)}</mark> (à 750 kg)<br>
NH<sub>3</sub>/NH<sub>4</sub><sup>+</sup>-N: <mark>${NH3_NH4.toFixed(2)}</mark> (g/L)<br>
NH<sub>3</sub>: <mark>${f_nh3.toFixed(2)}</mark> (%)<br>
NH<sub>3</sub>: <mark>${nh3_gl.toFixed(2)}</mark> (g/L)<br>
</div>
`
pdfButton = {
const btn = html`<button id="pdf-download-btn" style="background-color: #03865a; color: white; border: none; padding: 8px 20px; border-radius: 4px;">Ladda ner PDF</button>`;
const originalLabel = btn.innerText;
btn.onclick = async () => {
btn.disabled = true;
btn.innerText = "Skapar PDF...";
const stamp = document.querySelector(".print-timestamp");
stamp.style.display = "block";
stamp.innerText = "Utskrivet: " + new Date().toLocaleString("sv-SE");
window.scrollTo(0, 0);
const target = document.body;
const canvas = await html2canvas(target, {
scale: 2,
backgroundColor: "#ffffff",
ignoreElements: (el) => el.id === "pdf-download-btn" || el.id === "quarto-header"
});
stamp.style.display = "none";
const { jsPDF } = window.jspdf;
const orientation = canvas.width > canvas.height ? "landscape" : "portrait";
const pdf = new jsPDF({ orientation, unit: "mm", format: "a4" });
const margin = 10;
const pageWidth = pdf.internal.pageSize.getWidth() - margin * 2;
const pageHeight = pdf.internal.pageSize.getHeight() - margin * 2;
let imgWidth = pageWidth;
let imgHeight = (canvas.height * imgWidth) / canvas.width;
if (imgHeight > pageHeight) {
imgHeight = pageHeight;
imgWidth = (canvas.width * imgHeight) / canvas.height;
}
pdf.addImage(canvas.toDataURL("image/png"), "PNG", margin, margin, imgWidth, imgHeight);
pdf.save("ammoniak-resultat.pdf");
btn.disabled = false;
btn.innerText = originalLabel;
};
return btn;
}Tiden för nedbrytning av urea beräknas från temperaturen och är tillagd i den total tiden för hygienisering.
Modellen för inaktivering av Escherichia coli beroende på NH3-koncentration och temperatur (Nordin 2010) kan användas för både salmonella och Escherichia coli inaktivering då dessa har visat sig ha liknande inaktivering vid ureabehandling.
Detta verktyg utvecklades inom projekt JTI-21-83-613 finansierat av JTI-Stiftelsen under tema Cirkularitet/restströmmar.