strings not used internally11
This commit is contained in:
@@ -2,6 +2,8 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package komp_ac.analytics;
|
package komp_ac.analytics;
|
||||||
|
|
||||||
|
import "table_definition.proto";
|
||||||
|
|
||||||
import "google/protobuf/struct.proto";
|
import "google/protobuf/struct.proto";
|
||||||
|
|
||||||
// Runs read-only analytical SQL over runtime-defined profile tables.
|
// Runs read-only analytical SQL over runtime-defined profile tables.
|
||||||
@@ -37,7 +39,7 @@ message AnalyticsCatalogColumn {
|
|||||||
string name = 1;
|
string name = 1;
|
||||||
string field_type = 2;
|
string field_type = 2;
|
||||||
bool is_system = 3;
|
bool is_system = 3;
|
||||||
string rounding = 4;
|
komp_ac.table_definition.MoneyRounding rounding = 4;
|
||||||
// Canonical ISO-4217 code for MONEY; empty for every other type.
|
// Canonical ISO-4217 code for MONEY; empty for every other type.
|
||||||
string currency = 5;
|
string currency = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,13 +78,19 @@ message GetDocumentsRequest {
|
|||||||
optional string source_table_name = 5;
|
optional string source_table_name = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DocumentDataChangeKind {
|
||||||
|
DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED = 0;
|
||||||
|
DOCUMENT_DATA_CHANGE_KIND_GENERATED = 1;
|
||||||
|
DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Document {
|
message Document {
|
||||||
int64 snapshot_id = 1;
|
int64 snapshot_id = 1;
|
||||||
int64 document_id = 2;
|
int64 document_id = 2;
|
||||||
int64 schema_id = 3;
|
int64 schema_id = 3;
|
||||||
repeated SourceRecord source_records = 4;
|
repeated SourceRecord source_records = 4;
|
||||||
string data_json = 5;
|
string data_json = 5;
|
||||||
string change_kind = 6;
|
DocumentDataChangeKind change_kind = 6;
|
||||||
int32 version_number = 7;
|
int32 version_number = 7;
|
||||||
string created_at = 8;
|
string created_at = 8;
|
||||||
string updated_at = 9;
|
string updated_at = 9;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package komp_ac.exchange_rates;
|
package komp_ac.exchange_rates;
|
||||||
|
|
||||||
|
import "table_definition.proto";
|
||||||
|
|
||||||
import "common.proto";
|
import "common.proto";
|
||||||
|
|
||||||
// Selects WHICH DATE supplies the rate; this is independent of where the rate
|
// Selects WHICH DATE supplies the rate; this is independent of where the rate
|
||||||
@@ -106,7 +108,7 @@ message PreviewDirectConversionResponse {
|
|||||||
string accounting_currency = 4;
|
string accounting_currency = 4;
|
||||||
string conversion_basis_date = 5;
|
string conversion_basis_date = 5;
|
||||||
string determination_method = 6;
|
string determination_method = 6;
|
||||||
string rounding_method = 7;
|
komp_ac.table_definition.MoneyRounding rounding_method = 7;
|
||||||
optional string rate_date = 8;
|
optional string rate_date = 8;
|
||||||
optional string accounting_units = 9;
|
optional string accounting_units = 9;
|
||||||
optional string foreign_currency = 10;
|
optional string foreign_currency = 10;
|
||||||
@@ -149,7 +151,7 @@ message ConversionEvidence {
|
|||||||
string conversion_context = 10;
|
string conversion_context = 10;
|
||||||
string conversion_basis_date = 11;
|
string conversion_basis_date = 11;
|
||||||
string determination_method = 12;
|
string determination_method = 12;
|
||||||
string rounding_method = 13;
|
komp_ac.table_definition.MoneyRounding rounding_method = 13;
|
||||||
optional string rate_date = 14;
|
optional string rate_date = 14;
|
||||||
string source_id = 15;
|
string source_id = 15;
|
||||||
optional int64 official_observation_id = 16;
|
optional int64 official_observation_id = 16;
|
||||||
|
|||||||
@@ -8,6 +8,49 @@
|
|||||||
//! would reject, and each side renders the shared error as its own error type.
|
//! would reject, and each side renders the shared error as its own error type.
|
||||||
|
|
||||||
pub use rusty_money::iso;
|
pub use rusty_money::iso;
|
||||||
|
pub use crate::proto::komp_ac::table_definition::MoneyRounding;
|
||||||
|
|
||||||
|
impl MoneyRounding {
|
||||||
|
pub fn as_storage_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::None => "none",
|
||||||
|
Self::HalfUp => "half_up",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for MoneyRounding {
|
||||||
|
type Err = serde::de::value::Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
match value {
|
||||||
|
"none" => Ok(Self::None),
|
||||||
|
"half_up" => Ok(Self::HalfUp),
|
||||||
|
_ => Err(serde::de::Error::custom(format!("Invalid money rounding: {value}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for MoneyRounding {
|
||||||
|
type Error = serde::de::value::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod rounding_serde {
|
||||||
|
use super::MoneyRounding;
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(value: &MoneyRounding, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(value.as_storage_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<MoneyRounding, D::Error> {
|
||||||
|
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolves a canonical uppercase ISO-4217 alphabetic code to its currency.
|
/// Resolves a canonical uppercase ISO-4217 alphabetic code to its currency.
|
||||||
///
|
///
|
||||||
@@ -40,6 +83,34 @@ pub fn iso_currency_codes() -> Vec<&'static str> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rounding_storage_and_json_boundaries_are_strict() {
|
||||||
|
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct StoredPolicy {
|
||||||
|
#[serde(with = "super::rounding_serde")]
|
||||||
|
rounding: MoneyRounding,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (text, policy) in [("none", MoneyRounding::None), ("half_up", MoneyRounding::HalfUp)] {
|
||||||
|
assert_eq!(text.parse::<MoneyRounding>().unwrap(), policy);
|
||||||
|
assert_eq!(MoneyRounding::try_from(text.to_string()).unwrap(), policy);
|
||||||
|
assert_eq!(policy.as_storage_str(), text);
|
||||||
|
let stored = StoredPolicy { rounding: policy };
|
||||||
|
let json = serde_json::json!({ "rounding": text });
|
||||||
|
assert_eq!(serde_json::to_value(&stored).unwrap(), json);
|
||||||
|
assert_eq!(serde_json::from_value::<StoredPolicy>(json).unwrap(), stored);
|
||||||
|
}
|
||||||
|
for text in ["", "NONE", "HalfUp", "half-up", " half_up", "half_up ", "unknown"] {
|
||||||
|
assert!(text.parse::<MoneyRounding>().is_err());
|
||||||
|
assert!(serde_json::from_value::<StoredPolicy>(serde_json::json!({ "rounding": text })).is_err());
|
||||||
|
}
|
||||||
|
for value in [serde_json::Value::Null, serde_json::json!(0), serde_json::json!(1)] {
|
||||||
|
assert!(serde_json::from_value::<StoredPolicy>(serde_json::json!({ "rounding": value })).is_err());
|
||||||
|
}
|
||||||
|
assert!(MoneyRounding::try_from(-1).is_err());
|
||||||
|
assert!(MoneyRounding::try_from(2).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn canonical_codes_resolve_to_their_currency() {
|
fn canonical_codes_resolve_to_their_currency() {
|
||||||
assert_eq!(require_iso_currency_code("EUR").unwrap(), iso::EUR);
|
assert_eq!(require_iso_currency_code("EUR").unwrap(), iso::EUR);
|
||||||
|
|||||||
Binary file not shown.
@@ -36,8 +36,8 @@ pub struct AnalyticsCatalogColumn {
|
|||||||
pub field_type: ::prost::alloc::string::String,
|
pub field_type: ::prost::alloc::string::String,
|
||||||
#[prost(bool, tag = "3")]
|
#[prost(bool, tag = "3")]
|
||||||
pub is_system: bool,
|
pub is_system: bool,
|
||||||
#[prost(string, tag = "4")]
|
#[prost(enumeration = "super::table_definition::MoneyRounding", tag = "4")]
|
||||||
pub rounding: ::prost::alloc::string::String,
|
pub rounding: i32,
|
||||||
/// Canonical ISO-4217 code for MONEY; empty for every other type.
|
/// Canonical ISO-4217 code for MONEY; empty for every other type.
|
||||||
#[prost(string, tag = "5")]
|
#[prost(string, tag = "5")]
|
||||||
pub currency: ::prost::alloc::string::String,
|
pub currency: ::prost::alloc::string::String,
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ pub struct Document {
|
|||||||
pub source_records: ::prost::alloc::vec::Vec<SourceRecord>,
|
pub source_records: ::prost::alloc::vec::Vec<SourceRecord>,
|
||||||
#[prost(string, tag = "5")]
|
#[prost(string, tag = "5")]
|
||||||
pub data_json: ::prost::alloc::string::String,
|
pub data_json: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag = "6")]
|
#[prost(enumeration = "DocumentDataChangeKind", tag = "6")]
|
||||||
pub change_kind: ::prost::alloc::string::String,
|
pub change_kind: i32,
|
||||||
#[prost(int32, tag = "7")]
|
#[prost(int32, tag = "7")]
|
||||||
pub version_number: i32,
|
pub version_number: i32,
|
||||||
#[prost(string, tag = "8")]
|
#[prost(string, tag = "8")]
|
||||||
@@ -221,6 +221,36 @@ pub struct GetTypstTemplateVersionResponse {
|
|||||||
#[prost(message, optional, tag = "1")]
|
#[prost(message, optional, tag = "1")]
|
||||||
pub template_version: ::core::option::Option<TypstTemplateVersion>,
|
pub template_version: ::core::option::Option<TypstTemplateVersion>,
|
||||||
}
|
}
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum DocumentDataChangeKind {
|
||||||
|
Unspecified = 0,
|
||||||
|
Generated = 1,
|
||||||
|
UserAdjustment = 2,
|
||||||
|
}
|
||||||
|
impl DocumentDataChangeKind {
|
||||||
|
/// String value of the enum field names used in the ProtoBuf definition.
|
||||||
|
///
|
||||||
|
/// The values are not transformed in any way and thus are considered stable
|
||||||
|
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||||
|
pub fn as_str_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Unspecified => "DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED",
|
||||||
|
Self::Generated => "DOCUMENT_DATA_CHANGE_KIND_GENERATED",
|
||||||
|
Self::UserAdjustment => "DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||||
|
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||||
|
match value {
|
||||||
|
"DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED" => Some(Self::Unspecified),
|
||||||
|
"DOCUMENT_DATA_CHANGE_KIND_GENERATED" => Some(Self::Generated),
|
||||||
|
"DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT" => Some(Self::UserAdjustment),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Generated client implementations.
|
/// Generated client implementations.
|
||||||
pub mod document_data_service_client {
|
pub mod document_data_service_client {
|
||||||
#![allow(
|
#![allow(
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ pub struct PreviewDirectConversionResponse {
|
|||||||
pub conversion_basis_date: ::prost::alloc::string::String,
|
pub conversion_basis_date: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag = "6")]
|
#[prost(string, tag = "6")]
|
||||||
pub determination_method: ::prost::alloc::string::String,
|
pub determination_method: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag = "7")]
|
#[prost(enumeration = "super::table_definition::MoneyRounding", tag = "7")]
|
||||||
pub rounding_method: ::prost::alloc::string::String,
|
pub rounding_method: i32,
|
||||||
#[prost(string, optional, tag = "8")]
|
#[prost(string, optional, tag = "8")]
|
||||||
pub rate_date: ::core::option::Option<::prost::alloc::string::String>,
|
pub rate_date: ::core::option::Option<::prost::alloc::string::String>,
|
||||||
#[prost(string, optional, tag = "9")]
|
#[prost(string, optional, tag = "9")]
|
||||||
@@ -141,8 +141,8 @@ pub struct ConversionEvidence {
|
|||||||
pub conversion_basis_date: ::prost::alloc::string::String,
|
pub conversion_basis_date: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag = "12")]
|
#[prost(string, tag = "12")]
|
||||||
pub determination_method: ::prost::alloc::string::String,
|
pub determination_method: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag = "13")]
|
#[prost(enumeration = "super::table_definition::MoneyRounding", tag = "13")]
|
||||||
pub rounding_method: ::prost::alloc::string::String,
|
pub rounding_method: i32,
|
||||||
#[prost(string, optional, tag = "14")]
|
#[prost(string, optional, tag = "14")]
|
||||||
pub rate_date: ::core::option::Option<::prost::alloc::string::String>,
|
pub rate_date: ::core::option::Option<::prost::alloc::string::String>,
|
||||||
#[prost(string, tag = "15")]
|
#[prost(string, tag = "15")]
|
||||||
|
|||||||
2
server
2
server
Submodule server updated: 6d6f241b0b...cac5233a11
@@ -2,6 +2,7 @@
|
|||||||
//! responses into the view models the templates render.
|
//! responses into the view models the templates render.
|
||||||
|
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
|
use common::money::MoneyRounding;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
@@ -60,7 +61,7 @@ pub(crate) async fn load_catalog(
|
|||||||
.await
|
.await
|
||||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
Ok(catalog_view(&catalog))
|
catalog_view(&catalog)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_query(
|
pub(crate) async fn run_query(
|
||||||
@@ -141,15 +142,15 @@ fn value_output(value: crate::analytics::AnalyticsValue) -> serde_json::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn catalog_view(catalog: &GetAnalyticsCatalogResponse) -> CatalogView {
|
fn catalog_view(catalog: &GetAnalyticsCatalogResponse) -> Result<CatalogView, LoadError> {
|
||||||
CatalogView {
|
Ok(CatalogView {
|
||||||
tables: catalog.tables.iter().map(catalog_table_view).collect(),
|
tables: catalog.tables.iter().map(catalog_table_view).collect::<Result<_, _>>()?,
|
||||||
llm_context: build_llm_context(catalog),
|
llm_context: build_llm_context(catalog)?,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView {
|
fn catalog_table_view(table: &AnalyticsTable) -> Result<CatalogTableView, LoadError> {
|
||||||
CatalogTableView {
|
Ok(CatalogTableView {
|
||||||
name: table.name.clone(),
|
name: table.name.clone(),
|
||||||
starter_query: format!("SELECT *\nFROM {}\nLIMIT 100;", quote_identifier(&table.name)),
|
starter_query: format!("SELECT *\nFROM {}\nLIMIT 100;", quote_identifier(&table.name)),
|
||||||
columns: table
|
columns: table
|
||||||
@@ -160,19 +161,21 @@ fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView {
|
|||||||
if column.is_system {
|
if column.is_system {
|
||||||
details.push_str(", system");
|
details.push_str(", system");
|
||||||
}
|
}
|
||||||
if !column.rounding.is_empty() {
|
let rounding = MoneyRounding::try_from(column.rounding)
|
||||||
details.push_str(&format!(", rounding {}", column.rounding));
|
.map_err(|error| LoadError::Backend(error.to_string()))?;
|
||||||
|
if !column.is_system {
|
||||||
|
details.push_str(&format!(", rounding {}", rounding.as_storage_str()));
|
||||||
}
|
}
|
||||||
if !column.currency.is_empty() {
|
if !column.currency.is_empty() {
|
||||||
details.push_str(&format!(", currency {}", column.currency));
|
details.push_str(&format!(", currency {}", column.currency));
|
||||||
}
|
}
|
||||||
CatalogColumnView {
|
Ok::<_, LoadError>(CatalogColumnView {
|
||||||
name: column.name.clone(),
|
name: column.name.clone(),
|
||||||
insert_text: quote_identifier(&column.name),
|
insert_text: quote_identifier(&column.name),
|
||||||
details,
|
details,
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect(),
|
})
|
||||||
|
.collect::<Result<_, _>>()?,
|
||||||
links: table
|
links: table
|
||||||
.links
|
.links
|
||||||
.iter()
|
.iter()
|
||||||
@@ -182,10 +185,10 @@ fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView {
|
|||||||
required: link.required,
|
required: link.required,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_llm_context(catalog: &GetAnalyticsCatalogResponse) -> String {
|
fn build_llm_context(catalog: &GetAnalyticsCatalogResponse) -> Result<String, LoadError> {
|
||||||
let mut text = format!(
|
let mut text = format!(
|
||||||
"Write one read-only PostgreSQL SELECT query for the komp_ac analytics API.\n\
|
"Write one read-only PostgreSQL SELECT query for the komp_ac analytics API.\n\
|
||||||
Profile: {}\n\n\
|
Profile: {}\n\n\
|
||||||
@@ -212,8 +215,10 @@ AVAILABLE ANALYTICS SCHEMA\n",
|
|||||||
if column.is_system {
|
if column.is_system {
|
||||||
text.push_str(" [system]");
|
text.push_str(" [system]");
|
||||||
}
|
}
|
||||||
if !column.rounding.is_empty() {
|
let rounding = MoneyRounding::try_from(column.rounding)
|
||||||
text.push_str(&format!(" [rounding: {}]", column.rounding));
|
.map_err(|error| LoadError::Backend(error.to_string()))?;
|
||||||
|
if !column.is_system {
|
||||||
|
text.push_str(&format!(" [rounding: {}]", rounding.as_storage_str()));
|
||||||
}
|
}
|
||||||
if !column.currency.is_empty() {
|
if !column.currency.is_empty() {
|
||||||
text.push_str(&format!(" [currency: {}]", column.currency));
|
text.push_str(&format!(" [currency: {}]", column.currency));
|
||||||
@@ -232,7 +237,7 @@ AVAILABLE ANALYTICS SCHEMA\n",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quote_identifier(identifier: &str) -> String {
|
fn quote_identifier(identifier: &str) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user