syntax = "proto3"; package komp_ac.ecb; // How the publication date is selected from the accounting basis date. enum ExchangeRateDateRule { EXCHANGE_RATE_DATE_RULE_UNSPECIFIED = 0; // Latest TARGET publication strictly before the basis date. EXCHANGE_RATE_DATE_RULE_PREVIOUS_PUBLICATION = 1; // Latest TARGET publication on or before the basis date. EXCHANGE_RATE_DATE_RULE_ON_OR_BEFORE_DATE = 2; // The exact publication date supplied in specific_rate_date. EXCHANGE_RATE_DATE_RULE_SPECIFIC_PUBLICATION_DATE = 3; } enum ExchangeRateSource { EXCHANGE_RATE_SOURCE_UNSPECIFIED = 0; // The rate source's own published reference rate (ECB's, today). EXCHANGE_RATE_SOURCE_OFFICIAL = 1; // A reusable rate from the profile's custom_exchange_rates table. EXCHANGE_RATE_SOURCE_SAVED_CUSTOM = 2; // A one-off rate recorded only in this posting's immutable evidence. EXCHANGE_RATE_SOURCE_MANUAL = 3; } // Accountant-controlled conversion treatment. Exceptional choices remain // explicit and auditable rather than being inferred from a legal label. message ExchangeRateSelection { ExchangeRateDateRule date_rule = 1; ExchangeRateSource source = 2; // Required for SPECIFIC_PUBLICATION_DATE and MANUAL; YYYY-MM-DD. optional string specific_rate_date = 3; // Required only for MANUAL. Exact foreign-currency units per one unit of // the rate source's pivot currency (EUR for ECB). optional string manual_units_per_pivot = 4; // Required for SPECIFIC_PUBLICATION_DATE and MANUAL. string reason = 5; } // Read-only access to ECB conversion previews and audit evidence. // // This service exists for manual visual verification before and after posting. // Preview performs the authoritative unrounded backend calculation without // writing; evidence exposes conversions already recorded by posting workflows. service EcbService { // Preview the exact conversion the backend would currently perform. // This reads local verified ECB data and never persists conversion evidence. rpc PreviewEcbConversion(PreviewEcbConversionRequest) returns (PreviewEcbConversionResponse); // List immutable ECB conversion evidence for one money value. rpc ListEcbConversionEvidence(ListEcbConversionEvidenceRequest) returns (ListEcbConversionEvidenceResponse); // Health of the reference-rate import pipeline: how far verified coverage // reaches, how far it should reach by now, and how the recent import // attempts went. Not profile-scoped -- one pipeline feeds every profile. rpc GetEcbPipelineStatus(GetEcbPipelineStatusRequest) returns (GetEcbPipelineStatusResponse); } message GetEcbPipelineStatusRequest { // Optional. How many recent import attempts to return. Zero uses 20; the // maximum is 100. int32 batch_limit = 1; } // One row of the append-only import audit log. message EcbImportBatch { int64 batch_id = 1; // "running", "succeeded" or "failed". string status = 2; string requested_from = 3; string requested_through = 4; string endpoint = 5; string started_at = 6; optional string completed_at = 7; // Present on a batch that advanced coverage. optional string verified_through_date = 8; optional int32 observation_count = 9; // May be lower than observation_count: an observation already recorded by an // earlier batch is kept rather than replaced. optional int32 inserted_observation_count = 10; optional string error_message = 11; } message GetEcbPipelineStatusResponse { // Latest date a successful batch verified. Absent when nothing has ever // succeeded, which is what a pipeline that has never run looks like. optional string verified_through_date = 1; // The date coverage should have reached by now, derived from the same // publication rule the importer schedules against. Comparing the two is what // "healthy" means. string latest_verifiable_date = 2; // True when verified coverage has reached latest_verifiable_date. A // conversion whose publication date falls beyond coverage is refused, so // this answers "will posting work right now". bool healthy = 3; // Publication days between coverage and latest_verifiable_date. Zero when // healthy. int32 days_behind = 4; // True while a batch holds the single-running lock. bool import_running = 5; // When the scheduler next wakes, from the same cutoff the importer uses. string next_import_at = 6; // Newest first, running and failed attempts included. repeated EcbImportBatch batches = 7; // Distinct currencies observed on verified_through_date, so a coverage gap // for one currency is visible even when the date itself is covered. 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; } // Exact inputs for a conversion preview. Decimal values are strings so the // client never loses precision through binary floating point. message PreviewEcbConversionRequest { string original_amount = 1; string original_currency = 2; // ISO calendar date (YYYY-MM-DD) of the accounting case. string conversion_basis_date = 3; // Required. Profile whose rates apply. Consulted when the selection requests // a saved custom rate, and always required so the preview names its books. string profile_name = 4; // Empty uses previous publication and ECB. optional ExchangeRateSelection exchange_rate_selection = 5; } // The conversion and immutable local ECB observation that would be used now. // No rounding is performed and no conversion evidence is persisted. message PreviewEcbConversionResponse { string original_amount = 1; string original_currency = 2; string converted_amount = 3; string conversion_basis_date = 4; string determination_method = 5; string rounding_method = 6; optional string rate_date = 7; optional string units_per_pivot = 8; optional int64 rate_observation_id = 9; optional string observation_hash = 10; optional string source_payload_hash = 11; optional string rate_fetched_at = 12; optional int64 import_batch_id = 13; optional string source_endpoint = 14; // Present only when determination_method is custom_rate. rate_date and // units_per_pivot then describe the hand-entered row identified here, and // the ECB observation fields above are empty. optional int64 custom_rate_table_definition_id = 15; optional int64 custom_rate_record_id = 16; optional int64 custom_rate_row_revision = 17; optional string custom_rate_note = 18; // Accountant-supplied justification for an exceptional publication date or // one-off manual rate. Empty for the normal/default rule. ExchangeRateDateRule applied_date_rule = 19; ExchangeRateSource applied_source = 20; string selection_reason = 21; // The currency converted_amount is stated in: the profile's accounting // currency (EUR unless the profile keeps its books in something else). string converted_currency = 22; // True when reaching converted_currency needed two hops (original currency // to EUR, then EUR to converted_currency) because neither is EUR. The // fields above then describe only the second, final hop; the full // provenance of both hops is available from ListEcbConversionEvidence // after posting. bool via_two_hop_conversion = 23; } // Identify one logical money cell whose conversion history should be inspected. message ListEcbConversionEvidenceRequest { // Required. Profile containing the table. string profile_name = 1; // Required. Logical table name within the profile. string table_name = 2; // Required. Dynamic table row id. int64 record_id = 3; // Required. Current display name of the money column. The server resolves it // to the stable physical column name used by immutable evidence. string column_name = 4; // Optional. Maximum rows to return. Zero uses 100; maximum is 500. int32 limit = 5; // Optional cursor. Return evidence ids lower than this value. optional int64 before_evidence_id = 6; } // One immutable explanation of how a posted EUR value was determined. message EcbConversionEvidence { int64 evidence_id = 1; // Exact committed version of the target dynamic-table row. int64 row_revision = 2; string original_amount = 3; string original_currency = 4; string converted_amount = 5; string conversion_context = 6; string conversion_basis_date = 7; string determination_method = 8; string rounding_method = 9; optional string rate_date = 10; optional string units_per_pivot = 11; optional int64 rate_observation_id = 12; optional string observation_hash = 13; optional string source_payload_hash = 14; optional string rate_fetched_at = 15; optional int64 import_batch_id = 16; optional string source_endpoint = 17; string evidence_created_at = 18; // Present only when determination_method is custom_rate. The hand-entered row // that supplied rate_date and units_per_pivot. The row itself stays editable, so // the figures above are the immutable record of what was actually applied. optional int64 custom_rate_table_definition_id = 19; optional int64 custom_rate_record_id = 20; optional int64 custom_rate_row_revision = 21; optional string custom_rate_note = 22; // Immutable audit data for an accountant-controlled exception. string selection_reason = 23; optional string selected_by_user_id = 24; // The currency converted_amount is stated in. Always EUR for the // original-currency-to-EUR leg; the profile's native currency for the // EUR-to-native leg that follows it when that native currency is not EUR. string converted_currency = 25; // Set only on the EUR-to-native-currency leg of a two-hop conversion. Names // the original-currency-to-EUR leg's evidence_id, which produced this row's // original_amount/original_currency (always EUR). optional int64 derived_from_evidence_id = 26; // The pivot currency of the rate source that priced this hop (EUR for // every hop today). A rate-based hop always has this currency on exactly // one side of original_currency/converted_currency. string pivot_currency = 27; // Which rate source's policy governs this hop's profile ("ecb" for every // hop today). Absent only for already_same_currency, where no policy // needed to be consulted. This is about which policy applies, not about // who actually supplied this specific rate -- see observation_source_id // for that. optional string policy_source_id = 28; // Which rate source actually supplied this hop's rate as its own official // observation. Present only when determination_method is // official_reference_rate; absent for custom_rate, manual_rate, and // already_same_currency, since none of those were priced by the policy's // source itself -- a human entered them. optional string observation_source_id = 29; } message ListEcbConversionEvidenceResponse { // True when at least one evidence row for this cell used an ECB, saved // custom, or one-off manual rate. bool has_exchange = 1; // Newest-first immutable history for this page. repeated EcbConversionEvidence evidence = 2; // True when another page exists using the last evidence_id as the cursor. bool has_more = 3; }