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; EXCHANGE_RATE_SOURCE_ECB = 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 EUR. optional string manual_units_per_eur = 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 eur_amount = 3; string conversion_basis_date = 4; string determination_method = 5; string rounding_method = 6; optional string rate_date = 7; optional string units_per_eur = 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_eur 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; } // 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 eur_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_eur = 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_eur. 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; } 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; }