syntax = "proto3"; package komp_ac.ecb; // 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; } // Conversion basis rule used to select an ECB publication. enum EcbConversionContext { ECB_CONVERSION_CONTEXT_UNSPECIFIED = 0; ECB_CONVERSION_CONTEXT_ORDINARY_TRANSACTION = 1; ECB_CONVERSION_CONTEXT_FINANCIAL_STATEMENT = 2; ECB_CONVERSION_CONTEXT_DECISIVE_DATE = 3; } // 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; EcbConversionContext conversion_context = 3; // ISO calendar date (YYYY-MM-DD) from which the applicable publication date // is derived. Its meaning is selected by conversion_context: transaction, // statement, or decisive date. string conversion_basis_date = 4; // Required. Profile whose rates apply. Only consulted when use_custom_rate is // set, but always required so a preview names the books it describes. string profile_name = 5; // Preview the profile's own rate for the publication date rather than the ECB // reference rate. Must match what the posting will ask for, or the preview // describes a conversion that will not happen. bool use_custom_rate = 6; } // 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; EcbConversionContext conversion_context = 4; string conversion_basis_date = 5; string determination_method = 6; string rounding_method = 7; optional string rate_date = 8; optional string units_per_eur = 9; optional int64 rate_observation_id = 10; optional string observation_hash = 11; optional string source_payload_hash = 12; optional string rate_fetched_at = 13; optional int64 import_batch_id = 14; optional string source_endpoint = 15; // 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 = 16; optional int64 custom_rate_record_id = 17; optional int64 custom_rate_row_revision = 18; optional string custom_rate_note = 19; } // 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; } message ListEcbConversionEvidenceResponse { // True when at least one evidence row for this cell used a rate, whether it // came from ECB or from a hand-entered custom row. 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; }