ecb for dashboard - WRONG NEEDS CORRECTION ASAP
This commit is contained in:
2
client
2
client
Submodule client updated: d8cbdbfcf1...c48deeb2ca
@@ -80,6 +80,17 @@ message GetEcbPipelineStatusResponse {
|
|||||||
// Distinct currencies observed on verified_through_date, so a coverage gap
|
// Distinct currencies observed on verified_through_date, so a coverage gap
|
||||||
// for one currency is visible even when the date itself is covered.
|
// for one currency is visible even when the date itself is covered.
|
||||||
repeated string covered_currencies = 8;
|
repeated string covered_currencies = 8;
|
||||||
|
|
||||||
|
// Current coverage restated as the question a reader actually has: what may
|
||||||
|
// be posted right now. Both are the latest basis date whose derived
|
||||||
|
// publication date is still covered, under the two rules
|
||||||
|
// required_publication_date applies -- transactions look strictly before the
|
||||||
|
// basis date, statements and decisive dates look at it directly, so the
|
||||||
|
// transaction bound is the later of the two. Derived here rather than by the
|
||||||
|
// caller so it cannot disagree with the calendar the conversion path uses.
|
||||||
|
// Absent when nothing has ever been verified and nothing can be posted.
|
||||||
|
optional string transactions_postable_through = 9;
|
||||||
|
optional string statements_postable_through = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion basis rule used to select an ECB publication.
|
// Conversion basis rule used to select an ECB publication.
|
||||||
|
|||||||
Binary file not shown.
@@ -69,6 +69,22 @@ pub struct GetEcbPipelineStatusResponse {
|
|||||||
/// for one currency is visible even when the date itself is covered.
|
/// for one currency is visible even when the date itself is covered.
|
||||||
#[prost(string, repeated, tag = "8")]
|
#[prost(string, repeated, tag = "8")]
|
||||||
pub covered_currencies: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
pub covered_currencies: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
/// Current coverage restated as the question a reader actually has: what may
|
||||||
|
/// be posted right now. Both are the latest basis date whose derived
|
||||||
|
/// publication date is still covered, under the two rules
|
||||||
|
/// required_publication_date applies -- transactions look strictly before the
|
||||||
|
/// basis date, statements and decisive dates look at it directly, so the
|
||||||
|
/// transaction bound is the later of the two. Derived here rather than by the
|
||||||
|
/// caller so it cannot disagree with the calendar the conversion path uses.
|
||||||
|
/// Absent when nothing has ever been verified and nothing can be posted.
|
||||||
|
#[prost(string, optional, tag = "9")]
|
||||||
|
pub transactions_postable_through: ::core::option::Option<
|
||||||
|
::prost::alloc::string::String,
|
||||||
|
>,
|
||||||
|
#[prost(string, optional, tag = "10")]
|
||||||
|
pub statements_postable_through: ::core::option::Option<
|
||||||
|
::prost::alloc::string::String,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
/// Exact inputs for a conversion preview. Decimal values are strings so the
|
/// Exact inputs for a conversion preview. Decimal values are strings so the
|
||||||
/// client never loses precision through binary floating point.
|
/// client never loses precision through binary floating point.
|
||||||
|
|||||||
2
server
2
server
Submodule server updated: 59acd7d8d3...09b145f9e3
@@ -69,6 +69,8 @@ pub(crate) async fn load_ecb_page(
|
|||||||
import_running: status.import_running,
|
import_running: status.import_running,
|
||||||
next_import_at: status.next_import_at,
|
next_import_at: status.next_import_at,
|
||||||
covered_currencies: status.covered_currencies,
|
covered_currencies: status.covered_currencies,
|
||||||
|
transactions_postable_through: status.transactions_postable_through,
|
||||||
|
statements_postable_through: status.statements_postable_through,
|
||||||
batches: status
|
batches: status
|
||||||
.batches
|
.batches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -37,8 +37,12 @@ fn coarse(duration: SignedDuration) -> String {
|
|||||||
format!("{hours}h {minutes}m")
|
format!("{hours}h {minutes}m")
|
||||||
} else if minutes > 0 {
|
} else if minutes > 0 {
|
||||||
format!("{minutes}m {}s", seconds % 60)
|
format!("{minutes}m {}s", seconds % 60)
|
||||||
} else {
|
} else if seconds > 0 {
|
||||||
format!("{seconds}s")
|
format!("{seconds}s")
|
||||||
|
} else {
|
||||||
|
// A run that finished inside the same second is fast, not instant, and
|
||||||
|
// "0s" reads like a missing measurement.
|
||||||
|
"<1s".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +137,8 @@ pub(crate) struct EcbPageState {
|
|||||||
pub next_import_at: String,
|
pub next_import_at: String,
|
||||||
pub batches: Vec<ImportBatchView>,
|
pub batches: Vec<ImportBatchView>,
|
||||||
pub covered_currencies: Vec<String>,
|
pub covered_currencies: Vec<String>,
|
||||||
|
pub transactions_postable_through: Option<String>,
|
||||||
|
pub statements_postable_through: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EcbPageState {
|
impl EcbPageState {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ mod tests {
|
|||||||
next_import_at: "2026-08-13T15:00:00Z".to_string(),
|
next_import_at: "2026-08-13T15:00:00Z".to_string(),
|
||||||
batches: vec![batch(9, "succeeded")],
|
batches: vec![batch(9, "succeeded")],
|
||||||
covered_currencies: vec!["CZK".to_string(), "USD".to_string()],
|
covered_currencies: vec!["CZK".to_string(), "USD".to_string()],
|
||||||
|
transactions_postable_through: Some("2026-08-13".to_string()),
|
||||||
|
statements_postable_through: Some("2026-08-12".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +133,20 @@ mod tests {
|
|||||||
assert!(html.contains("2 failed"), "{html}");
|
assert!(html.contains("2 failed"), "{html}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Coverage is a publication date; the reader's question is which dates
|
||||||
|
/// they may post. The card has to answer the second one outright, and the
|
||||||
|
/// two bounds differ because the two rules differ.
|
||||||
|
#[test]
|
||||||
|
fn the_card_says_which_dates_can_be_posted() {
|
||||||
|
let html = render_page(&healthy_page());
|
||||||
|
|
||||||
|
assert!(html.contains("What can be posted right now"), "{html}");
|
||||||
|
assert!(html.contains("Ordinary transactions"));
|
||||||
|
assert!(html.contains("<strong>2026-08-13</strong>"), "{html}");
|
||||||
|
assert!(html.contains("Statements & decisive dates"));
|
||||||
|
assert!(html.contains("<strong>2026-08-12</strong>"), "{html}");
|
||||||
|
}
|
||||||
|
|
||||||
/// A pipeline that has never succeeded is a different problem from one
|
/// A pipeline that has never succeeded is a different problem from one
|
||||||
/// that has fallen behind, and reads differently.
|
/// that has fallen behind, and reads differently.
|
||||||
#[test]
|
#[test]
|
||||||
@@ -140,12 +156,16 @@ mod tests {
|
|||||||
page.verified_through_date = None;
|
page.verified_through_date = None;
|
||||||
page.covered_currencies.clear();
|
page.covered_currencies.clear();
|
||||||
page.batches.clear();
|
page.batches.clear();
|
||||||
|
page.transactions_postable_through = None;
|
||||||
|
page.statements_postable_through = None;
|
||||||
|
|
||||||
let html = render_page(&page);
|
let html = render_page(&page);
|
||||||
|
|
||||||
assert!(!html.contains("Template error"), "{html}");
|
assert!(!html.contains("Template error"), "{html}");
|
||||||
assert!(html.contains("No rates have ever been imported"));
|
assert!(html.contains("No rates have ever been imported"));
|
||||||
assert!(html.contains("every conversion in a foreign currency is refused"));
|
assert!(html.contains("every conversion in a foreign currency is refused"));
|
||||||
|
// And the postable block says so too rather than showing an empty date.
|
||||||
|
assert!(html.contains("<strong>Nothing.</strong>"), "{html}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// While a batch runs the card watches itself, and the fragment it swaps
|
/// While a batch runs the card watches itself, and the fragment it swaps
|
||||||
|
|||||||
@@ -232,6 +232,17 @@
|
|||||||
.status-card.healthy { border-left-color: #21643a; }
|
.status-card.healthy { border-left-color: #21643a; }
|
||||||
.status-card.unhealthy { border-left-color: #a12b2b; }
|
.status-card.unhealthy { border-left-color: #a12b2b; }
|
||||||
.status-card.unhealthy h2 { color: #a12b2b; }
|
.status-card.unhealthy h2 { color: #a12b2b; }
|
||||||
|
/* The postable limits. This is what the reader came for -- coverage dates
|
||||||
|
are the mechanism behind it -- so it sits directly under the headline and
|
||||||
|
is the only thing on the card set at reading size. */
|
||||||
|
.postable { margin-top: 16px; padding: 14px; border: 1px solid #dfe4ea; border-radius: 8px; background: white; }
|
||||||
|
.postable-lead { margin: 0 0 10px; color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
|
||||||
|
.postable-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; margin: 0 0 10px; }
|
||||||
|
.postable-grid dt { color: #33415c; font-size: 13px; font-weight: 600; }
|
||||||
|
.postable-grid dd { margin: 3px 0 0; color: #17202a; font-size: 14px; }
|
||||||
|
.postable-grid dd strong { font-size: 16px; font-variant-numeric: tabular-nums; }
|
||||||
|
.postable-grid dd.hint { font-size: 12px; }
|
||||||
|
|
||||||
/* Coverage reads left to right as one sentence -- reached, gap, owed -- so
|
/* Coverage reads left to right as one sentence -- reached, gap, owed -- so
|
||||||
the two dates are compared rather than looked up one at a time. */
|
the two dates are compared rather than looked up one at a time. */
|
||||||
.coverage { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 16px; padding: 12px 14px; border-radius: 8px; background: #f7f9fc; }
|
.coverage { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 16px; padding: 12px 14px; border-radius: 8px; background: #f7f9fc; }
|
||||||
|
|||||||
@@ -16,6 +16,37 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<p class="hint">{{ page.explanation() }}</p>
|
<p class="hint">{{ page.explanation() }}</p>
|
||||||
|
|
||||||
|
{# The question a reader came with. Coverage is a publication date, and
|
||||||
|
turning it into "which transaction dates will post" means applying the
|
||||||
|
rule the backend applies -- so the backend applies it and sends both
|
||||||
|
bounds, rather than the page inviting the reader to do it in their head. #}
|
||||||
|
<div class="postable">
|
||||||
|
<p class="postable-lead">What can be posted right now</p>
|
||||||
|
{% if let Some(through) = page.transactions_postable_through %}
|
||||||
|
<dl class="postable-grid">
|
||||||
|
<div>
|
||||||
|
<dt>Ordinary transactions</dt>
|
||||||
|
<dd>dated up to and including <strong>{{ through }}</strong></dd>
|
||||||
|
<dd class="hint">Converted at the rate published the business day before the transaction.</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Statements & decisive dates</dt>
|
||||||
|
<dd>dated up to and including <strong>{{ page.statements_postable_through.as_deref().unwrap_or("—") }}</strong></dd>
|
||||||
|
<dd class="hint">Converted at the rate published on that day, or the last business day before it.</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<p class="hint">
|
||||||
|
A foreign-currency amount dated after its limit is refused outright until the
|
||||||
|
importer catches up — it is not converted at a stale rate.
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
<strong>Nothing.</strong> No publication has ever been verified, so every
|
||||||
|
foreign-currency amount is refused whatever its date.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{# Coverage first and on its own line: the two dates and the gap between
|
{# Coverage first and on its own line: the two dates and the gap between
|
||||||
them are the whole answer, and burying them in a four-column grid made
|
them are the whole answer, and burying them in a four-column grid made
|
||||||
them read as trivia. #}
|
them read as trivia. #}
|
||||||
|
|||||||
Reference in New Issue
Block a user